零基础学习 之 TS

阅读: 评论:0

零基础学习 之 TS

零基础学习 之 TS

思考一个问题,JavaScript是一门非常优秀的编程语言,但是直到今天,JavaScript在类型检测上依然是毫无进展,所以我们需要学习TypeScript,这不仅仅可以为我们的代码增加类型约束,而且可以培养我们前端程序员具备类型思维

一、认识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代码。

  • JavaScript所拥有的特性,TypeScript全部都支持
  • 在语言层面上,不仅仅增加了类型约束,而且包括一些语法的扩展
  • TypeScript在实现新特性的同时,总是保持和ES标准的同步甚至是领先
  • TypeScript最终会被编译成JavaScript代码,不需要担心兼容性问题
  • TypeScript不仅让JavaScript更加安全,而且给它带来了诸多好用的好用特性

安装TypeScript

# 安装命令
npm install typescript -g# 查看版本
tsc --version

二、配置TypeScript的环境

1. 使用ts-node

01 - 安装

// 安装ts-node及其依赖包
npm install ts-node tslib @types/node -g

02 - 创建 .ts 文件

// 代码栗子,创建 .ts 文件// 规定message的类型为string
let message: string = 'hello'// 报错!
// message = 123// 规定参数类型为string
function abc(name: string){}console.log(message);// 因为ts默认作用域会在一个,这样设置导出,会让文件形成单独作用域
export {}

03 - 使用 ts-node

// ts-node可以直接运行TypeScript代码
ts-node 文件名

2. webpack进行配置

具体可看之前的webpack文章,webpack 之 零基础使用常用的Loader  ,这里我快速过一遍哈

01 - 项目初始化

npm init -y

02 - 安装包

npm install webpack webpack-cli webpack-dev-server html-webpack-plugin ts-loader typescript -D

03 - 新增index.html模版

04 - package.json配置

05 - fig.js配置

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'})]
};

06 - 生成tsconfig.json 

// 先不用理这个文件,后续讲解
tsc --init

07 - 创建文件夹

创建src文件夹,里面再创建main.ts 

const message: string = '123'console.log(message);

08 - npm run serve运行即可


三、变量的声明

01. 声明变量的关键字

声明了类型后TypeScript就会进行类型检测,声明的类型可以称之为类型注解

var/let/const 标识符: 数据类型 = 赋值;

数据类型的大小写是有区别的 ,用小写即可

  • string ( 小写 ) 是TypeScript中定义的字符串类型
  • String ( 大写 ) 是ECMAScript中定义的一个类

02. 类型推导/推断

类型推导 : 声明一个标识符时,如果有进行直接赋值,会根据赋值的类型推导出标识符的类型

 

ps : 

let  =>  进行类型推导,推导出来通用类型

const  =>  进行类型推导,推导出来的是字面量类型


四、TypeScript的数据类型

1. number 类型

TypeScript和JavaScript一样,不区分整数类型(int)和浮点型(double),统一为
number类型

let demo: number = 22;     // 十进制
let demo: number = 0b110;  // 二进制
let demo: number = 0o555;  // 八进制
let demo: number = 0xf23;  // 十六进制

2. boolean 类型

boolean类型只有两个取值:true和false 

let bool: boolean = true
let bool: boolean = 30 > 20

3. string 类型

string类型是字符串类型,可以使用单引号或者双引号表示,同时也支持ES6的模板字符串来拼接变量和字符串

const name: string = 'star'
const info: string = `my name is ${name}`

4. Array 类型 

需要制定数组类型,同时制定数组内部值的类型

// 写法一 : 不推荐
const name: Array<string> = []  // 写法二 : 推荐
const name: string[] = []let name1: string[] = ['张三', '李四', '王五'];
name1.push(1); // 报错, 只能添加字符串类型的数据// 数组的类型最好是一致的,不一致的话,可以使用联合类型
let name2: (string | number)[] = ['张三', '李四', '王五'];
name2.push(1);
name2.push('赵六');

