Hello, 各位怯懦的小伙伴, 大家好, 我是你们的嘴强王者小五, 身体健康, 脑子没病.
自己有丰盛的脱发技巧, 能让你一跃成为资深大咖.
一看就会一写就废是自己的宗旨, 菜到抠脚是自己的特点, 低微中透着一丝丝坚强, 傻人有傻福是对我最大的刺激.
欢送来到
小五
的随笔系列
之TypeScript 学习笔记
.
导读
本文初衷为笔者在应用一段时间 TS 后, 对所学所想做一个记录. 文章内容较为根底, 适宜作为入门级教程学习; 且涵盖内容并不全面, 缺失蕴含类在内的诸多内容. 如果各位看官不介意以上几点, 欢送与笔者一起进入这谨严却又妙不可言的奇幻旅途.
根底类型
Boolean – 布尔
let flag: boolean = false;
Number – 数字
let num: number = 10;
👺 扩大: number 类型还反对 二进制 (0b 结尾)、 八进制 (0o 结尾)、 十六进制(0x 结尾) 字面量. 运算或输入时会转换为对应的十进制.
let num02: number = 0b1010;
let num08: number = 0o744;
let num16: number = 0xf00d;
String – 字符串
let str: string = '黄刀小五';
Array – 数组
数组共有两种定义形式: Array<T>
或 T[]
(T 为该数组每项的元素类型)
let numArr: number[] = [1, 2, 3];
let numArr: Array<number> = [1, 2, 3];
Tuple – 元组
👺 特点: < 数组 >, < 长度已知 >, < 每项元素类型不尽相同 >
😼 就是一个给每项都定义数据类型的数组
let tuple: [string, string, number, boolean?] = ['No.1', '黄刀小五', 18];
🦥 利用:
- React Hook 的 useState
import {useState} from 'react';
const [loading, setLoading] = useState<boolean>(false); // 这里类型可省略, 详见后文类型推断
- csv 数据格式
type Touple = [string, number, string];
let csvData: Touple[] = [['黄刀小五', 18, '男'], ['二狗子', 14, '男']];
👺 扩大: 元组越界
let tuple: [number, string] = [1, '黄刀小五'];
tuple.push(2); // ✅ right 元组越界时, 该项类型相当于对元组的各项类型做联结
/* 过程如下, 看不懂的看官莫慌, 请先看后文 */
type UnionType<T> = T extends (infer P)[] ? P : never;
type TupleType = UnionType<typeof tuple>; // number | string
Enum – 枚举
enum Active {
inactive,
active,
}
enum Fruit {
apple = 'apple',
orange = 'orange',
banana = 'banana',
}
若枚举类型未指定值或指定的值为 number 类型, 如上述 Active, 可对其进行双向取值: Active[0]
、Active['active']
, 其映射如下【👇】
{
0: 'inactive',
1: 'active',
inactive: 0,
active: 1,
}
可对枚举的其中一项进行指定数值(通常为第一项), 其余项会程序递增
enum Type {
active = 1,
inactive,
}
{ // 对应映射为
1: 'active',
2: 'inactive',
active: 1,
inactive: 2,
}
😼 tips: 倡议采纳赋值模式的枚举. 可读性更高, 如上述 Fruit
🦥 利用:
- 联合 switch … case 应用:
enum ActionType {
ADD = 'ADD',
EDIT = 'EDIT',
DELETE = 'DELETE',
}
const reducer = (type: ActionType) => {switch(type) {
case ActionType.ADD:
// xxx
break
case ActionType.EDIT:
// xxx
break
...
}
}
reducer(ActionType.ADD); // ✅ right
let params = 'ADD';
reducer(params); // ❎ error (类型“string”的参数不能赋给类型“ActionType”的参数)
/* 😼 tips: 非赋值模式大家可自行尝试 */
- 定义类型映射 或 定义一组常量
enum Status { // 类型映射
unprocessed = 1, // 未解决
processed, // 已解决
refused, // 已回绝
}
enum Fruit { // 常量
apple = '苹果',
orange = '橘子',
banana = '香蕉',
}
👺 扩大: keyof typeof Enum
, 可将枚举类型转换为联结类型; 置信我, 你会用到的😏
enum ActionType {
ADD,
EDIT,
DELETE,
}
type ActionTypeConst = keyof typeof ActionType // 'ADD' | 'EDIT' | 'DELETE'
Any – 任意类型
let value: any;
😼 tips: 食物链最顶端, 应尽量减少 any 的应用.
Unknown – 未知类型
let value: unknown;
unknown 示意未知类型, 与 any 不同的是无奈对 unknown 类型执行任何操作. 认真思考下 <any: 任意类型 >、<unknown: 未知类型 > 的区别.
👉 以 number 的 toFixed()
办法举例:
- unknown 代表我不晓得这是什么类型, 而只有 number 有
toFixed()
办法, 故不能应用; - 而 any 代表我能够是任意类型, 此时调用
toFixed()
办法的我就是 number 类型;
😼 tips: 如想应用 any, 请先思考是否可用 unknown 代替. (搭配后文类型爱护可安心食用)
Void – 无类型
罕用于没有具体返回值的函数
const fn = (str: string): void => {// 执行 xxx 事件}
Null 和 Undefined
let u: undefined = undefined;
let n: null = null;
这两个不晓得要说点啥, 就补充以下两点吧 (然而与 TS 没啥关系)
Number(undefined) => NaN
,Number(null) => 0
const fn = (arg?: string) => {...}
arg 的类型是string | undefined
Never – 永不返回
let n: never;
- never 示意那些永远不存在的值, 当咱们不想捕捉以后值时, 可应用 never;
- 任何类型都不可赋值给 never, 包含 any;
never | T = T
此个性可用来过滤掉不须要的值;
🦥 利用:
- 做类型查看
type Type = string | number;
const fn = (arg: Type) => {if (typeof arg === 'string') {...}
else if (typeof arg === 'number') {...}
else {const check: never = arg;}
}
如上, 此时永远不会走到 else, 上面咱们做以下操作:
- type Type = string | number;
+ type Type = string | number | boolean;
* 此时 else 中的 check 会报错 (不能将类型“boolean”调配给类型“never”)
类型推论
如果没有指定类型, TS 会依据类型推论推断出一个类型.
let val; // 推论成: let val: any;
let num = 10; // 推论成: let num: number = 10;
num = '黄刀小五'; // ❎ error (不能将类型“string”调配给类型“number”)
🦥 利用: < 繁多动态类型 - 如上述 num>
、< 函数返回值 - 个别状况均可正确推断其返回值类型, 不必额定指定 >
、< 循环中的子元素 >
😼 tips: 如果 TS 能正确推断出其类型, 咱们可采纳类型推论而不用定义类型.
类型断言
类型断言用来通知编译器“我晓得本人在干什么”, 有 尖括号 和 as 两种写法. 在 $tsx$ 语法中, 只反对 as.
* 😼 tips: 上面咱们应用数组来举个例子, 理论场景中应应用元组.
type Key = string | number;
let arr: Key[] = ['黄刀小五', 18];
// 不能将类型“Key”调配给类型“string”- let name = arr[0];
- console.log(name.length);
// 应用类型断言
+ let name = arr[0] as string;
+ let name = <string>arr[0];
+ console.log(name.length);
Interface – 接口
接口用来定义对象的类型
interface User {
readonly id: number; // 只读属性, 不可批改
name: string; // 必须属性
desc?: string; // 可选属性
say?: (name: string) => void; // 办法
}
const say = (name: string) => {...}
let user: User = {
id: 1,
name: '黄刀小五',
say,
}
user.id = 2; // ❎ error (无奈调配到 "id",因为它是只读属性)
索引签名 – 使接口更加灵便
interface User {[key: string]: string; // 示意 key 为 string, value 为 string 的任意属性
}
let user:User = {
name: '黄刀小五',
desc: '菜鸡前端一只',
}
😼 tips: 所有成员都必须合乎索引签名的特色, 索引签名参数类型必须为 string | number
interface User {[key: string]: string;
age: number; // ❎ error (类型“number”的属性“age”不能赋给字符串索引类型“string”。)
}
接口合并
interface User {name: string;}
interface User {age: number;}
/* 两者会合并为👇 */
interface User {
name: string;
age: number;
}
接口继承
关键字: extends
interface Person {name: string;}
interface User {age: number;}
interface Student extends Person, User {desc: string;}
/* Student 接口格局如下👇 */
interface Student {
name: string;
age: number;
desc: string;
}
😼 tips: 接口和类型别名均可应用的状况下应用接口
🤔 思考: ❓ 如何定义一个树形构造
interface Tree {
key: number;
value: string;
child?: Tree[];}
let tree: Tree[] = [];
类型别名
顾名思义, 为该类型取一个新的名字
type Key = string | number;
与 Interface 比照
- type 不反对继承和申明合并, interface 能够, 参考上文;
- type 更为通用, 右侧能够是任意类型, interface 次要用于定义对象;
- type 和 interface 均可应用的状况下应用 interface;
联结类型与穿插类型
- 联结类型 (A | B)
type Key = string | number; // 代表 string 或 number 类型
😼 tips: 联结类型能够用来申明具体的值
type Status = 'active' | 'inactive';
- 穿插类型 (A & B)
interface User {name: string;}
interface Student {age: number;}
type Blogger = User & Student;
/* Blogger 类型格局如下👇 */
{
name: string;
age: number;
}
😼 tips: 两个根底类型做穿插, 会生成 never 类型
type Key = string & number; // never, 没有类型能够满足即是 string 又是 number
类型查找
类型查找能够提取对象类型上某一属性的类型
interface Person {
User: {
name: string;
age?: number;
}
}
type User = Person['User']
😼 tips: 罕用于第三方库类型无奈援用的场合
罕用关键字
const
👺 配合类型断言 as 来申明常量
type Status = 'active' | 'inactive';
const fn = (status: Status) => {...}
- let status = 'active'; // 此时 'active' 被解析为字符串而十分量
- fn(status); // ❎ error (类型“string”的参数不能赋给类型“Status”的参数)
* 以下 3 种等价, 均可将 'active' 解析为常量
+ let status = 'active' as const;
+ const status = 'active';
+ let status: Status = 'active';
+ fn(status); // ✅ right
😼 tips: 第三方插件定义的常量常常须要配合 as const
应用呦
typeof
👺 用来获取变量的类型
let str = '黄刀小五';
type Str = typeof str; // type Str = string
let user = {name: '黄刀小五'}
type User = typeof user; // type User = {name: string;}
keyof
👺 用来提取对象类型的 key 值
interface User {
name: string;
age?: number;
}
type Key = keyof User; // type Key = 'name' | 'age'
类型爱护
❓ 为什么须要类型爱护
const fn = (value: string | number) => {if (value.length) {...} // ❎ error (类型“string | number”上不存在属性“length”)
}
上述代码, 如果想在 value: string
时执行一段逻辑要怎么办呢?
此时就须要类型爱护了, 应用类型爱护后, 以后代码段会依照当时所指定的类型执行.
👺 typeof
const fn = (value: string | number) => {if (typeof value === 'string') { // 利用 typeof 限度 value 的类型为 string
console.log(value.length);
}
}
👺 is
const isString = (x: unknown): x is string => {return typeof x === 'string';}
const fn = (value: string | number) => {if (isString(value)) console.log(value.length); // value is string
}
👺 in
示意某属性是否在以后对象中存在
interface User {
name: string;
age?: number;
}
const fn = (args: User) => {if ('age' in args) {...}
else {...}
}
fn({name: '黄刀小五', age: 18}); // 执行 if 语句
fn({name: '黄刀小五'}); // 执行 else 语句
泛型
👺 作用: 用于做代码复用
先来看个例子
const fn = (value: number): number => value;
🤔 思考 ❓ 如果此时我想传入一个 string 类型并返回一个 string 类型呢
const fn = <T>(value: T): T => value;
fn<string>('黄刀小五'); // const fn: <string>(value: string) => string
* 😼 tips: 可依据 类型推断 推断出其为 string 类型, 而不必特意指定 -> `fn('黄刀小五');`
以上, 一个泛型就实现好了, 其能够将类型作为一个参数, 在调用时传入类型进行指定.
👺 留神: 在 $tsx$ 中, <T>
会被解析成标签, 可应用上面的写法:
const fn = <T extends {}>(value: T): T => value; // extends 也可用于放大 T 的范畴
const fn = <T,>(value: T): T => value;
- 传入多个类型:
const fn = <T, U>(type: T, value: U): U => {...};
fn<boolean, string>(true, '黄刀小五');
泛型的作用相当之广, 如定义一个接口:
interface Teacher<T> {
readonly id: number;
name: string;
student?: T[];}
interface Student {
readonly id: number;
name: string;
age?: number;
}
let teahcer: Teacher<Student> = {
id: 1,
name: '黄刀小五',
student: [{
id: 1001,
name: '二狗子',
age: 14,
}]
}
罕用语法糖
上面咱们来用上述常识实现下 TS 中封装好的语法糖 (舒适提醒: 倡议先搞懂上文, 至多搞懂泛型在浏览上面内容)
look down 👇
Readonly
👺 将对象类型的属性均变为只读
- 代码实现
type Readonly<T> = {readonly [K in keyof T]: T[K];
};
- Demo
interface Person {
readonly name: string;
age: number;
desc?: string;
}
let person: Readonly<Person> = {
name: '黄刀小五',
age: 18,
}
person.age = 19; // ❎ error (无奈调配到 "age",因为它是只读属性)
Partial
👺 将对象类型的属性均变为可选
- 代码实现
type Partial<T> = {[K in keyof T]?: T[K];
};
- Demo
interface Person {
readonly name: string;
age: number;
desc?: string;
}
let person: Partial<Person> = {}; // 此时所有属性均为可选
Required
👺 将对象类型的属性均变为必须
- 代码实现
type Required<T> = {[K in keyof T]-?: T[K];
};
😼 tips: -? 示意去掉可选符号 ?, 此符号 (–) 同样可用于其它地位, 如: -readonly 可去掉只读属性等.
- Demo
interface Person {
readonly name: string;
age: number;
desc?: string;
}
let person: Required<Person>> = {
name: '黄刀小五',
age: 18,
desc: '基于搜索引擎的复制粘贴工程狮',
}; // 此时所有属性均为必须
Record
👺 将一个类型的所有属性值映射到另一个类型上并创立一个新的类型
- 代码实现
type Record<K extends string, T> = {[P in K]: T;
};
- Demo
interface Person {
readonly name: string;
age: number;
desc?: string;
}
type Kind = 'teacher' | 'student';
let person: Record<Kind, Person> = {
teacher: {
name: '黄刀小五',
age: 18,
},
student: {
name: '二狗子',
age: 14,
}
}; // 将 Person 类型映射到 Kind 类型中
/* Record<Kind, Person> 相当于👇 */
type NewPerson = {[key in Kind]: Person
};
Extract
👺 从 T 中提取 U
- 代码实现
type Extract<T, U> = T extends U ? T : never;
- Demo
type Student = '二狗子' | '如花';
type Teacher = '黄刀小五' | '二狗子';
type Trainee = Extract<Student, Teacher>; // 输入 '二狗子'
/* Extract<Student, Teacher> 相当于👇 */
'二狗子' in Teacher -> '二狗子'
'如花' in Teacher -> never
'二狗子' | never = '二狗子'
Exclude
👺 从 T 中排除 U
- 代码实现
type Exclude<T, U> = T extends U ? never : T;
- Demo
type Student = '二狗子' | '如花';
type Teacher = '黄刀小五' | '二狗子';
type OnlyStudent = Exclude<Student, Teacher>; // 输入 '如花'
/* Exclude<Student, Teacher> 相当于👇 */
'二狗子' in Teacher -> never
'如花' in Teacher -> '如花'
'如花' | never = '如花'
Pick
👺 筛选对象中的局部属性
- 代码实现
type Pick<T, K extends keyof T> = {[P in K]: T[P];
}
- Demo
interface Person {
readonly name: string;
age: number;
desc?: string;
}
let person: Pick<Person, 'age' | 'desc'> = {age: 18,};
/* Pick<Person, 'age' | 'desc'> 相当于👇 */
interface Person {
age: number;
desc?: string;
}
Omit
👺 疏忽对象中的局部属性
- 代码实现
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
- Demo
interface Person {
readonly name: string;
age: number;
desc?: string;
}
let person: Omit<Person, 'name'> = {age: 18,};
/* Omit<Person, 'name'> 相当于👇 */
interface Person {
age: number;
desc?: string;
}
ReturnType
👺 获取办法的返回值类型
- 代码实现
type ReturnType<T> = T extends (...args: any[]
) => infer R ? R : any;
-> infer 配合 extends 应用, 用于推断函数的返回值类型
- Demo
const getName = (name: string) => name;
type ReturnGetName = ReturnType<typeof getName>; // string
Parameters
👺 获取办法的参数类型
- 代码实现
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
-> 这里 infer 用于推断函数的参数类型, Parameters 返回格局为元组.
- Demo
const getName = (name: string) => name;
type ParamGetName = Parameters<typeof getName>; // [name: string]
NonNullable
👺 排除 null 和 undefined 类型
- 代码实现
type NonNullable<T> = T extends null | undefined ? never : T;
- Demo
type NewPerson = Person | null;
let person: NonNullable<NewPerson> = null; // ❎ error (不能将类型“null”调配给类型“Person”)
结束语
联合文章内容, 大家可依据理论需要封装更多的语法糖, 便于在我的项目中应用. 若想更粗疏的学习 TS, 这里举荐一个博主 阿宝哥, 其发表了很多对于 TS 的文章, 并针对 TS 的某一特色做了具体的解说.
参考🔗链接
【TypeScript 文档】
【FESKY】联合实例学习 Typescript
【刘哇勇】TypeScript infer
关键字