The user-defined guard pattern

Knowing the type of an interface or type can be challenging. We saw in this chapter the use of a discriminator. However, there is a drawback with the commonly named string literal approach, which is with inheritance and intersection. The following code does not compile:

interface Type1 extends Type2 {
kind: "Type1"; // Does not compile, expect “Type2”
m1: number;
}

interface Type2 {
kind: "Type2";
m2: string;
}

The same is true with an intersection:

interface Type2 {
kind: "Type2";
m2: string;
}

interface Type3 {
kind: "Type3";
m3: string;
}

type Type4 = Type2 & Type3;
const type4: Type4 = { kind: ???, m2: "1", m3: "2" }; // Does not compile

The last code example creates for the member kind a type that requires to be both strings literal at the same time make it impossible to fulfill and not practical. With that information to hand, we can see that the discriminator pattern works well when inheritance is avoided as well as intersecting. The idea is to use a custom user-defined guard per type. This can be cumbersome to create but will ensure you the type at design and runtime. The idea is to check for fields and see whether they are defined. This technique works well for a type with no optional field, since you need to check whether fields exist. As the author of the function and the type, you do not need to check every field. You should know which field is enough to identify the type. In the following code, both types exist, and one type extends the other. Two type user-defined guards are created—one for each interface:


interface Type1 extends Type2 {
m1: number;
}

interface Type2 {
m2: string;
m3: number;
}

function checkInterfaceICheck1(obj: any): obj is Type1 {
const type1WithMaybeManyUndefinedMembers = (obj as Type1);
return type1WithMaybeManyUndefinedMembers.m1 !== undefined
&& type1WithMaybeManyUndefinedMembers.m2 !== undefined
&& type1WithMaybeManyUndefinedMembers.m3 !== undefined
}

function checkInterfaceICheck2(obj: any): obj is Type2 {
const type1WithMaybeManyUndefinedMembers = (obj as Type2);
return type1WithMaybeManyUndefinedMembers.m2 !== undefined
&& type1WithMaybeManyUndefinedMembers.m3 !== undefined;
}

function codeWithUnionParameter(obj: Type1 | Type2): void {
if (checkInterfaceICheck1(obj)) {
console.log("Type1", obj.m1);
}

if (checkInterfaceICheck2(obj)) {
console.log("Type2", obj.m2);
}
}

The function must know which of the two types are passed, and it checks by using the user-defined guard. The return type of the defined guard is unique. It uses the name of the parameter followed by is and the type we are expecting if the value is true. It allows to automatically narrow down to the expected type by comparing the structure. If everything is present and defined, it returns true, but the function will not return an actual boolean value. It returns the object cast to the type.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.245.1