Type assertions in TypeScript

1. Introduction to type assertion

That is, for values without type declaration, sometimes when ts type inference, the inference result is incorrect, so ts provides a type assertion method to tell the compiler what type the value here is. , and once ts discovers that there is a type assertion, it will no longer perform type inference on the value and directly give the asserted type.

The essence of type assertion: allows developers to bypass the compiler’s type inference at a certain point, so that code that originally fails the type check can pass, avoiding the compiler’s error, although this weakens the ts type system strict, but also brings convenience to developers.

In short, type assertions do not actually change the type of a value, but indicate to the compiler how to handle the value.

Writing method:

// Syntax 1: <type> value
<Type>value

// Syntax 2: value as type
value as Type

Application

  • The object type will strictly check the literal. If there are additional attributes, an error will be reported. This can be solved by type assertion.

Writing method 1: Assert that the right type is changed to be consistent with the left type

Writing method 2: The right type becomes a subtype of the left type

// Error report
const p:{<!-- --> x: number } = {<!-- --> x: 0, y: 0 };
// Correct writing method one
const p0:{<!-- --> x: number } =
  {<!-- --> x: 0, y: 0 } as {<!-- --> x: number };

// Correct writing method 2
const p1:{<!-- --> x: number } =
  {<!-- --> x: 0, y: 0 } as {<!-- --> x: number; y: number };
  • Sometimes you can’t abuse it, otherwise you will get an error when running.

For example, if the object itself does not have a length attribute, and a type assertion is used to forcibly set it, although it can pass the compile-time detection, an error will be reported at runtime.

  • Specify the specific type of the unknown type variable, because the unknown type variable cannot be assigned to other types, but it can be done through type assertion.
  • You can also specify the specific type of the value of a union type. Originally, a variable is a value of a union type, but its specific type can be specified through type assertions.
const s1:number|string = 'hello';
const s2:number = s1 as number;

2. Conditions for type assertion

Type assertion does not assert that a certain value is of any type, but requires certain conditions, that is, the specified type is a subtype of the actual value or the actual value is a subtype of the specified type.

That is to say, type assertion requires that the actual type be compatible with the asserted type. The actual type can be asserted as a more general type parent type, or it can be asserted as a more precise subtype, but it cannot be asserted as a completely unrelated type.

If you really want to assert an unrelated type, you can make two type assertions, first asserting it to the any or unknown type, and then asserting it to the target type, because the any and unknown types are the parent types of all other types and can be used as two completely unrelated types. type of intermediary.

// Or written as <T><unknown>expr
expr as unknown as T

3. as const assertion

If the type of the variable is not declared, the variable named let will be inferred by ts as the built-in basic type, while the variable declared by const will be inferred as the value type.

as constEssence: The type of the literal will be asserted as an immutable type and reduced to the smallest type allowed by TypeScript.

Question

Sometimes variables declared by let may report an error, which can be solved by using const.

For example, when a function passes parameters, it passes the string declared by let, and the parameter type is a union type. At this time, an error will be reported, because the string type is the parent class of the union type.

Solution:

  • Change the let command to a const command and change it to a value type, so that no error will be reported.
  • Use type assertion as const to tell the compiler that when inferring the type, infer this type as a constant, that is, assert the let variable as a const variable, which changes the type from a string to a value. type. At this time let has become const, so the variable can no longer have the value at this time.
let s = 'JavaScript' as const;
setLang(s); // correct

Points to note when using as const

  • It can only be used in literals, not variables, otherwise an error will be reported.
  • cannot be used in expressions
let s = ('Java' + 'Script') as const; // Report an error
for objects

It can be used for the entire object or for individual attributes of the object. Its type reduction effect is different.

const v1 = {<!-- -->
  x: 1,
  y: 2,
}; // The type is { x: number; y: number; }

const v2 = {<!-- -->
  x: 1 as const,
  y: 2,
}; // type is { x: 1; y: number; }

const v3 = {<!-- -->
  x: 1,
  y: 2,
} as const; // The type is { readonly x: 1; readonly y: 2; }

After using the as const assertion for an array, the type is inferred as a read-only tuple. This is very suitable for the rest parameter of the function.

const nums = [1, 2] as const;
const total = add(...nums); // Correct
Enum members

Enum members can also use as const assertions and cannot be changed after use.

4. Non-null assertion

That is, for variables that may be empty (the value is equal to undefined or hard), ts provides a non-null assertion. When ts infers, it infers that the variable is not empty. To write it, add an exclamation point! directly after the variable name.

Excessive use of non-null assertions will cause security risks, so you must ensure that certain variables are not null.

Non-null assertions can be used for assignment assertions, because ts has a compilation option that requires the attributes of the class to be initialized. If not initialized, an error will be reported. At this time, you can use non-null assertions to indicate that these two attributes will definitely have values. An error will be reported.

class Point {<!-- -->
  x!:number; // Correct
  y!:number; // correct

  constructor(x:number, y:number) {<!-- -->
    // ...
  }
}

5. Assertion function

Assertion function is a special function used to ensure that function parameters conform to a certain type. If the function parameters do not meet the requirements, an error will be thrown and program execution will be interrupted. If the requirements are met, the code will be executed normally.

Assertion function before ts3.7

Used to ensure that the parameter value is a string, otherwise an error will be thrown and execution will be interrupted.

function isString(value:unknown):void {<!-- -->
  if (typeof value !== 'string')
    throw new Error('Not a string');
}

Disadvantages: The parameter type is unknown and the return value type is void. Because declaring the type in this way, it is not very clear to express the assertion function.

ts3.7 introduces new type writing

The return value type is written as asserts value is string, where asserts and is are keywords, value is the parameter name of the function, and string is the expected type of the function parameter. What it means is that this function is used to assert that the type of parameter value is string, and if it does not meet the requirements, an error will be reported.

function isString(value:unknown):asserts value is string {<!-- -->
  if (typeof value !== 'string')
    throw new Error('Not a string');
}

Note: The assertion writing method of function return value is only to express the function intention. The real verification needs to be deployed by the developer himself. And if the internal persistence is inconsistent with the assertion, ts will not report an error

The assertion of the function is that the parameter value type is a string, but the internal check is a numeric value, and no error will be thrown.

function isString(value:unknown):asserts value is string {<!-- -->
  if (typeof value !== 'number')
    throw new Error('Not a number');
}

In addition, the asserts statement of the assertion function is equivalent to the void type, so if a value other than undefined and null is returned, an error will be reported.

If you want to assert that a parameter is non, you can use the utility type NoNullable

function assertIsDefined<T>(
  value:T
):asserts value is NonNullable<T> {<!-- -->
  if (value === undefined || value === null) {<!-- -->
    throw new Error(`${<!-- -->value} is not defined`)
  }
}

Used in function expressions

//Writing method 1
const assertIsNumber = (
  value:unknown
):asserts value is number => {<!-- -->
  if (typeof value !== 'number')
    throw Error('Not a number');
};

//Writing method two
type AssertIsNumber =
  (value:unknown) => asserts value is number;

const assertIsNumber:AssertIsNumber = (value) => {<!-- -->
  if (typeof value !== 'number')
    throw Error('Not a number');
};

Assertion functions and type guard functions are two different functions. The difference between them is that asserted functions do not return a value, while type-protected functions always return a Boolean value.

If you want to assert that a parameter is guaranteed to be true (that is, not equal to false, undefined, and null), you can use a shorthand form. Use asserts x directly. This shorthand form is usually used to check whether an operation was successful.

function assert(x:unknown):asserts x {<!-- -->
  // ...
}