一. 基础数据类型
1. JS中的类型
TS包含了JS的八种数据类型,并且将它们都定义成基础数据类型。有以下几点需要注意:
null和undefined是任何类型的子集;所以在任何类型都可以被赋值为null或undefined。- 配置中设置
"strictNullChecks": true。null只能赋值给自身,undefined只能赋值给void和自身 number和bigint类型不兼容,不能直接互相赋值 ```typescript let str: string = ‘a’; let num: number = 2; let bool: boolean = false; let nul: null = null; let undef: undefined = undefined; let bigInt: bigint = 3n; let sym: symbol = Symbol(‘that’); let obj: object = {x: 1};
str = nul; // 只有”strictNullChecks”: false情况下可以 function v(): void{ return undefined; } // “strictNullChecks”: true情况下undefined只能赋值给undefined和void num = bigint // 不可以,两种数据类型不兼容
<a name="tGHHc"></a>### 二. 其他类型<a name="wfkG9"></a>#### 1. 数组Array<a name="O7EaG"></a>##### 1) 定义数组类型的两种方式:```typescriptlet arr1: number[] = [1, 2, 3];let arr2: Array<Number> = [4, 5, 6];
2) 定义联合类型的数组
联合类型的数组可以同时存储多种类型的数据
let uarr: (number | string)[] = [1, 'a', 2, 'b', 'c'];
3) 定义某种对象类型的数组
interface ArrType {isMe: boolean;name: string;}let tarr: ArrType[] = [{isMe: true,name: 'jay'},{isMe: false,name: 'chou'}];
2. 函数
1) 函数声明
function funcDec (x: string, y: boolean): void {return undefined;}
2) 函数表达式
let funcExpr: (x: string, y: boolean) => void = function (x: string, y: boolean): void {return undefined;}
3) 接口定义的函数类型
interface IFunc {(x: string, y: boolean): void;}let FuncObj: IFunc = function(x: string, y: boolean): void {return undefined;}
4) 可选参数
可选参数后面不能有必选参数
function optionalFunc(x: string, y: boolean, z?: number): void {console.log(z)}optionalFunc("df", false); // z输出undefined
5) 默认参数
可以通过传入undefined方式告诉函数这个参数不传值
function defaultFunc(x: string = 'fg', y: boolean):void {console.log(x)}defaultFunc(undefined, false);
6) 剩余参数
function restFunc(x: string, y: boolean, ...items: string[]): void {items.forEach((item) => {console.log(item)});}restFunc('dg', true, '43', 'hs');
7) 函数重载
函数重载是一种调用相同函数名,不同参数数量,类型和返回值类型的能力。调用的函数会依次匹配声明的函数
type combinable = string | number;function reloadFunc(x: number, y: number): number;function reloadFunc(x: number, y: string): string;function reloadFunc(x: string, y: number): string;function reloadFunc(x: string, y: string): string;function reloadFunc(x: combinable, y: combinable): combinable {if (typeof x === 'string' || typeof y === 'string') {return x.toString() + y.toString();}return x + y;}
3. 元组 Tuple
1) 声明与使用
使用下标访问
const tup: [number, boolean, string] = [1, false, 'gs'];console.log(tup[1]) // false
2) 解构赋值
解构赋值的长度不能超过元组的长度,否则会报错
const tup: [number, boolean, string] = [1, false, 'gs'];const [id, isMe] = tup; // 1 false
3) 可选类型
添加?的形式添加可选参数。可选类型后面不能有必选类型
const tup: [number?, boolean?, string?] = [];
4) 剩余参数
剩余参数在可选类型后面
const tup: [number?, boolean?, ...number[]] = [1, false, 2, 3];
5) 只读元组
只读元组内部的值不能再改变
const tup: readonly [number?, boolean?, ...number[]] = [1, false, 2, 3];tup[6] = 4; // 报错,因为是readonly,不能将undefined改变为4
4. void类型
void类型和其他类型平级,一般表示没有返回值的函数的返回类型。不能直接赋值为undefeind以外的值。
在strictNullChecks: false时可以赋值为null和undefined
let v1: void;v1 = undefined;function v(): void {};
5. never类型
never类型代表永远不存在的值的类型;比如:
- 一个抛出错误的函数,它会中断函数;所以不存在返回值 ```typescript function throwFunc(): never { throw new Error(‘throw an error’); }
let bar: never = throwFunc();
2. 一个无限循环的函数```typescriptfunction loopFunc(): never {while(true);}let foo: never = loopFunc();
never类型和undefined,null一样;是其他类型的子类型。但是除了never类型的值,其他任何类型的值都不能赋值给never,包括any
let a: any = 234;let bar: never = a; // 报错,any类型不能赋值给never
应用场景:可以进行编译时全面的类型检查,排除联合类型中未实现的部分
type combinable = string | boolean | number;function checkType(x: combinable): boolean {if (typeof x === 'string') {return true;} else if (typeof x === 'boolean') {return true;} else {const check: never = x; // number逻辑未实现,编译器报错}return false;}
6. any类型
any类型可以被赋值为任何类型,any类型可以赋值给其他任何类型(never除外);可以访问任意属性。
声明未指定类型,未赋值的变量默认为any类型
let defaultAn; // any类型let an: any = {};an.getType = '43';an = 325let str:string = an; // OKlet undef: undefined = an; //OKlet nul: null = an; // OKlet v: void = an; // OKlet nev: never = an; // Error never不可以被赋值为anyfunction nevFunc():never {throw new Error('')}let an: any;an = nevFunc(); // OK any可以被赋值为never
7. unknown类型
unknown类型和any一样都可以被任意类型赋值,但是unknown类型只能赋值给unknown和any。除非进行了类型断言和类型判断;否则不能赋值给其他类型
function nevFunc():never {throw new Error('')}let unk: unknown = '543';let unk2: unknown = unk;unk = false;unk = nevFunc();let an: any = unk;let str: string = unk; // Error
如果不缩小类型,就不能对unknown类型做任何操作
function getDog() {return true;}let d: unknown = {getDog};d.getDog(); // Error(d as any).getDog(); // OK
8. Number String Boolean Symbol等包装对象类型
原始类型可以赋值给包装类型,包装类型不可以赋值给原始类型
let num: number = 1;let Num: Number = 2;Num = num; // OKnum = Num // Error
9. object,Object和{}
1) object
object代表所有的非原始类型,不包含js中的基本数据类型string, number, boolean, symbol, bigint, null, undefined
let obj: object = {a: 12}; // OKobj = new Date(); //OKobj = 'dsf'; // Errorobj = 234; // Errorobj = false; // Errorobj = Symbol('324'); // Errorobj = 43543n; // Errorobj = null; // Errorobj = undefined; // Error
2) Object
Object代表所有有toString(), hasOwnProperty()等方法的数据类型(编译器显示支持all objects),所以所有的原始类型,非原始类型都可以赋值给它(null和undefined需要开启strictNullCheck)
let Obj: Object;Obj = {}; // OKObj = 1; // OKObj = 234n; // OKObj = 'gd'; // OKObj = false; // OKObj = Symbol('sdf'); // OKObj = null; // ErrorObj = undefined; // Error
Object既是object的父类型,也是object的子类型。他们之间可以互相赋值
type is_obj_extends_Obj = object extends Object ? true : false; // truetype is_Obj_extends_obj = Object extends object ? true: false // truelet a: is_obj_extends_Obj = true;let b: is_Obj_extends_obj = true;
3) {}
空对象的行为和Object一致,表示原始类型和非原始类型的集合(null,undefined受strictNullCheck影响)
let Obj: {};function nevFunc(): never {throw new Error();}Obj = nevFunc()Obj = 1;
三. 类型推断
- 声明时赋值可以自动进行类型推断
- 函数返回值根据return判断
- 声明时未赋值,未添加类型的会被推断为
any```typescript let str: string = ‘gsh’; // string
function add(a: number, b = 1) { return a + b; } // function add(a: number, b?: number): number
let an; // any类型 an = 324;
<a name="MYiDB"></a>### 四. 类型断言类型断言就是告诉编译器按照我的意思执行类型检查。只在编译阶段生效,并不作类型转换<a name="KIP50"></a>##### 1) 类型断言语法类型断言有`as number`和`<number>`两种语法,其中`<number>`语法不与jsx兼容```typescriptconst arrNum: number[] = [1, 3 ,4];const lagestNum1: number = (arrNum.find((num) => num > 3)) as number;const lagestNum: number = <number>(arrNum.find((num) => num > 3));
2) 非空断言
非空断言就是断言一个变量类型不是null和undefined,用后置的!表示
let combStr: null | undefined | string;combStr!.toString(); // 断言combStr排除null和undefinedcombStr.toString(); // Error 可能为null和undefinedtype NumGen = () => number;function assetFunc(numGen: NumGen | undefined) {numGen!(); // 断言排除undefinednumGen(); // Error 可能为undefined}
3) 确定赋值断言
明确告诉编译器变量会被赋值,类型检查的时候不会报未赋值的错误
let x!: number;initFunc();console.log(x * 2);initFunc() {x = 10;}
五. 字面量类型
字面量可以作为变量的类型;字面量类型只有字符串字面量,数字字面量,布尔字面量类型三种。
let spe: 'this' = 'this';let str: string = spe;spe = str; // Error 字符串字面量类型'this'是string的子类型
字面量类型可以限制变量的类型和取值
interface Config {dir: 'up' | 'down';isWalk: false | true;margin: 0 | 2 | 4;}
let和const的类型推断其实也是一种类型拓展;
str2是字面量类型,它的父类型是string,所以typeof str2 === 'string'
str是字面量类型,类型是'b',它的父类型是string,所以被允许赋值给其他字符串字面量。推断出的类型就是字面量的父类型
let str = 'a';const str2 = 'b';
六. 类型拓宽(Type Widening)
1. 普通的类型拓宽
类型拓宽指类型推断的时候编译器将变量的类型进行拓宽,推断为合理的类型
let str = 'this is a string' // 类型拓宽,string类型let strFunc = (str = 'this is a string') => str // 类型拓宽 (str?: string) => stringconst specStr = 'this is a string'; // 没有拓宽 'this is a string'类型let str2 = specStr; // 类型拓宽 string类型let strFunc2 = (str = specStr) => str; // 类型拓宽 (str?: string) => string
2. 特殊的类型拓展
null和undefined会自动拓宽为any,变量有确定值的时候类型会收窄为特定的类型
let nul = null // any类型let undef = undefined; // any类型let nul2 = nul; // 收窄为null类型
interface Vector2 {x: number;y: number;z: number;}function getComponent(vec: Vector2, axios: 'x' | 'y' | 'z') {return vec[axios];}let vec = {x: 10, y: 20, z: 30 };let x = 'x';getComponent(vec, x); // x类型string,不能赋值给'x' | 'y' | 'z'类型
3. 限制类型拓展
1) 通过字面量类型限制类型拓展
let specStr2: 'this is a string' = 'this is a string';let str3 = specStr2; // 限制类型拓展 类型为'this is a string'
2) const数据类型
对于原始类型,const可以限制为字面量类型
对于对象和数组,对象内部的类型会被认为是let声明的变量
const x = 'x' // 限制拓展'x'const obj = {x: 1,y: 2}; // 内部的属性会拓展 {x: number, y: number}obj.x = 3; // OK
3) const 断言
const断言从值空间引入,将它收窄为最小的类型推断
let obj2 = {x: 1,y: 2 as const} // { x: number, y: 2 }let obj3 = {x: 1,y: 2} as const; // { readonly x: 1, readonly y: 2 }let arr = [1, 4] as const; // readonly[1, 4]
七. 类型收窄(Type Narrowing)
将宽泛的类型判断为一个比较明确的类型称之为类型缩小;
TS中用类型守卫来收窄类型的判断,编译器通过分析代码流判断类型。类型守卫是通过代码影响代码分析流的功能
function narrowFunc(varType: number | string): boolean {if (typeof varType === 'number') {varType; // numberreturn true;}varType; // stringreturn false;}
一种常用的做法是通过标签联合或可辨识联合帮助接口类型收窄
interface TypeA {type: 'A',name: string}interface TypeB {type: 'B',age: number}type comb = TypeA | TypeB;function combJudge(x: comb): string {switch(x.type) {case 'A':return 'a';case 'B':return 'b';default:return 'ha'}}
注意:
不能通过
object排除null类型const el = document.getElementById('me');if (typeof el === 'object') {el; // null | HTMLElement}
通过falsy值很多类型无法收窄
function falsyJudge(x: number | string | null | true) {if (!x) {x; // number | string | null 只排除了true}}
八. 联合类型
使用
|创建联合类型,联合类型里可以是普通的类型或字面量类型;
如果联合类型中普通类型包裹了字面量类型,编译器只会提示普通类型let str: string | 'hi' = 'jay'; // string 这里是特殊的let comb: number | 'h1' = 'h1'; // number | 'h1'let huge: Object | string = true; // Object | stringlet bigNum: 12n | 13 = 12n; // 12n | 13
九. 类型别名
给一个类型,联合类型取别名
type otherObj = Object;type baseNum = 1 | 2 | 4; // 字面量类型也可以取类型别名let str: otherObj = 'jskdh';
十. 交叉类型
交叉类型用
&连接两个类型,原子类型取交集,非原子类型取并集
对于多个接口类型合并,不重名的原子属性取并集,重名的元素属性取交集,对象属性内部规则同理 ```typescript type obj = Object & object; // Object & object 收窄为非原始数据类型 let o: obj = {} // OK let o1: obj = 1; // Error 不符合object type str = ‘hi’ & string; // 收窄为’hi’ type useless = string & number; // never 原子类型没有交集
function nevFunc(): never {throw new Error()} interface ICat { id: string; age: number; feature: { miao: string } } interface IDog { id: number; age: number; isDog: boolean; feature: { wang: string } } type IPet = ICat & IDog; let pet: IPet = { id: nevFunc(), // 重名的原子类型取交集 age: 5, // 不重名的原子类型取并集 isDog: false, feature: { // 对象属性内部和外部同理,不重名合并,重名相交 miao: ‘miao’, wang: ‘wang’ } }
<a name="S1FC3"></a>### 十一. 接口TS中的接口可以是**对类部分行为的抽象**,也可以**描述对象的形状**。<a name="p3TWB"></a>#### 1. 接口描述对象形状,要严格一致,属性不能多也不能少```typescriptinterface ICat {name: string;age: number;}let c: ICat = { // Error 少了age属性name: 'ksduhf'};let d: ICat = { // Error 多了isDog属性name: 'dsg',age: 43,isDog: true;};
2. 可选参数,只读参数
使用readonly表明只读参数,可选只读参数也只能在初始化时赋值
interface ICat {readonly isDog: boolean;readonly isCat?: boolean;name: string;age?: number;}let cat: ICat = {isDog: true,name: 'cat'};cat.age = 32;cat.name = 'rwe';cat.isCat = true; // Errorcat.isDog = false; // Error
还有一个ReadonlyArray,是一个只读的数组类型,不能修改数组内容
let rarr: ReadonlyArray<number> = [1, 3, 4];rarr.push(4); // 不能修改
3. 任意参数
使用索引签名为接口添加任意参数;任意参数只能有一个;任意参数类型必须包含其他参数的类型(可选参数多一个undefined类型)
interface IAnimal {isDog: boolean;name: string;age?: number; // 类型 number|undefined[k: string]: number | string | boolean | null | undefined;}
4. 类型兼容性
TS类型的兼容性是基于结构子类型的(也叫鸭式辨型法),结构类型是一种只使用其成员来描述类型的方法,结构类型的兼容性或等价性不要求显示的声明类型,它是基于类型的组成结构。和名义类型形成对比。
- 普接口,通对象的兼容性
如果x兼容y,则y需要把包含x的所有属性
interface IX {name: string;}let x: IX;let y = {name: 'hi',age: 23};x = y; // OK y有x所有成员y = x; // Error x没有y所有成员
- 函数兼容性
参数类型:如果x兼容y,则x需要包含y所有参数类型且能对齐
let x = (x: string, y: number) => 0;let y = (a: string) => 0;x = y; // OK y的参数类型都能在x中找到y = x; // Error x的参数类型number在y中找不到
返回值类型:如果x兼容y,则y需要包含x所有参数
let x = () => ({age: 13});let y = () => ({name: 'jay', age: 12});x = y; // OK y包含了x的所有参数y = x; // Error x没有包含y的所有参数
- 类的兼容性
类型兼容只比较实例属性,不比较静态属性和构造函数X要兼容Y,则Y需要有X所有实例属性
class X {jump: boolean = false;constructor(name: string, jump: boolean){}}class Y {jump: boolean = true;constructor(){}run() {}}let x = new X('jay', false);let y = new Y();x = y; // OK y有x所有实例属性y = x; // Error x没有y的run属性
5. 绕开额外属性检查的办法
1) 鸭式辨型法
长得像鸭子并嘎嘎叫的就是鸭子,这就是鸭子辨型法。下面的例子如果直接将{name: 'A', age: 23}传入字面量对象会被类型检查限制。但是如果作为普通对象传入就会因为类型兼容不报错。
interface duck {name: string;}function getDuck(d: duck): duck { return d }let duckA: {name: string, age: number} = {name: 'A',age: 23}getDuck(duckA); // OKgetDuck({name: 'A', age: 23}) // Error
2) 类型断言 as
使用as告诉编译器自己知道在做什么,只要对象包含接口的属性就可以将对象断言为接口的实现
interface duck {name: string;}function getDuck(d: duck): duck { return d }getDuck({name: 'A', age: 23} as duck); // OK
3) 在接口定义的时候就是用索引签名
interface duck {name: string;[key: string]: any}function getDuck(d: duck): duck { return d }getDuck({name: 'A', age: 23}); // OK
十二. 接口和类型别名的区别
二者都可用来声明对象和方法。
区别1:人如其名,类型别名可用于基本类型,联合类型,元组等取别名
type str = string;type son = string | number;type point = [number, number];
区别 2:interface,type可以拓展且可以相互拓展,interface使用extends,type使用&
interface IPoint {x: number;y: number;}interface I3DPoint extends IPoint { z: number };type TPoint = {x: number,y: number}type T3DPoint = TPoint & { z: number };
区别3:interface被重复声明会合并不报错,type重复声明会报错
interface IPoint {x: number;y: number;}interface IPoint {isPoint: boolean;}// 变成{x: number, y: number, isPoint: boolean}let p: IPoint = {x: 1,y: 2,isPoint: true}; // OK
十三. 泛型
泛型就是在定义函数,接口,类的时候不预先定义类型,而是在使用的时候再指定类型。
1. 泛型约束
如果需要泛型支持某个属性,就需要让这个泛型拓展某个接口<T extends someinterface>
interface IPoint {x: number;y: number;}function getDistance<T extends IPoint>(p: T): number { // 让p具有了x, y属性return p.x + p.y;}
2. 泛型工具类型
1) typeof
根据实例获取泛型类型,返回一个type。这个类型可以是推断出来的
interface IPoint {x: number;y: number;}let p:IPoint = {x: 1, y: 2};type I = typeof p; // IPointlet i: I = {x: 2, y: 3};console.log(i)// 支持推断出的类型const cat = {name: 'Kitty',age: 3,feature: {isBig: false}};type c = typeof cat;/* 推断出的类型{name: string,age: number,feature: {isBig: boolean}}*/const cat2: c = {name: 'Jay',age: 1,feature: {isBig: true}};
2) keyof
keyof返回一个类型,获取类型的所有键。只能用于类型
interface cat {name: string;age: number;feature: {isBig: boolean}}type k = keyof cat;const k1: k = 'feature' // OK 类型是"name" | "age" | "feature"
TypeScript支持字符串索引和数字索引,其中数字索引是字符串索引的子集。所以keyof字符串索引得到的类型是string|number
interface cat {[key: string]: string}const kit: cat = {23: "hello"}type c = keyof cat; // string | numberinterface cat {[key: number]: string}const kit: cat = {23: "hello"}type c = keyof cat; // number
应用场景:获取某个泛型对象的key
function getProps<T, K>(obj: T, key: K) {return obj[key]; // Error}function getProps<T, K extends keyof T>(obj: T, key: K): T[K] {return obj[key];}let res = getProps({a: 1, b: 'hi'}, 'b');
