[Official Document](TypeScript: JavaScript With Syntax For Types.)
> The following is a condensed version. If you don’t want to read the full article, just read the following content.
ts has 8 built-in types
- Number
- String
- BigInt
- Boolean
- Symbol
- Null
- Undefined
- Object
For more details, please refer to [MDN](JavaScript data types and data structures – JavaScript | MDN),
Note: All primitive types can be tested using typeof. Except for null, typeof null returns ‘object’ and needs to be tested using ===null.
Other important ts types
- unKnown
- never
- object literal eg{property:Type}
- void for functions with no documented return value
- T[] Array[T]
- [T,T] tuples,
- (t:T) => U functions
Notes:
1. The syntax of the method contains the names of the parameters, which is difficult to use.
let fast:(a:any,b:any)=>any=(a,b)=>a; //More accurate let fast:<T,U>(a:T,b:U)=>T=(a,b)=>a;
2. The object type indicates the syntax of the object value.
let o:{n:number;xs:object[]} = {n:1,xs:[]};
Boxed types
Javascript has boxed equivalents of basic types that contain methods relevant to these types by programmers. Typescript reflects this, for example, the difference between the primitive type number and the boxed type Number. Boxed types are rarely needed because their methods return primitive methods
(1).toExponential(); // equivalent to Number.prototype.toExponential.call(1);
Gradual typing
Typescript uses the type any when it cannot determine the type of an expression. Compared to Dynamic, calling any a type is an overstatement. It just turns off the type checker wherever it appears, configuring tsconfig.json with noImplicitAny
//tsconfig.json { "compilerOptions": { "noImplicitAny": true } }
Structural typing
Structural types are a concept familiar to most function programmers.
// @strict: false let o = { x: "hi", extra: 1 }; // ok let o2: { x: string } = o; // ok
Below you can use type naming, interfaces, and classes to define interfaces (however, type aliases behave differently than interfaces with regard to recursive definitions and type parameters)
type One = { p: string }; interface Two { p: string; } class Three { p = "Hello"; } let x: One = { p: "hi" }; let two: Two = x; two = new Three();
Union type (|)
A union type consists of two or more members, and when used, the value must satisfy one of them.
The following are the built-in conditions for determining union types:
- String typeof s === “string”
- Number typeof n ===”number”
- Big integer typeof m === “bigint”
- Boolean value typeof b === “boolean”
- Symbol typeof g === “symbol”
- undefined typeof undefined === “undefined”
- Function typeof f === “function”
- Array Array.isArray(a)
- Object typeof o === “object”
type LockStates = "locked" | "unlocked" let state:LockStates = 'locked' //Pay attention to the use of the following content let s = "right"; pad("hi",10,s) //At this time, s reports an error error:'string' is not assignable to '"left"' | '"right"' //Change usage Correct usage let s:"left" | "right" = "right"; pad("hi",10,s)
Cross type (& amp;)
Cross types can combine multiple types into one type, and the merged type has the properties and methods of all types.
type Combined = { a: number } & amp; { b: string }; type Conflicting = { a: number } & amp; { a: string };
Note: Conflicting a has two types, one is number and the other is string. There is no such type, so it is parsed as never type.
Contextual typing context class
Typescript has some obvious places for type inference.
declare function map<T, U>(f: (t: T) => U, ts: T[]): U[]; let sns = map((n) => n.toString(), [1, 2, 3]);
Here, the same is true for n: number in this example, although T and U have not been inferred before the call. In fact, after using [1,2,3] to infer T=number, the return type of n => n.toString() is used to infer U=string, resulting in sns being of type string[].
declare function run<T>(thunk: (t: T) => void): T; let i: { inference: string } = run((o) => { o.inference = "INSERT STATE HERE"; });
The type of o is determined to be {inference:string} because:
- Declares an initializer to be contextually typed according to the declared type: { inference: string }.
- The return type of the call is inferred using the context type, so the compiler infers T={ inference: string }.
- Arrow functions use context types to type their arguments, so the compiler gives o: { inference: string }.
Type alias
The class name is just an alias and will give the type a new name
- Basic case
type Size = [number,number] let x:Size = [122,125]
- Create new types with & amp;
type FString = string & amp; { __compileTimeOnly: any };
FString is just like a normal string, except that the compiler thinks it has a property called __compileTimeOnly, but it doesn’t actually exist, which means that FString can still be assigned to string, but not vice versa.
The difference between interface and type
Note: Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all functions of interface can be used in type. The main difference is that the type cannot be reopened to add new properties, while interface can always be extended.
- Category names cannot participate in declaration merging, interfaces can
How to expand the two
- Interfaces can only be used to declare the type of objects and cannot rename basic types.
Suggestion: In most cases, you can choose according to personal preference. Generally, interface is used. If it is not satisfied, type can be used.
Discriminant Union
The closest equivalent to data is a type union with discriminant properties
type Shape = | { kind: "circle"; radius: number } | { kind: "square"; x: number } | { kind: "triangle"; x: number; y: number };
A tag or discriminator is just an attribute in each object type, each variant has the same attribute and a different unit type, this is still a normal union type; the leading | is an optional part of the union type syntax. Members of the union can be distinguished through javascript code
type Shape = | { kind: "circle"; radius: number } | { kind: "square"; x: number } | { kind: "triangle"; x: number; y: number }; function area(s: Shape) { if (s.kind === "circle") { return Math.PI * s.radius * s.radius; } else if (s.kind === "square") { return s.x * s.x; } else { return (s.x * s.y) / 2; } }
The return type of the above code area is inferred to be number, which defaults to the complete code. If some results are not involved, the return type can be modified to number|undefined
Type parameters
Function parameters may also need to have declared types. There is no case requirement, but the type is usually a single uppercase letter. Type parameters can also be constrained to a type. Kind of like type constraints.
function firstish<T extends { length: number }>(t1: T, t2: T): T { return t1.length > t2.length ? t1 : t2; }
Because Typescript is structured, it doesn’t require type parameters like nominal systems. Specifically, they are not required to be functionally polymorphic. Type parameters should only be used to propagate type information, such as constraining parameters to be of the same type
//T is not required; because it is only referenced once, it is not used to constrain the return value or other parameter types. function length<T extends ArrayLike<unknown>>(t: T): number {} function length(t: ArrayLike<unknown>): number {}
Module system
Import and export via import and export
//Import import { value, Type } from "npm-package"; import * as prefix from "../lib/third-package"; import f = require("single-function-package"); //Use node.js module through commonjs module
readonly and const
- Application of const
In Javascript, although it allows variable declarations with const, references can still become immutable when declared.
const a = [1, 2, 3]; a.push(102); // ): a[0] = 101; // D:
Use const for assertions on objects or arrays.
//Array assertion let a = [1, 2, 3] as const; a.push(102); // error a[0] = 101; // error //Object assertion (advantage: you can clearly see the attribute content when using it) const test = { name:'124', age:13 } as const
- readonly application
//Use of attributes interface Rx{ readoly x:number } let rx: Rx = { x: 1 }; rx.x = 12; // error //Use of interface interface x: number; } let rx: Readonly<X> = { x: 1 }; rx.x = 12; // error //Usage of array let a: ReadonlyArray<number> = [1, 2, 3]; let b: readonly number[] = [1, 2, 3]; a.push(102); // error b[0] = 101; // error
Non-null assertion operator (suffix!)
Typescript also has a special syntax that allows you to remove null and undefined from a type without doing an explicit check. When expressing, write ! is actually a type assertion that the value is not null or undefined
function liveDangerously(x?: number | null) { //No error console.log(x!.toFixed()); } //When we know that x is not null or undefined, it is very important to use!
Writing standards
- In some cases, typescript can infer the type for us. It is recommended not to fill in the type manually.
- Use noImplicitAny to control any
- Use strictNullChecks to control undefined or null
FAQ
- Type assertion
declare function handleRequest(url: string, method: "GET" | "POST"): void; const req = { url: "https://example.com", method: "GET" }; handleRequest(req.url, req.method); //Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
Solution one:
//Explicit type is GET handleRequest(req.url, req.method as "GET");
Solution 2
//The as const suffix works similarly to the one used in the diagram above, but is used by the type system to ensure that all correspondence is assigned a literal type. instead of a generic version like string or number const req = { url: "https://example.com", method: "GET" } as const; handleRequest(req.url, req.method);