5. 对象 类型

// 1. 使用自动推导
const info = {name: 'star',age: 18
};// 2. 手动写入
let info: {name: string;age: 20; // 这里的20是字面量类型,表示age只能是20
} = {name: 'John',age: 20 // 必须是20,否则报错
};

6. null 类型

const n: null = null

7. undefined 类型

const n: undefined = undefined

8. symbol 类型

// Symbol 生成独一无二的key
const info = {[Symbol('name')]: 'star',[Symbol('name')]: 'coder'
};

9. any 类型 

无法确定一个变量的类型,并且可能它会发生一些变化,这个时候可以使用any类型

any类型有点像一种讨巧的TypeScript手段:

  • 可以对any类型的变量进行任何的操作,包括获取不存在的属性、方法
  • 给一个any类型的变量赋值任何的值,比如数字、字符串的值

什么时候使用any

  • 如果对于某些情况的处理过于繁琐不希望添加规定的类型注解( 比如从服务器中获取到的数据 )
  • 或者在引入一些第三方库时,缺失了类型注解
    • 包括在Vue源码中,也会使用到any来进行某些类型的适配
// 任意类型都可赋值,也可赋值给任何类型
let message: any = 'star';message = 123;
message = true;// 任意类型的值,可以访问任意属性和方法
message.length;
Fixed();

10. unknown 类型

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;
}
  • unknown类型只能赋值给unknown类型和any类型
  • any类型可以赋值给任何类型
  • unknown类型是更加安全的any类型
let result: unknown;
// 报错
let num: number = result// 可以赋值
let unres: unknown = result
let anyres: any = result

11. void 类型

当函数没有返回值的时候,该函数就是void类型,不写也阔以,会自动推导

// 如果返回值时void类型,也可以返回undefined
const sum = (num1: number, num2: number): void => {console.log(num1, num2);return undefined // 不会报错
};

12. never 类型 

开发中很少实际去定义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); 

13. tuple 元组类型

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);

14. 函数的参数及返回值 类型

// 可指定函数的参数类型和个数和返回值的类型   name: string => 指参数类型为string    string => 指函数的返回值类型为string,可不写,会推导
function getInfo(name: string): string {return name;
}// 匿名函数不需要写类型,TypeScript会根据forEach函数的类型以及数组的类型  推断出  item的类型
// 因为函数执行的上下文可以帮助确定参数和返回值的类型
const names = ['a', 'b', 'c'];
names.forEach((item) => {});

15. {} 对象类型

  • 对象可以添加属性,并且告知TypeScript该属性需要是什么类型
  • 属性之间可以使用 , 或者 ; 来分割,最后一个分隔符是可选的
  • 每个属性的类型部分也是可选的,如果不指定,那么就是any类型
// info是一个对象类型,对象中有两个属性function find(info: { name: string; age: number }) {}

16. ? 可选类型

对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?

// money是可选类型,可传可不传
function find(info: { name: string; age: number; money?: number }) {}find({ name: 'coder', age: 123 });
find({ name: 'coder', age: 123, money: 1000 });

17. | 联合类型

TypeScript的类型系统允许我们使用多种运算符,从现有类型中构建新类型

联合类型(Union Type)

  • 联合类型是由两个或者多个其他类型组成的类型
  • 表示可以是这 类型中的任何一个值
  • 联合类型中的每一个类型被称之为联合成员(union's members) 
  • 使用时,需使用 类型缩小判断类型后 再进行操作
// 联合类型  可以为其中的一种// 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);

 18. 交叉类型 & 

交叉类似表示需要满足多个类型的条件,交叉类型使用 & 符号

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: () => {}
};

19. type 类型别名

// type 用于定义类型别名
type IdType = string | number | boolean;// 太长了
function idSet(id: string | number | boolean) {}
// 取别名可以优化
function idSet(id: IdType) {}

20. as 类型断言

有时候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)); // 运行时报错

21. ! 非空类型断言

如果确定传入的参数是有值的,这个时候可以使用非空类型断言

非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过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);
}

22. ?.可选链

可选链事实上并不是TypeScript独有的特性,它是ES11(ES2020)中增加的特性

  • 可选链使用可选链操作符 ?.
  • 它的作用是当对象的属性不存在时,会短路,直接返回undefined,如果存在,那么才会继续执行
// 定义一个类型
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 {};

23. !! 取boolean类型

const message: string = 'hello'
const flag: boolean = Boolean(message)
// !! 可以用作取反        
const flag1: boolean = !!message

24. ?? 

// ?? 有值的时候取前面的值,没值的时候取后面的值
// 和三目运算符很像,不过更简洁一点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

25. 字面量类型

字面量类型的类型和值要保持一致

const message = 'hello';  // 这个message的类型就是hello
let mess = 'hello'  //这个mess的类型才是stringlet me : 123 = 123
me = 456 // 报错

字面量类型的意义 : 必须结合联合类型

let align: 'left' | 'right' | 'center' = 'left'
// 再次修改值的时候,只能允许修改定义了类型的值
align = 'right'

26. 字面量推理

栗子

const info = {url: 'www.baidu',method: 'GET'
};function request(url: string, method: 'GET' | 'POST') {console.log(url, method);
}// 这里hod会报错,因为默认推导method是字符串类型,所以不能这么传
request(info.url&#hod)

解决方式一 : 推荐

// 定义一个类型
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 {};

27. 严格字面量赋值检测

对于对象的字面量赋值,在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);

28. 类型缩小

 typeof 类型缩小 

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';}
}

instanceof 类型缩小

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();}
}

in 类型缩小

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');}
};

29. 枚举类型

枚举类型是为数不多的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);

五、TypeScript的函数详解

1. 函数的类型

函数类型表达式

/***  函数类型表达式*  格式:(参数: 类型, 参数: 类型, ...) => 返回值类型*  定义的时候*   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类的方法

2. 参数的可选类型

以指定某个参数是可选的 : 该参数的类型为 指定的类型与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 });

3. 参数的默认值

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);

4. 剩余参数

// 第一个参数放到了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);

5. 函数的重载

编写不同的重载签名(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 });

6. 函数返回值类型和参数类型

可使用内置工具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]

六、TypeScript的this类型

在没有对ts配置的情况下,this的使用可能存在隐患

可在tsconfig.json中进行配置

1. 函数中this默认类型

在没有指定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"
}

2. 进行tsconfig.json配置

在设置了noImplicitThis为true时, TypeScript会根据上下文推导this,但是在不能正确推导时,就会报错,需要明确的指定this

如果整个项目不想指定this类型,那么要设置为 noImplicitThis:false,注释可能没用

3. 指定函数中this的类型

函数的第一个参数类型

函数的第一个参数类型

  • 函数的第一个参数可以根据该函数之后被调用的情况
    • 用于声明this的类型(名词必须叫this)
  • 在后续调用函数传入参数时,从第二个参数开始传递的,this参数会在编译后被抹除 
// 第一个参数是this, 第二个参数开始才是函数的参数
function foo(this: { name: string }, info: {name: string}) {console.log(this, info)
}foo.call({ name: "why" }, { name: "kobe" })

4. this相关的内置工具 

Typescript 提供了一些工具类型来辅助进行常见的类型转换,这些类型全局可用

ThisParameterType

用于提取一个函数类型中的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的类型

OmitThisParameter

用于移除一个函数类型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 

ThisType

这个类型不返回一个转换过的类型,它被用作标记一个上下文的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 } 李四

七、TypeScript的类

1. 类的封装 ( 定义 )

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);

2. 类的继承

// 父类
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~

3. 类的多态

// 父类
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()]);

4. 类的成员修饰符 

public 

class Person {//  默认就是publicname: string;public name2: string;
}

private 

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');

protected

class Person {protected name: string;
}class Student extends Person {getName() {// 可以直接访问父类中的namereturn this.name;}
}const s = new Student();
s.getName();
// 外面直接访问不了
// s.name

5. 只读属性 readonly

class Person {// 1.只读属性可以在构造函数中赋值,赋值之后就不能再更改了// 2.属性本身不能进行修改,但如果是对象类型,那么对象中的属性是可以修改的readonly name: string;constructor(name: string) {this.name = name;}
}const p = new Person('star');// 不能修改
// p.name = '132';

6. getter - setter

私有属性是不能直接访问的,或者某些属性想要监听它的获取(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: 年龄不合法

7. 参数属性

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

8. 类的静态成员 

class Person {// 静态属性static age: number = 18;// 静态方法static eating() {console.log('eating');}
}// 可直接通过类名访问
console.log(Person.name);
Person.eating();

9. abstract 抽象类

abstract用来定于抽象类和抽象方法

继承是多态使用的前提

  • 所以在定义很多通用的调用接口时, 通常会让调用者传入父类,通过多态来实现更加灵活的调用方式。
  • 但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,可以定义为抽象方法

抽象方法

  • 在TypeScript中没有具体实现的方法(没有方法体),就是抽象方法
  • 抽象方法,必须存在于抽象类中
  • 抽象类是使用abstract声明的类

抽象类有如下的特点:

  • 抽象类是不能被实例的话(也就是不能通过new创建)
  • 抽象方法必须被子类实现,否则该类必须是一个抽象类
  • 有抽象方法的类,必须是一个抽象类
  • 抽象类可以包含抽象方法,也可以包含有实现体的方法
/*** 抽象类中的抽象方法,必须在子类中实现,否则会报错* 抽象类不能直接被实例化,只能被继承,抽象类中的抽象方法不能包含具体实现,只能用于接口* 不能用于实例,抽象类中的抽象方法不能被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 螺旋踢

10. 鸭子🦆类型 

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,小红}
});

11. 类的类型

类本身也是可以作为一种数据类型的

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

八、TypeScript的接口

1. 声明对象类型

type

// 通过类型 (type) 来声明对象类型
type InfoType = {name: string;age: 18;
};
const info: InfoType = {name: 'John',age: 18
};

interface

/*** 另外一种方式声明对象类型,使用 interface* interface 用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法* 同时 interface 也可以当成类型声明去使用,可以定义可选属性,也可以定义只读属性*/
interface IInfoType2 {readonly name: string;age?: number;
}
const info2: IInfoType2 = {name: 'John'
};

interface和type区别

区别一 : 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根本区别 :

  • interface 接口名可以重复,会合并。 
  • type 定义的类型名,不可重复,会报错

2. 对象类型的属性修饰符

3. 索引签名

不能提前知道一个类型里的所有属性的名字,但是知道这些值的特征

