思考一个问题,JavaScript是一门非常优秀的编程语言,但是直到今天,JavaScript在类型检测上依然是毫无进展,所以我们需要学习TypeScript,这不仅仅可以为我们的代码增加类型约束,而且可以培养我们前端程序员具备类型思维
GitHub说法:TypeScript is a superset of JavaScript that compiles to clean
TypeScript官网:TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
翻译一下:TypeScript是拥有类型的JavaScript超集,它可以编译成普通、干净、完整的JavaScript代码。
安装TypeScript
# 安装命令
npm install typescript -g# 查看版本
tsc --version
// 安装ts-node及其依赖包
npm install ts-node tslib @types/node -g
// 代码栗子,创建 .ts 文件// 规定message的类型为string
let message: string = 'hello'// 报错!
// message = 123// 规定参数类型为string
function abc(name: string){}console.log(message);// 因为ts默认作用域会在一个,这样设置导出,会让文件形成单独作用域
export {}
// ts-node可以直接运行TypeScript代码
ts-node 文件名
具体可看之前的webpack文章,webpack 之 零基础使用常用的Loader ,这里我快速过一遍哈
npm init -y
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin ts-loader typescript -D
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');ports = {mode: 'development',entry: './src/main.ts',output: {path: solve(__dirname, 'dist'),filename: 'bundle.js'},devServer: {},resolve: {extensions: ['.ts', '.js', '.cjs', '.json']},module: {rules: [{test: /.ts$/,use: 'ts-loader'}]},plugins: [new HtmlWebpackPlugin({template: './index.html'})]
};
// 先不用理这个文件,后续讲解
tsc --init
创建src文件夹,里面再创建main.ts
const message: string = '123'console.log(message);
声明了类型后TypeScript就会进行类型检测,声明的类型可以称之为类型注解
var/let/const 标识符: 数据类型 = 赋值;
数据类型的大小写是有区别的 ,用小写即可
类型推导 : 声明一个标识符时,如果有进行直接赋值,会根据赋值的类型推导出标识符的类型
ps :
let => 进行类型推导,推导出来通用类型
const => 进行类型推导,推导出来的是字面量类型
TypeScript和JavaScript一样,不区分整数类型(int)和浮点型(double),统一为
number类型
let demo: number = 22; // 十进制
let demo: number = 0b110; // 二进制
let demo: number = 0o555; // 八进制
let demo: number = 0xf23; // 十六进制
boolean类型只有两个取值:true和false
let bool: boolean = true
let bool: boolean = 30 > 20
string类型是字符串类型,可以使用单引号或者双引号表示,同时也支持ES6的模板字符串来拼接变量和字符串
const name: string = 'star'
const info: string = `my name is ${name}`
需要制定数组类型,同时制定数组内部值的类型
// 写法一 : 不推荐
const name: Array<string> = [] // 写法二 : 推荐
const name: string[] = []let name1: string[] = ['张三', '李四', '王五'];
name1.push(1); // 报错, 只能添加字符串类型的数据// 数组的类型最好是一致的,不一致的话,可以使用联合类型
let name2: (string | number)[] = ['张三', '李四', '王五'];
name2.push(1);
name2.push('赵六');
// 1. 使用自动推导
const info = {name: 'star',age: 18
};// 2. 手动写入
let info: {name: string;age: 20; // 这里的20是字面量类型,表示age只能是20
} = {name: 'John',age: 20 // 必须是20,否则报错
};
const n: null = null
const n: undefined = undefined
// Symbol 生成独一无二的key
const info = {[Symbol('name')]: 'star',[Symbol('name')]: 'coder'
};
无法确定一个变量的类型,并且可能它会发生一些变化,这个时候可以使用any类型
any类型有点像一种讨巧的TypeScript手段:
什么时候使用any
// 任意类型都可赋值,也可赋值给任何类型
let message: any = 'star';message = 123;
message = true;// 任意类型的值,可以访问任意属性和方法
message.length;
Fixed();
unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量
和any类型有点类似,但是unknown类型的值上做任何事情都是不合法的
ps : unknown进行任意操作前,必须进行类型校验(缩小),才能进行对应的操作
function foo() {return 'string';
}
function bar() {return 123;
}
let flag: boolean = true;// result 不知是什么类型时可用
let result: unknown;
if (flag) {result = foo();
} else {result = bar();
}/*** unknown 类型的值不能直接使用* result.length; // error* 需要先判断类型,再使用 => 类型缩小* if (typeof result === 'string') {* result.length;* }*/
if (typeof result === 'string') {result.length;
}
let result: unknown;
// 报错
let num: number = result// 可以赋值
let unres: unknown = result
let anyres: any = result
当函数没有返回值的时候,该函数就是void类型,不写也阔以,会自动推导
// 如果返回值时void类型,也可以返回undefined
const sum = (num1: number, num2: number): void => {console.log(num1, num2);return undefined // 不会报错
};
开发中很少实际去定义never类型 => 某些情况下会自动类型推导出never
永远没有返回值的时候,用never
// 其他时候在扩展工具的时候,对于一些没有处理的case,可以直接报错
function handleMessage(message: string | number | boolean) {switch (typeof message) {case 'string':console.log('string', message.length);break;case 'number':console.log('number', Fixed(2));break;default:// 永远不会执行,因为上面的 case 已经覆盖了所有可能的值,赋值给never类型const check: never = message; // error,Type 'boolean' is not assignable to type 'never'}
}handleMessage('hello world');
handleMessage(100.123);/*** 如果仅仅更改了参数类型,而没有更改 switch 的 case,TypeScript 会提示错误*/
handleMessage(true);
tuple和数组的区别
// tuple 多种元素的组合// 1. 数组
const info1: any[] = ['star', 1, 2, 'coder'];
info1[0].slice(1); // 是any类型 => 虽然可以使用slice方法,但是不安全// 2.1 元组
const info2: [string, number, number, string] = ['star', 1, 2, 'coder'];
info2[0].slice(1); // 可以直接知道是string类型 => 安全// 2.2 元组类型的抽取
type infoType = [string, number, number, string];
const info3: infoType = ['star', 1, 2, 'coder'];// 2.3 元组类型的使用,一般用于函数返回值
function useState(initState: number): [number, (newStateValue: number) => void] {let stateValue: number = initState;function setStateValue(newStateValue: number) {stateValue = newStateValue;}return [stateValue, setStateValue];
}// 可以明确知道返回值的类型,state是number类型,setState是一个函数
const [state, setState] = useState(0);
// 可指定函数的参数类型和个数和返回值的类型 name: string => 指参数类型为string string => 指函数的返回值类型为string,可不写,会推导
function getInfo(name: string): string {return name;
}// 匿名函数不需要写类型,TypeScript会根据forEach函数的类型以及数组的类型 推断出 item的类型
// 因为函数执行的上下文可以帮助确定参数和返回值的类型
const names = ['a', 'b', 'c'];
names.forEach((item) => {});
// info是一个对象类型,对象中有两个属性function find(info: { name: string; age: number }) {}
对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?
// money是可选类型,可传可不传
function find(info: { name: string; age: number; money?: number }) {}find({ name: 'coder', age: 123 });
find({ name: 'coder', age: 123, money: 1000 });
TypeScript的类型系统允许我们使用多种运算符,从现有类型中构建新类型
联合类型(Union Type)
// 联合类型 可以为其中的一种// 1. 联合类型基本使用
let info: number | string = 1;
info = '1';
// 使用的时候需要判断类型
if (typeof info === 'string') {console.log(info.length);
}// 2. 联合类型的函数参数使用
function find(id: string | number) {// 使用联合类型的值的时候,需要特别小心switch (typeof id) {case 'string':break;case 'number':break;}
}
find(123);
find('456');
可选类型和联合类型的关系
一个参数是一个可选类型的时候,它其实类似于这个参数是 类型|undefined的联合类型
function foo(message?: string) {}
// 这里可以不传
foo();function foo(message: string | undefined) {}
// 但是这里还是要传一个值
foo(undefined);
交叉类似表示需要满足多个类型的条件,交叉类型使用 & 符号
interface Ieat {eating: () => void;
}
interface Idrink {drink: () => void;
}// 满足其中一个即可
type myType1 = Ieat | Idrink;
// 满足两个才可以 联合类型
type myType2 = Ieat & Idrink;const obj1: myType1 = {eating: () => {}
};const obj2: myType2 = {eating: () => {},drink: () => {}
};
// type 用于定义类型别名
type IdType = string | number | boolean;// 太长了
function idSet(id: string | number | boolean) {}
// 取别名可以优化
function idSet(id: IdType) {}
有时候TypeScript无法获取具体的类型信息,这个时候需要使用类型断言
// 默认是 HTMLElement 类型,范围太广了,有时会出错
// const oDom: HTMLElement = ElementById('star');// <img id="star" />
// 用 as 指定是什么元素类型
const oDom: HTMLImageElement = ElementById('star') as HTMLImageElement;
oDom.src = 'url地址';
TypeScript只允许类型断言转换为 更具体 或者 不太具体( any/unknow ) 的类型版本,此规则可防止不可能的强制转换
/*** 可以跳过类型检测,无敌,不推荐使用** message先转成 any,再转成 number* (message as any) as number** 转换后无法正常使用*/
const message = 'hello';
// 可以跳过类型检测,无敌,不推荐使用
const num: number = message as any as number;console.Fixed(2)); // 运行时报错
如果确定传入的参数是有值的,这个时候可以使用非空类型断言
非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测
// 这样是没问题的
function printStrLength(message: string) {console.log(message.length);
}
printStrLength('aaa');
printStrLength('11');// 但是如果参数变成了可选类型呢,编译就会报错
function printStrLengthCan(message?: string) {console.log(message.length);
}// 解决方式一 : 类型缩小
function printStrLengthOne(message?: string) {// 做个if判断即可if (message) {console.log(message.length);}
}// 解决方式二 : 非空断言(有点危险,确保有值时才使用)
function printStrLengthTwo(message?: string) {// 加个非空断言 , 保证message一定有值console.log(message!.length);
}
可选链事实上并不是TypeScript独有的特性,它是ES11(ES2020)中增加的特性
// 定义一个类型
type Person = {name: string;// 可能有,可能没有friend?: {name: string;// 可能有,可能没有age?: number;};
};const info: Person = {name: 'star'
};
const info2: Person = {name: 'star',friend: {name: 'coder'}
};// 如果有friend,继续往后取,如果没有,返回undefined,相当于短路
console.log(info.friend);
console.log(info.friend?.name);
console.log(info.friend?.age);
// 类似于,可以节省很多代码
if (info.friend) {if (info.friend.name) {console.log(info.friend.name);}if (info.friend.age) {console.log(info.friend.age);}
}export {};
const message: string = 'hello'
const flag: boolean = Boolean(message)
// !! 可以用作取反
const flag1: boolean = !!message
// ?? 有值的时候取前面的值,没值的时候取后面的值
// 和三目运算符很像,不过更简洁一点const message: string | null = null;
const res: string = message ?? 'hello';
console.log(res); // helloconst message1: string | null = 'coder';
const res1: string = message ?? 'hello';
console.log(res1); // coder
字面量类型的类型和值要保持一致
const message = 'hello'; // 这个message的类型就是hello
let mess = 'hello' //这个mess的类型才是stringlet me : 123 = 123
me = 456 // 报错
字面量类型的意义 : 必须结合联合类型
let align: 'left' | 'right' | 'center' = 'left'
// 再次修改值的时候,只能允许修改定义了类型的值
align = 'right'
const info = {url: 'www.baidu',method: 'GET'
};function request(url: string, method: 'GET' | 'POST') {console.log(url, method);
}// 这里hod会报错,因为默认推导method是字符串类型,所以不能这么传
request(info.urlhod)
// 定义一个类型
type requestType = {url: string;method: 'GET' | 'POST';
};// 定义对象的时候就使用这个类型
const info: requestType = {url: 'www.baidu',method: 'GET'
};function request(url: string, method: 'GET' | 'POST') {console.log(url, method);
}request(info.url, hod);
const info = {url: 'www.baidu',method: 'GET'
};function request(url: string, method: 'GET' | 'POST') {console.log(url, method);
}// 使用类型断言
request(info.url, hod as 'GET');
function request(url: string, method: 'GET' | 'POST') {console.log(url, method);
}// 这里使用类型断言成const,其内部的属性值都变成了readeronly
const info = {url: 'www.baidu',method: 'GET'
} as const;request(info.url, hod);export {};
对于对象的字面量赋值,在TypeScript中有一个非常有意思的现象
interface IPerson {name: string;age: number;studying(this: IPerson): void;
}// 1. 直接赋值
// const person1: IPerson = {
// name: 'coder',
// age: 18,
// studying() {
// console.log('studying');
// }
// // 新增属性会报错
// message:'hello'
// };// 2. 间接赋值
const obj = {name: 'star',age: 18,studying() {console.log('studying');},message:'hello'
}
// 这样可以赋值成功
const person2: IPerson = obj;
console.log(person2); // { name: 'star', age: 18, studying: [Function: studying] }
interface IPerson {name: string;age: number;studying(this: IPerson): void;
}function foo(person: IPerson) {}// 1. 直接赋值 => 新增属性会报错
// foo({ name: 'coder', age: 18, studying() {}, message: 'hello' });// 2. 间接赋值 => 新增属性不会报错
const person = { name: 'coder', age: 18, studying() {}, message: 'hello' };
foo(person);
interface IPerson {name: string;age: number;studying(this: IPerson): void;
}function foo(person: IPerson) {}/*** 解释现象:* 第一次创建的对象字面量,称之为fresh object (新鲜对象)* 对于新鲜的字面量,会进行严格的类型检测,必须完全满足接口的要求(不能多也不能少)* 1. 所以直接赋值会报错,会被严格检测* 2. 间接赋值,已经不是新鲜的字面量了,不会被严格检测*/// 1. 直接赋值 => 新增属性会报错
// foo({ name: 'coder', age: 18, studying() {}, message: 'hello' });// 2. 间接赋值 => 新增属性不会报错
const person = { name: 'coder', age: 18, studying() {}, message: 'hello' };
foo(person);
type idType = string | number | boolean;function getId(id: idType): idType {if (typeof id === 'string') {return id;}if (typeof id === 'number') {return id * 2;}if (typeof id === 'boolean') {return id ? true : false;}
}
// 使用 === || == || !== || != || switch
type directionType = 'left' | 'right' | 'top' | 'bottom';function getDirect(direct: directionType) {if (direct === 'left') {return 'x';}if (direct === 'right') {return 'y';}if (direct === 'top') {return 'z';}if (direct === 'bottom') {return 'w';}// 或者这样亦可switch (direct) {case 'left':return 'x';case 'right':return 'y';case 'top':return 'z';case 'bottom':return 'w';}
}
function foo(str: string | Date) {if (str instanceof Date) {Time();} else {str.length;}
}class Student {studying() {}
}class Teacher {teaching() {}
}
function boo(obj: Student | Teacher) {if (obj instanceof Student) {obj.studying();} else {aching();}
}
type Fish = {swimming: () => void;
};type Bird = {running: () => void;
};function work(animal: Fish | Bird): void {// 和对象中判断是否有某个属性一样if ('swimming' in animal) {animal.swimming();}if ('running' in animal) {animal.running();}
}const fish: Fish = {swimming: () => {console.log('swimming');}
};const bird: Bird = {running: () => {console.log('running');}
};
枚举类型是为数不多的TypeScript特性有的特性之一:
// 枚举这些数据
enum Direction {Up, // 默认是0 可以更改默认值 up = 1,之后的值会自动加1 或者up = 'left'Down, // 默认是1Left, // 默认是2Right // 默认是3
}function trunDirection(dir: Direction): void {switch (dir) {case Direction.Up:console.log('向上');break;case Direction.Right:console.log('向右');break;case Direction.Down:console.log('向下');break;case Direction.Left:console.log('向左');break;default:// 正常来说走不到这里,这样写就是为了防止枚举的数据越界,会报错const never: never = dir;}
}
trunDirection(Direction.Up);
trunDirection(Direction.Right);
trunDirection(Direction.Down);
trunDirection(Direction.Left);
/*** 函数类型表达式* 格式:(参数: 类型, 参数: 类型, ...) => 返回值类型* 定义的时候* 1. 形参的名字可以不和定义的参数名一致* 2. 对于参数的个数不进行检测,可以少传,但是不能多传* 3. 如果函数没有返回值,返回值类型可以是 void 或者省略* 使用的时候* 1. 参数的名字可以不和定义的参数名一致、* 2. 参数个数一定要和定义的一致,不能少传,也不能多传*/type IncrementType = (x: number, y: number) => number;
// 1. 没有参数也可以
const increment: IncrementType = () => 0;
// 2. 参数的个数不进行检测,
const increment2: IncrementType = (x) => x;
const increment3: IncrementType = (x: number, y: number) => x + y;
// 3. 参数的个数可以少传,但是不能多传
// const increment4: IncrementType = (x: number, y: number, z: number) => x + y + z; // 报错,不能多传// 使用的时候!必须传递两个参数,不能少传
console.log(increment(1, 2)); // 0
console.log(increment2(1, 2)); // 1
console.log(increment3(1, 2)); // 3
function foo() {console.log('foo');
}
// 定义fn为函数类型 如果返回void,可以指不返回类型,可以指返回任意类型
type FnType = (num1: number, num2: number) => void;
function bar(fn: FnType) {// 1. 调用fn,必须传入两个参数fn(1, 2);
}// 2. 调用bar,必须传入一个函数,且函数必须返回void,不一定要传入两个参数
bar(foo);
从对象的角度看待函数,函数除了可以被调用,自己也是可以有属性值的
函数类型表达式并不能支持声明属性
想描述一个带有属性的函数,可以在一个对象类型中写一个调用签名(call signature)
注意这个语法跟函数类型表达式稍有不同,在参数列表和返回的类型之间用的是 : 而不是 =>
interface IBar {age: number;address: string;// 告知这是一个函数,可以被调用 => 函数签名// 格式 (参数列表): 返回值类型 ,这里是用冒号表示,而不是箭头(num1: number, num2: number): void;
}const bar: IBar = (num: number) => {console.log(num);
};
bar.age = 18;
bar.address = '北京市';
// 使用的时候,必须传入两个参数
bar(1, 2); // 1console.log(bar.age); // 18
console.log(bar.address); // 北京市
JavaScript 函数也可以使用 new 操作符调用,当被调用的时候,TypeScript 会认为这是一个构造函数(constructors),因为他们会产生一个新对象
可以写一个构造签名( Construct Signatures ),方法是在调用签名前面加一个 new 关键词
// 1. 定义一个类
class Person {}// 2. 构造签名
interface IConstructor {// 声明这个接口可以被new调用,返回值是Person类的实例new (): Person;
}/*** 3. 定义一个函数,批量创建Person类的实例* @param fn 接收一个类作为参数,该函数可以被new调用* @returns 返回该类的实例*/
function factory(fn: IConstructor) {return new fn();
}// 4. 调用函数
const p = factory(Person); // p是Person类的实例,可以调用Person类的方法
以指定某个参数是可选的 : 该参数的类型为 指定的类型与undefined的联合类型
/*** money是可选类型,可传可不传* 可选的参数必须放在必传参数的后面* 可选类型是 指定类型和undefined 的联合类型*/
function find(info: { name: string; age: number; money?: number }) {// 如果要使用可选类型的属性,需要先判断是否存在,因为可能不存在if () {console. + 1000);}
}find({ name: 'coder', age: 123 });
find({ name: 'coder', age: 123, money: 1000 });
JavaScript是支持默认参数的,TypeScript也是支持默认参数的
默认参数的类型其实是 undefined 和 number 类型的联合
/*** num2: 默认参数* 有默认值的参数,可以不传,也可以传undefined,不会报错*/
function foo(num1: number, num2: number = 20) {console.log(num1 + num2);
}foo(10);
foo(10, undefined);
foo(10, 30);
// 第一个参数放到了initNumber,剩余的参数放到了nums数组中
function foo(initNumber: number, ...nums: number[]) {duce((a, b) => a + b, initNumber);
}foo(10);
foo(10, 20);
foo(10, 20, 30);
foo(10, 20, 30, 40);
foo(10, 20, 30, 40, 50);
编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用
一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现
// 函数的重载 : 函数名称相同,参数个数不同,参数类型不同
// 这里是函数的声明,没有函数体
function increment(num1: number, num2: number): number;
function increment(num1: string, num2: string): string;// 这里是函数的实现,使用较为宽泛的类型
function increment(num1: any, num2: any): any {// 如果是数字,则返回数字if (typeof num1 === 'number' && typeof num2 === 'number') {return num1 + num2;} else if (typeof num1 === 'string' && typeof num2 === 'string') {// 如果是字符串,则返回字符串长度return num1.length + num2.length;}
}// 会自动匹配函数的参数类型
const result = increment(1, 2);
// 会自动匹配函数的参数类型
const result2 = increment('hello', 'world');console.log(result, result2);// 函数的重载中,实现函数是不能直接调用的
// 如果没有匹配的函数声明,则会报错
// increment({ name: 'name' }, { age: 18 });
可使用内置工具ReturnType获取函数的返回值类型
可使用内置工具Parameters获取函数的参数类型
/*** ReturnType : 获取函数返回值的类型* Parameters : 获取函数参数的类型*/type cals = (n1: number, n2: number) => number;
// 返回值类型
type calsReturnType = ReturnType<cals>; // number
// 参数类型
type calsParameters = Parameters<cals>; // [n1: number, n2: number]function bar(str: string): string {return 'foo';
}
// 返回值类型
type FooReturnType = ReturnType<typeof bar>; // string
// 参数类型
type FooParameters = Parameters<typeof bar>; // [str: string]
在没有对ts配置的情况下,this的使用可能存在隐患
可在tsconfig.json中进行配置
在没有指定this的情况,this默认情况下是any类型的
/*** 在没有对ts配置的情况下,this的使用可能存在隐患* this的类型是any*/const obj = {name: 'demo',studying() {console.log(`${this.name} is studying`);}
};obj.studying(); // demo is studyingobj.studying.call({});
// TypeError: Cannot read property 'name' of undefinedfunction fn() {console.log(this); // "this" 隐式具有类型 "any"
}
在设置了noImplicitThis为true时, TypeScript会根据上下文推导this,但是在不能正确推导时,就会报错,需要明确的指定this
如果整个项目不想指定this类型,那么要设置为 noImplicitThis:false,注释可能没用
函数的第一个参数类型
函数的第一个参数类型
// 第一个参数是this, 第二个参数开始才是函数的参数
function foo(this: { name: string }, info: {name: string}) {console.log(this, info)
}foo.call({ name: "why" }, { name: "kobe" })
Typescript 提供了一些工具类型来辅助进行常见的类型转换,这些类型全局可用
用于提取一个函数类型中的this 的类型
如果这个函数类型没有this参数返回unknown
function fn(this: { name: string }, age: number) {console.log(this.name, age);
}// 1. 拿到函数的类型
type fnType = typeof fn; // (this: { name: string }, age: number) => void 函数的类型// 2. 通过 ThisParameterType 拿到 this 的类型
type thisType = ThisParameterType<fnType>; // { name: string } this的类型
用于移除一个函数类型Type的this参数类型, 并且返回当前的函数类型
function fn(this: { name: string }, age: number) {console.log(this.name, age);
}// 1. 拿到函数的类型
type fnType = typeof fn; // (this: { name: string }, age: number) => void 函数的类型// 2. 拿到删除this参数,剩余的函数类型
type pureFnType = OmitThisParameter<fnType>; // (age: number) => void
这个类型不返回一个转换过的类型,它被用作标记一个上下文的this类型
用于绑定一个上下文的this
old
interface IState {name: string;age: number;
}interface IStore {state: IState;eating: () => void;
}const store: IStore = {state: {name: '张三',age: 18},// 需要指定 this 的类型,比较麻烦eating(this: IState) {console.log(this.name); // 希望这里的 this 指向 state}
};store.eating.call(store.state); // 张三
new
interface IState {name: string;age: number;
}interface IStore {state: IState;eating: () => void;
}/*** 通过 ThisType<T> 指定 this 的类型为 IState* 使用交叉类型将 this 的类型和 store 的类型合并* 该对象中的所有方法中的 this 都会被推断为 IState* * 相当于不用在每个方法中都写 this: IState!*/
const store: IStore & ThisType<IState> = {state: {name: '张三',age: 18},eating() {console.log(this, this.name); // this 指向 state}
};// store.eating.call(store.state); // { name: '李四', age: 18 } 李四
store.eating.call({ name: '李四', age: 18 }); // { name: '李四', age: 18 } 李四
class Person {/*** ts中的类成员属性必须要声明后使用,普通的js中可以直接使用* 可以给默认值,也可以在构造函数中初始化*/name: string;age: number = 66; // 默认值,但是不知道有什么用,构造函数中初始化的值会覆盖默认值constructor(name: string, age: number) {this.name = name;this.age = age;}
}const p = new Person('Jack', 32);
console.log(p.name); // Jack
console.log(p.age); // 32const p2 = new Person('Tom', 19);
// 父类
class Person {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}eating() {console.log('person eating~');}
}
// 子类
class Student extends Person {sno: string;constructor(name: string, age: number, sno: string) {// 调用父类的构造方法,必须写在第一行super(name, age);this.sno = sno;}// 子类重写父类的eatingeating() {// 调用父类的方法super.eating();console.log('student eating~');}
}
const s = new Student('star', 16, 't100010');
console.log(s.name); // star
console.log(s.age); // 16
console.log(s.sno); // t100010s.eating(); // person eating~ student eating~
// 父类
class Person {eating() {console.log('person eating~');}
}
// 子类
class Student extends Person {eating() {console.log('Student eating~');}
}
// 子类
class Teacher extends Person {eating() {console.log('Teacher eating~');}
}// 多态的目的是为了写出更加具备通用型的代码
// 父类引用指向子类对象
function eatFunction(persons: Person[]) {persons.forEach((person) => {person.eating();});
}// 传入子类对象
eatFunction([new Student(), new Teacher()]);
class Person {// 默认就是publicname: string;public name2: string;
}
class Person {private _name: string;// 通过这样暴露属性出去getName() {return this._name;}// 通过这样更改属性setName(name: string) {this._name = name;}
}const p = new Person();
// 外面直接访问不了
// p._name
p.getName();
p.setName('star');
class Person {protected name: string;
}class Student extends Person {getName() {// 可以直接访问父类中的namereturn this.name;}
}const s = new Student();
s.getName();
// 外面直接访问不了
// s.name
class Person {// 1.只读属性可以在构造函数中赋值,赋值之后就不能再更改了// 2.属性本身不能进行修改,但如果是对象类型,那么对象中的属性是可以修改的readonly name: string;constructor(name: string) {this.name = name;}
}const p = new Person('star');// 不能修改
// p.name = '132';
私有属性是不能直接访问的,或者某些属性想要监听它的获取(getter)和设置(setter)的过程,这个时候可以使用存取器
可以对属性的访问和读取进行拦截操作
class Person {private _name: string;private _age: number;constructor(name: string, age: number) {this._name = name;this._age = age;}set name(name: string) {this._name = name;}get name() {return this._name;}set age(age: number) {// 限制年龄范围,不合法抛出异常if (age < 0 || age > 200) {throw new Error('年龄不合法');}this._age = age;}get age() {// 可以拼接一些字符串,或者做一些其他操作return this._age;}
}const p = new Person('star', 20);console.log(p.name); // star
// 赋值
p.name = 'coder';
// 获值
console.log(p.name); // coder// 限制年龄范围
p.age = 300; // Error: 年龄不合法
TypeScript 提供了特殊的语法,可以把一个构造函数参数转成一个同名同值的类属性
在构造函数参数前添加一个可见性修饰符 public private protected 或者 readonly 来创建参数属性,最后这些类属性字段也会得到这些修饰符
/*** 参数属性 : 语法糖* 1. 修饰符 + 参数名* 2. 修饰符 + 参数名 + 类型* 3. 修饰符 + 参数名 + 类型 + 可选标识符** 修饰符: public, private, protected, readonly** 相当于默认做了以下操作:* 1. 在类中定义了一个同名的成员属性 => public name: string* 2. 在构造函数中给成员属性赋值 => this.name = name*/class Person {// 1. 定义了一个同名的成员属性// public name: string;constructor(public name: string) {// 2. 在构造函数中给成员属性赋值// this.name = name;}sayHello() {return 'Hello, ' + this.name;}
}const person = new Person('World');console.log(person.sayHello()); // Hello, World
class Person {// 静态属性static age: number = 18;// 静态方法static eating() {console.log('eating');}
}// 可直接通过类名访问
console.log(Person.name);
Person.eating();
abstract用来定于抽象类和抽象方法
继承是多态使用的前提
抽象方法
抽象类有如下的特点:
/*** 抽象类中的抽象方法,必须在子类中实现,否则会报错* 抽象类不能直接被实例化,只能被继承,抽象类中的抽象方法不能包含具体实现,只能用于接口* 不能用于实例,抽象类中的抽象方法不能被static修饰,只能用于抽象类,不能用于实例*/// 抽象类,不能直接被实例化,只能被继承
abstract class Person {// 抽象方法,具体实现由子类实现abstract sayHello(): void;// 抽象类中也可以有具体实现的方法,子类可以不实现,也可以实现,也可以重写,也可以调用sayHi() {console.log('Hi');}
}class Student extends Person {constructor(private _studentName: string) {super();}// 子类必须实现抽象类中的抽象方法,否则会报错sayHello() {console.log('Hello,Student', this._studentName);}
}class Teacher extends Person {constructor(private _teacherName: string) {super();}// 子类必须实现抽象类中的抽象方法,否则会报错sayHello() {console.log('Hello,Teacher', this._teacherName);}
}// 1. 普通的方式
const student = new Student('小明哥');
const teacher = new Teacher('decade');
student.sayHello(); // Hello,Student 小明哥
teacher.sayHello(); // Hello,Teacher decade// 2. 多态的方式,父类的引用指向子类的对象
function saySomething(person: Person) {person.sayHello();person.sayHi();
}
saySomething(new Student('空我')); // Hello,Student 空我
saySomething(new Teacher('螺旋踢')); // Hello,Teacher 螺旋踢
TypeScript对于类型检测的时候使用的鸭子类型
鸭子类型
class Student {constructor(public name: string) {}sayHello() {console.log('Hello,Student');}
}class Teacher {constructor(public name: string) {}sayHello() {console.log('Hello,Teacher');}
}function printTeacher(people: Teacher) {people.sayHello();
}// 正常使用
printTeacher(new Teacher('老师')); // Hello,Teacher/*** 不会报错,* 1. Student没有继承Teacher,传错了类型但是没有问题,因为使用了鸭子类型* 只要满足有name属性和sayHello方法就可以传入* 2. 甚至可以传入一个对象,只要满足有name属性和sayHello方法就可以传入*/
printTeacher(new Student('小明')); // Hello,Student
printTeacher({name: '小红',sayHello() {console.log('Hello,小红'); // Hello,小红}
});
类本身也是可以作为一种数据类型的
class Person {name: string;constructor(name: string) {this.name = name;}eating() {console.log(this.name + ' is eating');}
}const p1: Person = new Person('aaa');
p1.eating(); // aaa is eating/*** 因为是鸭子类型, 所以只要有name属性和eating方法就可以* 定义p2为Person的类型,要求p2中必须有name属性和eating方法*/
const p2: Person = {name: 'bbb',eating() {console.log(this.name + ' is eating');}
};
p2.eating(); // bbb is eating
// 通过类型 (type) 来声明对象类型
type InfoType = {name: string;age: 18;
};
const info: InfoType = {name: 'John',age: 18
};
/*** 另外一种方式声明对象类型,使用 interface* interface 用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法* 同时 interface 也可以当成类型声明去使用,可以定义可选属性,也可以定义只读属性*/
interface IInfoType2 {readonly name: string;age?: number;
}
const info2: IInfoType2 = {name: 'John'
};
区别一 : type类型使用范围更广,接口类型只能用来声明对象
// type 可以声明基础类型、联合类型、元组、枚举、类、接口、函数、对象等
type infoType = number | string;
type objType = {name: string;age: number;
};// interface 可以声明接口、函数、对象等
interface infoInterface {}
区别二 : 在声明对象时,interface可以多次声明
/*** type 只能声明一次, 不能重复声明,会报错* 不允许两个相同名称的别名同时存在*/
type objType = {name: string;age: number;
};
// err, 重复声明
// type objType = {
// address: string;
// }
const obj: objType = {name: 'lison',age: 18
};/*** interface 可以声明多次, 会进行合并* 允许两个相同名称的接口同时存在* 使用时,合并的属性会叠加,方法会进行合并,同名方法会被覆盖 => 必须都使用上*/
interface infoInterface {name: string;
}
interface infoInterface {age: number;address?: string;
}
const info: infoInterface = {// name和age都必须使用,否则报错, address可选name: 'lison',age: 18
};
区别三 : interface可以实现继承
// interface可以实现继承
interface IPerson {name: string;age: number;
}interface Ikun extends IPerson {secret: string;
}const smallBlack: Ikun = {// name 和 age 和 secret 都是必须的name: '小黑子',age: 18,secret: '鸡你太美!!!!!!'
};
区别四 : interface可以被类实现
// interface可以被类实现
interface IPerson {name: string;age: number;
}class Person implements IPerson {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}
}
如果是非对象类型的定义 => 使用type
如果是对象类型的声明 => 使用interface,会更加灵活
type 和 interface根本区别 :
不能提前知道一个类型里的所有属性的名字,但是知道这些值的特征
这种情况,可以用一个索引签名 (index signature) 来描述可能的值的类型
// 定义类型
interface IndexLanguage {// 索引为number,值为string[index: number]: string;
}const language: IndexLanguage = {1: 'typescript',2: 'javascript',3: 'python',4: 'ruby',5: 'c++'// 报错,索引只能是number// '7':1
};console.log(language[1]); // typescript
console.log(language[2]); // javascript
// type calcFn = (n1: number, n2: number) => number;interface ICalcFn {// 定义一个函数类型(n1: number, n2: number): number;
}function calc(n1: number, n2: number, calcFn: ICalcFn): number {return calcFn(n1, n2);
}const add = (n1: number, n2: number): number => n1 + n2;console.log(calc(20, 30, add));
接口是支持多继承的(类不支持多继承)
interface Ieat {eating: () => void;
}
interface Idrink {drink: () => void;
}// 可以多继承
interface action extends Ieat, Idrink {sleep: () => void;
}const person: action = {// 都要实现eating: () => {},drink: () => {},sleep: () => {}
};
类可以实现多个接口
interface IPerson {name: string;age: number;studying(this: IPerson): void;
}/*** implements 实现接口* 属性和方法必须实现*/
class Person implements IPerson {age: number;constructor(public name: string, age: number) {this.age = age;}studying() {console.log(this.name, 'studying');}
}const p = new Person('why', 18);
p.studying();
interface IPerson {eating: () => void;
}
interface IPerson {drink: () => void;
}// 相同的接口名会合并成一个接口
const p: IPerson = {// 都要实现eating: () => {},drink: () => {}
};
// 封装一个函数,传入一个参数,并且返回这个参数function sum<T>(n: T): T {return n;
}/*** 完整的写法* 调用方式一 : 明确指定参数类型* 在调用函数时,用明确的类型来代替T*/
const n1 = sum<number>(1); // n1的类型是number
const n2 = sum<string>('str'); // n2的类型是string
const n3 = sum<boolean[]>([true, false]);
const n4 = sum<number[][]>([[1, 2, 3],[4, 5, 6]
]);/*** 简化的写法* 调用方式二 : 不指定参数类型,类型推断,推出来的是字面量类型* 在调用函数时,不用明确的类型来代替T,而是通过传入的参数来推断出T的类型* const => 返回的是字面量类型 * let => 返回的是具体的类型*/const n5 = sum(1); // n5的类型是字面量类型1
let n6 = sum('str'); // n6的类型是string
/*** 通过泛型来约束,传入的参数和返回值的类型* @param initState state的初始值* @returns 返回一个数组, 第一个元素是state, 第二个元素是setState*/
function useState<T>(initState: T): [T, (newState: T) => void] {let state = initState;function setState(newState: T) {state = newState;}return [state, setState];
}const [count, setCount] = useState(0); // count: number | setCount: (newState: number) => voidconst [age, setAge] = useState('jack'); // age: string | setAge: (newState: string) => void
function foo<T, E, O>(arg1: T, arg2: E, arg3: O): [T, E, O] {return [arg1, arg2, arg3];
}// 1. 指定类型
const [a, b, c] = foo<number[], { name: string }, boolean>([1, 2], { name: 'why' }, true);// 2. 类型推断
const res = foo(1, 'str', true); // [1, 'str', true] => [number, string, boolean]
/*** 泛型* T: 类型变量* O:类型变量,默认值为string*/
interface Iperson<T, O = number> {name: T;age: O;address: string;
}// 传递类型变量,T为string,O为number,使用默认值
const p: Iperson<string> = {name: '张三',age: 18,address: '北京'
};// 传递类型变量,T为number,O为string
const p1: Iperson<number, string> = {name: 11,age: '22',address: '北京'
};
class point<T> {x: T;y: T;constructor(x: T, y: T) {this.x = x;this.y = y;}
}// 1. 指定泛型类型
const p1 = new point<number>(1, 2);
const p3: point<string> = new point<string>('1.11', '1.22');// 2. 类型推断
const p2 = new point('1.11', '1.22');// 3. 指定数组有两种,一种是元素类型后面加[], 一种是Array<元素类型>
const names1: string[] = ['Max', 'Manu'];
const names2: Array<string> = ['Max', 'Manu'];
interface Ilength {length: number;
}// 规定传入的参数必须包含length属性
function getLength<T extends Ilength>(target: T): T {return target;
}// 都拥有length属性,但是不一定是字符串,也可以是数组,对象,其他类型,所以不能用类型断言,只能用接口
const r1 = getLength('123');
const r2 = getLength([1]);
const r3 = getLength({ length: 10, name: '123' });
/*** keyof => 获取对象的所有key, 返回一个联合类型*/
interface IKun {name: string;age: number;
}
type kunkun = keyof IKun; // "name" | "age"/*** K extends keyof O => K必须是O的key, 且K的类型必须是O的key的类型* 把传入的k进行类型约束* 确保不会传入一个不存在的key*/
function getObjectProperty<O, K extends keyof O>(obj: O, key: K) {return obj[key];
}const kun: IKun = {name: 'kun',age: 18
};const name = getObjectProperty(kun, 'name');
// const address = getObjectProperty(kun, 'address'); // 报错, 因为address不是kun的keyexport {};
一个类型需要基于另外一个类型,但是你又不想拷贝一份,这个时候可以考虑使用映射类型
大部分内置的工具都是通过映射类型来实现的
大多数类型体操的题目也是通过映射类型完成的
映射类型建立在索引签名的语法上
// 1. 定义一个接口
interface IPerson {name: string;age: number;
}/*** 2. 映射类型函数* 2.1. 映射类型不能用于interface, 只能用于type* 2.2 <Type> => 表示要对Type进行映射* 2.3 keyof Type => 表示Type中的所有key组成的联合类型 => "name" | "age"* 2.4 [K in keyof Type] => 表示对Type中的每一个key进行映射*/
type MapToPerson<Type> = {[K in keyof Type]: Type[K];
};// 3. 使用映射类型,将IPerson中的所有属性都映射到新的类型中
type newPerson = MapToPerson<IPerson>; // {name: string, age: number}//4. 定义一个对象,必须包含IPerson中的所有属性
const person: newPerson = {name: 'why',age: 18
};
修饰符 :
interface IPerson {name: string;age: number;address: string;
}/*** readonly : 将所有的属性变成只读的* ? : 将所有的属性变成可选的*/
type MapToPerson<T> = {readonly [k in keyof T]?: T[k];
};/*** 更改后的结果: 都变成了只读的可选属性* readonly name?: string | undefined;* readonly age?: number | undefined;* readonly address?: string | undefined;*/
type newPerson = MapToPerson<IPerson>;
还可以通过前缀 - 或者 + 删除或者添加这些修饰符,如果没有写前缀,相当于使用了 + 前缀
interface IPerson {name: string;age?: number;readonly height: number;address?: string;
}/*** -? : 删除属性的可选性* -readonly : 删除属性的只读性*/
type MapToPerson<T> = {-readonly [k in keyof T]-?: T[k];
};/*** 删除后的结果: 所有属性都是必选的* name: string;* age: number;* height: number;* address: string;*/
type PersonRequire = MapToPerson<IPerson>;
/*** 条件类型例子一*/
// 1. 定一个类型
type IDType = string | number;
// 2. 条件类型 => T extends IDType ? true : false; => 三元表达式
type ResType = number extends IDType ? true : false;console.log('-------------------------------------------------------------');/*** 条件类型例子二* 函数重载*/
// 1. 写法一 => 默认写法
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {return a + b;
}
const one1 = add(1, 2); // one1 => number类型 => 3
const onw2 = add('1', '2'); // onw2 => string类型 => '12'// 2. 写法二 => 调用签名,函数实现签名
interface IAdd {(a: number, b: number): number;(a: string, b: string): string;
}
const increment: IAdd = (a: any, b: any): any => {return a + b;
};
const two1 = increment(1, 2); // two1 => number类型 => 3
const two2 = increment('1', '2'); // two2 => string类型 => '12'// 3. 写法三 => 使用条件类型
function sum<T extends number | string>(a: T, b: T): T extends number ? number : string;
function sum(a: any, b: any) {return a + b;
}
const three1 = sum(1, 2); // three1 => number类型 => 3
const three2 = sum('1', '2'); // three2 => string类型 => '12'
条件类型提供了 infer 关键词
可以从正在比较的类型中推断类型,然后在 true 分支里引用该推断结果
ps : 在返回值处重新写一遍<T extends new (...args: any[]) => any>
/*** 使用infer关键字* 自定义封装一个ReturnType,获取函数的返回值类型* 自定义封装一个Parameters,获取函数的参数类型*/// 使用TypeScript内置的ReturnType和Parameters
function bar(n: number): string {return `${n}hello`;
}
// 函数的返回值类型
type FooReturnType = ReturnType<typeof bar>; // string
// 函数的参数类型
type FooParameters = Parameters<typeof bar>; // [n: number]console.log('------------------------------------');// 使用infer关键字,自定义封装一个ReturnType,获取函数的返回值类型
/*** (...args: any[]) => any 代表一个函数类型* T extends (...args: any[]) => any 代表泛型T必须是一个函数类型,且函数的参数类型和返回值类型是什么不确定,所以用T代替 => 限制T的范围* (...args: any[]) => infer R 代表函数的返回值类型是什么不确定,所以用R代替*/
type MyReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R? R: never;
type FooReturnType2 = MyReturnType<typeof bar>; // stringconsole.log('------------------------------------');// 使用infer关键字,自定义封装一个Parameters,获取函数的参数类型
/*** (...args: any[]) => any 代表一个函数类型* T extends (...args: any[]) => any 代表泛型T必须是一个函数类型,且函数的参数类型和返回值类型是什么不确定,所以用T代替 => 限制T的范围* (...args: infer P) => any 代表函数的参数类型是什么不确定,所以用P代替*/
type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any? P: never;type FooParameters2 = MyParameters<typeof bar>; // [n: number]
当在泛型中使用条件类型的时候,如果传入一个联合类型,就会变成 分发的
很多内置工具底层都是用了分发,很重要!!!
type toArray<T> = T extends any ? T[] : never;type NumberArray = toArray<number>; // number[]
type StringArray = toArray<string>; // string[]/*** number[] | string[] 而不是 (number | string)[]* 因为extxtends是分发式的, 会把联合类型拆分成多个条件, 然后再进行条件判断, 最后再合并成一个联合类型* 1. 当传入string | number时,会遍历联合类型中的每一个成员* 2. 相当于执行了ToArray<string> | ToArray<number>* 3. 最后合并成一个联合类型,也就是string[] | number[]*/
type NumberOrStringArray = toArray<number | string>; // number[] | string[]
主要有两种 :
commonjs => node环境 => webpack/vite
ESmodule => 在TypeScript中最主要使用的模块化方案就是ES Module
import / export 语法
util/math.ts
export function sum(num1: number, num2: number): number {return num1 + num2;
}
util/math.ts
export type Idtype = string | number;export interface IPersion {name: string;age: number;
}
main.ts
// 导入普通的js模块,正常使用即可
import { sum } from './util/math';
console.log(sum(1, 2)); // 3/*** 当导入类型时,可以使用type关键字* 因为最终编译后的代码中,不会有ts的类型代码* * 让编译器自己识别的话,会比较耗性能,因为要多一步类型的判断* 所以用type声明后,方便编译打包时的删除,减少代码量,提高性能*/
import { type Idtype, type IPersion } from './util/type';// 如果导入的全都是类型,可以使用import type
// import type { Idtype, IPersion } from './util/type';const id: Idtype = 123;
const person: IPersion = {name: 'why',age: 18
};
了解即可,直接使用ES模块
// 定义命名空间
namespace time {// 只有导出的函数才能被外部调用export function format(): number {return new Date().getTime();}function foo(): string {return '123';}const bar = '123';// 只有导出的变量才能被外部调用export const baz = '456';
}// 调用命名空间导出的函数
console.log(time.format()); // 1702541990130
// 调用命名空间导出的变量
console.log(time.baz); // 456// 导出命名空间,可以被其他模块使用
export namespace price {export function format(): string {return '123.456';}
}
// 其他模块
// import { price } from './文件名.ts';
内置类型声明是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件
内置类型声明通常在我们安装typescript的环境中会带有的
TypeScript 使用模式命名这些声明文件lib.[something].d.ts
可在 tsconfig.json 的 target以及l ib 中进行配置内置声明
外部类型声明通常是我们使用一些库(比如第三方库)时,需要的一些类型声明
外部定义类型声明 通常有两种类型声明的方式
在自己的库中进行类型声明 ( 编写.d.ts文件 )
例如 : axios,可在node_modules中找到
通过社区的一个 公有库DefinitelyTyped 存放类型声明文件
例如 : react,在node_modules中找不到
DefinitelyTyped的github地址 : GitHub - DefinitelyTyped
( 推荐 ) 查找声明安装地址的方式 : TypeScript: Search for typed packages
假如第三方库中没有声明文件,需要自己编写.d.ts文件~
只要有代码的实现,声明一下,在代码中即可运行,这里使用webpack配置的环境
ps : 不需主动引入,会自动查找
在index.html中写代码的实现
ps :
按道理来说,这里是最顶层,这里定义后,其他的文件中都能使用,但是不行
因为其他的打包后会到bound.js中,然后在这里被引用
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script>let starName = 123function foo(){console.log('foo');}</script>
</body>
</html>
在src目录下生成star.d.ts文件
需要生声明后,变成全局后,才能在其他ts中使用
// 声明变量
declare const starName: string;
// 声明函数
declare function foo(): void;// 如果用到了图片,需要声明下,要不然解析不了, 因为图片是一个文件, 所以不能直接在声明文件中引用
declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.gif';
declare module '*.svg';
declare module '*.bmp';
declare module '*.tiff';// 声明vue文件
declare module '*.vue' {import Vue from 'vue';export default Vue;
}// 声明命名空间,是全局的,可通过访问
declare namespace Star {export const coderName: string;
}
在main.js中进行调用
// 如果没有在.d.ts文件中声明,是访问不到的
console.log(starName); // 123
foo(); // foo
JavaScript 项目可以使用 jsconfig.json 文件,它的作用与 tsconfig.json 基本相同,只是默认启用了一些 JavaScript 相关的编译选项
tsconfig.json文件有两个作用:
在调用 tsc 命令并且没有其它输入文件参数时,编译器将由当前目录开始向父级目录寻找包含 tsconfig 文件的目录
调用 tsc 命令并且没有其他输入文件参数,可以使用 --project (或者只是 -p)的命令行选项来指定包含了 tsconfig.json 的目录
webpack中使用ts-loader进行打包时,也会自动读取tsconfig文件,根据配置编译TypeScript代码
练习 练习+答案
用于构造一个Type下面的所有属性都设置为可选的类型
interface Ikun {name: string;age: number;say?: () => void;
}/*** Ikun都变成可选的* Partial 的作用就是将某个类型里的属性全部变为可选项 ?* 结果如下:* interface Ikun {* name?: string | undefined;* age?: number | undefined;* say?: () => void | undefined;* }*/
type IkunOptional = Partial<Ikun>;
interface Ikun {name: string;age: number;say?: () => void;
}/*** 使用映射类型实现Partial* 将某个类型里的属性全部变为可选项 ?*/
type MyPartial<T> = {[P in keyof T]?: T[P];
};/*** 结果如下:* type IkunOptional = {* name?: string | undefined;* age?: number | undefined;* say?: () => void | undefined;* }*/
type IkunOptional = MyPartial<Ikun>;
用于构造一个Type下面的所有属性全都设置为必填的类型,这个工具类型跟 Partial 相反
interface Ikun {name?: string;age: number;say?: () => void;
}/*** Ikun都变成必填的* Required 的作用就是将某个类型里的属性全部变为可选项 ?* 结果如下:* interface Ikun {* name: string;* age: number;* say: () => void;* }*/
type IkunOptional = Required<Ikun>;
interface Ikun {name?: string;age: number;say?: () => void;
}/*** 使用映射类型实现Required* 将某个类型里的属性全部变为必传属性*/
type MyRequired<T> = {[P in keyof T]-?: T[P];
};/*** 结果如下:* type IkunRequired = {* name: string;* age: number;* say: () => void;* }*/
type IkunOptional = MyRequired<Ikun>;
用于构造一个Type下面的所有属性全都设置为只读的类型,意味着这个类型的所有的属性全都不可以重新赋值
interface Ikun {name?: string;age: number;say: () => void;
}/*** Ikun都变成只读的* Readonly 会将所有属性变成只读的,也就是不可修改* 结果如下:* interface Ikun {* readonly name?: string | undefined;* readonly age: number;* readonly say: () => void;* }*/
type IkunOptional = Readonly<Ikun>;
interface Ikun {name?: string;age: number;say: () => void;
}/*** 使用映射类型实现readonly* 将某个类型里的属性变成只读的*/
type MyReadonly<T> = {readonly [P in keyof T]: T[P];
};
/*** 结果如下:* interface Ikun {* readonly name?: string | undefined;* readonly age: number;* readonly say: () => void;* }*/
type IkunOptional = MyReadonly<Ikun>;
用于构造一个对象类型,它所有的key(键)都是Keys类型,它所有的value(值)都是Type类型
type IAddresskeys = '上海' | '北京' | '广州' | '深圳';
interface Ikun {name: string;age: number;say?: () => void;
}/*** Record<K,T> 用于将 K 中所有的属性的值转化为 T 类型* 结果的类型为一个新的对象类型,该对象包含了 K 中所有的属性,每个属性值都是 T 类型* 结果如下:* type IBlack = {* 上海: Ikun;* 北京: Ikun;* 广州: Ikun;* 深圳: Ikun;* }*/
type IBlack = Record<IAddresskeys, Ikun>;const black: IBlack = {上海: { name: '小红', age: 18 },北京: { name: '小蓝', age: 18 },广州: { name: '小紫', age: 18 },深圳: {name: '小黑子',age: 18,say() {console.log('深圳');}}
};
type IAddresskeys = '上海' | '北京' | '广州' | '深圳';
interface Ikun {name: string;age: number;say?: () => void;
}/*** Keys 必须 是联合类型,且联合类型的值必须是 'string' | 'number' | 'symbol' 的子集 * 1. keyof any => 'string' | 'number' | 'symbol',代表的是所有可以作为属性名的类型* 2. Keys extends keyof any => 代表的是 Keys 必须是 'string' | 'number' | 'symbol' 的子集* 3. [P in Keys]: T => 代表的是遍历 Keys,将 Keys 中的属性名作为新对象的属性名,属性值为 T*/
type MyRecord<Keys extends keyof any, T> = {[P in Keys]: T;
};
/*** 结果如下:* type IBlack = {* 上海: Ikun;* 北京: Ikun;* 广州: Ikun;* 深圳: Ikun;* }*/
type IBlack = MyRecord<IAddresskeys, Ikun>;const black: IBlack = {上海: { name: '小红', age: 18 },北京: { name: '小蓝', age: 18 },广州: { name: '小紫', age: 18 },深圳: {name: '小黑子',age: 18,say() {console.log('深圳');}}
};
用于构造一个类型,它是从Type类型里面挑了一些属性Keys
interface Ikun {name: string;age: number;say?: () => void;address?: string;
}/*** Pick 从某个类型中挑选出指定的属性* 结果如下:* type IkunPick = {* name: string;* address?: string | undefined;* }*/
type IkunPick = Pick<Ikun, 'name' | 'address'>;
interface Ikun {name: string;age: number;say?: () => void;address?: string;
}/*** K extends keyof T 表示 K 是 T 的属性 => 约束 K 的类型* [P in K]: [P 是 K 的属性]*/
type MyPick<T, K extends keyof T> = {[P in K]: T[P];
};/*** 结果如下:* type IkunPick = {* name: string;* address?: string | undefined;* }*/
type IkunPick = MyPick<Ikun, 'name' | 'address'>;
用于构造一个类型,它是从Type类型里面过滤了一些属性Keys
interface Ikun {name: string;age: number;say?: () => void;address?: string;
}/*** Omit<T, K> 表示忽略 T 中的 K 属性* 获取 Ikun 中除了 name 属性之外的所有属性* 结果如下:* type IkunOmit = {* age: number;* say?: (() => void) | undefined;* address?: string | undefined;* }*/
type IkunOmit = Omit<Ikun, 'name'>;
interface Ikun {name: string;age: number;say?: () => void;address?: string;
}/*** 从T中排除K* 使用了分发条件类型* P in keyof T => 表示遍历T中的所有属性* as P => 表示将遍历的结果赋值给P* P extends K ? never : P => 表示如果P是K的子集,则返回never,否则返回P*/
type MyOmit<T, K extends keyof T> = {[P in keyof T as P extends K ? never : P]: T[P];
};/*** 结果如下:* type IkunOmit = {* age: number;* address?: string | undefined;* }*/
type IkunOmit = MyOmit<Ikun, 'name' | 'say'>;
用于构造一个类型,它是从UnionType联合类型里面排除了所有可以赋给ExcludedMembers的类型
interface Ikun {name: string;age: number;say?: () => void;address?: string;
}/*** Exclude<UnionType, ExcludedMembers>* 从UnionType中排除ExcludedMembers* UnionType: 联合类型* ExcludedMembers: 要排除的成员, 可以是单个成员,也可以是多个成员组成的联合类型** 结果如下:* type IkunExclude = "address" | "say"*/
type IkunExclude = Exclude<keyof Ikun, 'name' | 'age'>;
可以使用它来实现MyOmit
interface Ikun {name: string;age: number;say?: () => void;address?: string;
}/*** 从T中排除U* Exclude<keyof T, U> => keyof T中排除U,把交集排除掉* [K in Exclude<keyof T, U>]: T[K] => 从T中排除U后,再遍历T中的key,然后取出T中的value*/
type MyOmit<T, U extends keyof T> = {[K in Exclude<keyof T, U>]: T[K];
}/*** 结果如下:* type IkunExclude = "address" | "say"*/
type IkunExclude = MyOmit<Ikun, 'name' | 'age'>;
interface Ikun {name: string;age: number;say?: () => void;address?: string;
}/*** 从T中排除U* 使用了分发条件类型* T extends U => 表示如果T是U的子集,那么返回never,否则返回T*/
type MyExclude<T, U> = T extends U ? never : T;/*** 结果如下:* type IkunExclude = "address" | "say"*/
type IkunExclude = MyExclude<keyof Ikun, 'name' | 'age'>;
用于构造一个类型,它是从Type类型里面提取了所有可以赋给Union的类型,和Exclude相反
interface Ikun {name: string;age: number;say?: () => void;address?: string;
}/*** Extract<Type, Union>* 从Type中提取出可以赋值给Union的类型* Type: 联合类型* Union: 要提取的类型,单个类型或者联合类型* 结果如下:* type IkunExclude = "name" | "age"*/
type IkunExtract = Extract<keyof Ikun, 'name' | 'age'>;
interface Ikun {name: string;age: number;say?: () => void;address?: string;
}/*** 方式一* @param T => Ikun* @param U => 'name' | 'age'* K in keyof T => k是Ikun的key, 也就是name, age, say, address* as K => 表示将遍历的结果赋值给K* K extends U ? K : never => 如果K是U的子集,那么返回K, 否则返回never*/
type MyExtract<T, U extends keyof T> = {[K in keyof T as K extends U ? K : never]: T[K];
};
/*** 结果如下* type IkunExtract{* name: string;* age: number;* }*/
type IkunExtract = MyExtract<Ikun, 'name' | 'age'>;console.log('------------------------------------');/*** 方式二* @param T => Ikun的所有key, 也就是name | age | say | address* @param U => 'name' | 'age'* T extends U ? T : never => 如果T是U的子集,那么返回T, 否则返回never*/
type MyExtract2<T, U> = T extends U ? T : never;
/*** 结果如下* type IkunExtract{* name: string;* age: number;* }*/
type IkunExtract2 = MyExtract2<keyof Ikun, 'name' | 'age'>;
用于构造一个类型,这个类型从Type中排除了所有的null、undefined的类型
type IKun = string | number | undefined | null | boolean;/*** NonNullable* 从类型T中排除null和undefined* 结果如下* type Ikuns = string | number | boolean*/
type Ikuns = NonNullable<IKun>;
type IKun = string | number | undefined | null | boolean;/*** T extends null | undefined => 如果T是null或者undefined,返回never,否则返回T*/
type MyNonNullable<T> = T extends null | undefined ? never : T;/*** 结果如下* type Ikuns = string | number | boolean*/
type Ikuns = MyNonNullable<IKun>;
用于构造一个由所有Type的构造函数的实例类型组成的类型
class Person {}const p1: Person = new Person();/*** typeof Person => Person构造函数的类型 * InstanceType<typeof Person> => Person构造函数构建出的实例的类型*/
type MyPerson = InstanceType<typeof Person>;
const p2: MyPerson = new Person();
class Person {}
class Studen {}/*** 可以使用infer关键字来推断出函数的返回值类型* 推断出p的类型为Person* 推断出s的类型为Studen* 在返回值处重新写一遍<T extends new (...args: any[]) => any>* 是为了让函数的返回值类型和传入的构造函数类型保持一致* 如果不写的话,会推断出p和s的类型为any*/
function fectory<T extends new (...args: any[]) => any>(ctor: T
): T extends new (...args: any[]) => infer R ? R : any {return new ctor();
}
const p = fectory(Person);
const s = fectory(Studen);console.log('-------------------');/*** 可以使用InstanceType<T>来获取构造函数的实例类型* 推断出p2的类型为Person* 推断出s2的类型为Studen*/
function fectory2<T extends new (...args: any[]) => any>(ctor: T): InstanceType<T> {return new ctor();
}
const p2 = fectory2(Person);
const s2 = fectory2(Studen);
本文发布于:2024-01-31 21:00:38,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170670604031322.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |