Hello, 各位勇敢的小伙伴, 你们好, 我是大家的嘴强王者小五, 身体健康, 脑子没病.html
本人有丰富的脱发技巧, 能让你一跃成为资深大咖.前端
一看就会一写就废是本人的主旨, 菜到抠脚是本人的特色, 卑微中透着一丝丝刚强, 傻人有傻福是对我最大的安慰.react
欢迎来到
小五
的随笔系列
之TypeScript 学习笔记
.typescript
本文初衷为笔者在使用一段时间 TS 后, 对所学所想作一个记录. 文章内容较为基础, 适合做为入门级教程学习; 且涵盖内容并不全面, 缺失包含类在内的诸多内容. 若是各位看官不介意以上几点, 欢迎与笔者一同进入这严谨却又妙不可言的奇幻旅途.数组
let flag: boolean = false;
let num: number = 10;
👺 扩展: number类型还支持 二进制(0b开头)、 八进制(0o开头)、 十六进制(0x开头) 字面量. 运算或输出时会转换为对应的十进制.app
let num02: number = 0b1010; let num08: number = 0o744; let num16: number = 0xf00d;
let str: string = '黄刀小五';
数组共有两种定义方式: Array<T>
或 T[]
(T为该数组每项的元素类型)函数
let numArr: number[] = [1, 2, 3]; let numArr: Array<number> = [1, 2, 3];
👺 特色: <数组>, <长度已知>, <每项元素类型不尽相同> post
😼 就是一个给每项都定义数据类型的数组学习
let tuple: [string, string, number, boolean?] = ['No.1', '黄刀小五', 18];
🦥 应用:ui
import { useState } from 'react'; const [loading, setLoading] = useState<boolean>(false); // 这里类型可省略, 详见后文类型推断
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 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
🦥 应用:
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'
let value: any;
😼 tips: 食物链最顶端, 应尽可能减小 any 的使用.
let value: unknown;
unknown 表示未知类型, 与 any 不一样的是没法对 unknown 类型执行任何操做. 仔细思考下 <any: 任意类型>、 <unknown: 未知类型> 的区别.
👉 以 number 的 toFixed()
方法举例:
toFixed()
方法, 故不能使用;toFixed()
方法的我就是 number 类型;😼 tips: 如想使用 any, 请先考虑是否可用 unknown 代替. (搭配后文类型保护可安心食用)
经常使用于没有具体返回值的函数
const fn = (str: string): void => { // 执行xxx事件 }
let u: undefined = undefined; let n: null = null;
这两个不知道要说点啥, 就补充如下两点吧 (然而与 TS 没啥关系)
Number(undefined) => NaN
, Number(null) => 0
const fn = (arg?: string) => { ... }
arg的类型是 string | undefined
let n: never;
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 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 Key = string | number; // 表明 string 或 number 类型
😼 tips: 联合类型能够用来声明具体的值
type Status = 'active' | 'inactive';
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: 经常使用于第三方库类型没法引用的场合
👺 配合类型断言 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
使用呦
👺 用来获取变量的类型
let str = '黄刀小五'; type Str = typeof str; // type Str = string let user = { name: '黄刀小五' } type User = typeof user; // type User = { name: string; }
👺 用来提取对象类型的 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 👇
👺 将对象类型的属性均变为只读
type Readonly<T> = { readonly [K in keyof T]: T[K]; };
interface Person { readonly name: string; age: number; desc?: string; } let person: Readonly<Person> = { name: '黄刀小五', age: 18, } person.age = 19; // ❎ error (没法分配到 "age" ,由于它是只读属性)
👺 将对象类型的属性均变为可选
type Partial<T> = { [K in keyof T]?: T[K]; };
interface Person { readonly name: string; age: number; desc?: string; } let person: Partial<Person> = {}; // 此时全部属性均为可选
👺 将对象类型的属性均变为必须
type Required<T> = { [K in keyof T]-?: T[K]; };
😼 tips: -? 表示去掉可选符号 ?, 此符号 (-) 一样可用于其它位置, 如: -readonly 可去掉只读属性等.
interface Person { readonly name: string; age: number; desc?: string; } let person: Required<Person>> = { name: '黄刀小五', age: 18, desc: '基于搜索引擎的复制粘贴工程狮', }; // 此时全部属性均为必须
👺 将一个类型的全部属性值映射到另外一个类型上并建立一个新的类型
type Record<K extends string, T> = { [P in K]: T; };
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 };
👺 从 T 中提取 U
type Extract<T, U> = T extends U ? T : never;
type Student = '二狗子' | '如花'; type Teacher = '黄刀小五' | '二狗子'; type Trainee = Extract<Student, Teacher>; // 输出 '二狗子' /* Extract<Student, Teacher> 至关于👇 */ '二狗子' in Teacher -> '二狗子' '如花' in Teacher -> never '二狗子' | never = '二狗子'
👺 从 T 中排除 U
type Exclude<T, U> = T extends U ? never : T;
type Student = '二狗子' | '如花'; type Teacher = '黄刀小五' | '二狗子'; type OnlyStudent = Exclude<Student, Teacher>; // 输出 '如花' /* Exclude<Student, Teacher> 至关于👇 */ '二狗子' in Teacher -> never '如花' in Teacher -> '如花' '如花' | never = '如花'
👺 挑选对象中的部分属性
type Pick<T, K extends keyof T> = { [P in K]: T[P]; }
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; }
👺 忽略对象中的部分属性
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
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; }
👺 获取方法的返回值类型
type ReturnType<T> = T extends ( ...args: any[] ) => infer R ? R : any;
-> infer 配合 extends 使用, 用于推断函数的返回值类型
const getName = (name: string) => name; type ReturnGetName = ReturnType<typeof getName>; // string
👺 获取方法的参数类型
type Parameters<T> = T extends ( ...args: infer P ) => any ? P : never;
-> 这里 infer 用于推断函数的参数类型, Parameters 返回格式为元组.
const getName = (name: string) => name; type ParamGetName = Parameters<typeof getName>; // [name: string]
👺 排除 null 和 undefined 类型
type NonNullable<T> = T extends null | undefined ? never : T;
type NewPerson = Person | null; let person: NonNullable<NewPerson> = null; // ❎ error (不能将类型“null”分配给类型“Person”)
结合文章内容, 你们可根据实际需求封装更多的语法糖, 便于在项目中使用. 若想更细致的学习 TS, 这里推荐一个博主 阿宝哥, 其发表了不少关于 TS 的文章, 并针对 TS 的某一特征作了详细的讲解.