这种情况,可以用一个索引签名 (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

4. 函数类型

// 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));

5. 接口的继承

接口是支持多继承的(类不支持多继承

interface Ieat {eating: () => void;
}
interface Idrink {drink: () => void;
}// 可以多继承
interface action extends Ieat, Idrink {sleep: () => void;
}const person: action = {// 都要实现eating: () => {},drink: () => {},sleep: () => {}
};

6. 接口的实现

类可以实现多个接口

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();

7. 接口的合并

interface IPerson {eating: () => void;
}
interface IPerson {drink: () => void;
}// 相同的接口名会合并成一个接口
const p: IPerson = {// 都要实现eating: () => {},drink: () => {}
};

8. 抽象类和接口的区别 


九、TypeScript的泛型

1. 泛型的基本使用

  • 在定义函数时,不决定参数的类型
  • 是让程序员决定参数的类型,通过传入不同的参数来决定
  • 格式: function 函数名<T>(参数:T):T{}
  • <T> :传递的类型参数
  • T相当于一个变量,用于记录传入的参数的类型,在整个函数执行过程中,T的类型是不变的
// 封装一个函数,传入一个参数,并且返回这个参数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

2. 泛型的练习

/*** 通过泛型来约束,传入的参数和返回值的类型* @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

3. 泛型接收多个参数 

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]

4. 泛型接口 

/*** 泛型* 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: '北京'
};

5. 泛型类

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'];

6. 泛型的类型约束

例子一 : 简单约束

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 {};

7. 映射类型

一个类型需要基于另外一个类型,但是你又不想拷贝一份,这个时候可以考虑使用映射类型

大部分内置的工具都是通过映射类型来实现的
大多数类型体操的题目也是通过映射类型完成的 

映射类型建立在索引签名的语法上

  • 映射类型,就是使用了 PropertyKeys 联合类型的泛型
  • 其中 PropertyKeys 多是通过 keyof 创建,然后循环遍历键名创建一个类型

基本使用

// 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
};

映射修饰符

修饰符 : 

  • 一个是 readonly
    • 用于设置属性只读
  • 一个是 ?
    • 用于设置属性可选 
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>;

8. 条件类型

基本使用

/*** 条件类型例子一*/
// 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[]

十、TypeScript知识扩展

1. TypeScript模块化

主要有两种 :

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
};

2. TypeScript的命名空间

了解即可,直接使用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';

3. 类型的查找

内置类型声明

概念

内置类型声明是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件

内置类型声明通常在我们安装typescript的环境中会带有的

  • 包括比如Function、String、Math、Date等内置类型
  • 也包括运行环境中的DOM API,比如Window、Document等

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

4. declare

declare 声明模块

declare 声明文件

declare 声明命名空间

5. tsconfig.json

JavaScript 项目可以使用 jsconfig.json 文件,它的作用与 tsconfig.json 基本相同,只是默认启用了一些 JavaScript 相关的编译选项

作用

tsconfig.json文件有两个作用:

  • 作用一(主要的作用):
    • 让TypeScript Compiler在编译的时候,知道如何去编译TypeScript代码和进行类型检测
    • 比如是否允许不明确的this选项( noImplicitThis ),是否允许隐式的any类型 ( noImplicitAny )
    • 将TypeScript代码编译成什么版本的JavaScript代码 ( target )
  • 作用二:
    • 让编辑器(比如VSCode)可以按照正确的方式识别TypeScript代码
    • 对于哪些语法进行提示、类型错误检测等等

使用

手动使用
  • 在调用 tsc 命令并且没有其它输入文件参数时,编译器将由当前目录开始向父级目录寻找包含 tsconfig 文件的目录

  • 调用 tsc 命令并且没有其他输入文件参数,可以使用 --project (或者只是 -p)的命令行选项来指定包含了 tsconfig.json 的目录

  • 当命令行中指定了输入文件参数, tsconfig.json 文件会被忽略
使用webpack

webpack中使用ts-loader进行打包时,也会自动读取tsconfig文件,根据配置编译TypeScript代码

选项

顶层选项

compilerOptions选项


十一、内置工具和类型体操 

 练习       练习+答案

1. Partial<Type>

用于构造一个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>;

2. Required<Type>

用于构造一个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>;

3. Readonly<Type>

用于构造一个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>;

4. Record<Keys, Type>

用于构造一个对象类型,它所有的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('深圳');}}
};

5. Pick<Type, Keys>

用于构造一个类型,它是从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'>;

6. Omit<Type, Keys> 

用于构造一个类型,它是从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'>;

7. Exclude<UnionType, ExcludedMembers> 

用于构造一个类型,它是从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'>;

8. Extract<Type, Union> 

用于构造一个类型,它是从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'>;

9. NonNullable<Type>

 用于构造一个类型,这个类型从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>;

10. InstanceType<Type> 

用于构造一个由所有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小时内删除。

标签:基础   TS
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23