[深入25] Typescript

766 阅读23分钟

导航

[react] Hooks

[封装01-设计模式] 设计原则 和 工厂模式(简单抽象方法) 适配器模式 装饰器模式
[封装02-设计模式] 命令模式 享元模式 组合模式 代理模式

[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend

[源码-vue06] Vue.nextTick 和 vm.$nextTick

[源码-react01] ReactDOM.render01
[源码-react02] 手写hook调度-useState实现

[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI

[数据结构和算法01] 二分查找和排序

[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例
[深入24] Fiber
[深入25] Typescript

[前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
[前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
[前端学java03-SpringBoot实战] lombok,日志,部署
[前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
[前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能
[前端学java06-SpringBoot实战] 注入 + Swagger2 3.0 + 单元测试JUnit5
[前端学java07-SpringBoot实战] IOC扫描器 + 事务 + Jackson
[前端学java08-SpringBoot实战总结1-7] 阶段性总结
[前端学java09-SpringBoot实战] 多模块配置 + Mybatis-plus + 单多模块打包部署
[前端学java10-SpringBoot实战] bean赋值转换 + 参数校验 + 全局异常处理
[前端学java11-SpringSecurity] 配置 + 内存 + 数据库 = 三种方式实现RBAC
[前端学java12-SpringSecurity] JWT
[前端学java13-SpringCloud] Eureka + RestTemplate + Zuul + Ribbon

(一) 前置知识

(1) 一些单词

vite 快点
declare 声明
interop 相互操作 // esModuleInterop=true 表示commonjs模块可以使用import和require两种语法

primavera 青年队 春天
survey 调研 调查 测量
preserve 保留 保护

implicit 隐式 // noImplicitAny 不包含隐式的any,即any报错
synthetic 合成的 人造的 // React.BaseSyntheticEvent 合成事件
acquisition 收购 得到 // typeAcquisition 如何得到.d.ts类型

(2) 如何安装typescript

  • cnpm install typescript -g

(3) tsc 命令

  • tsc命令官方文档
  • 当安装了typescript后,就可以使用 tsc 命令了
  • 命令具体分了很多类,比如 compilerOptoin, buildOption, watchOption
tsc index.ts // 编译由tsconfig.json定义的项目中的index.ts文件
tsc src/*.ts // 编译所有scr中的ts文件
tsc --project tsconfig2.json // 指定具体的tsconfig.json文件中的配置来编译

compilerOption
tsc --init // 初始化typescript项目,并生成一个tsconfig.json文件
tsc --all // 显示所有编译器命令选项
tsc --version // 打印编译器的版本信息

(4) keyof索引类型查询,T[K]索引访问

  • keyof
    • 索引类型查询 - 操作符
    • 对于任何 ( 类型T ),( keyof T ) 返回T上已知的公共 ( 属性名 ) 的 ( 联合类型 )
  • T[K]
    • 索引访问 - 操作符
    • 返回接口中对应的 keyof T 索引对应的类型
interface Person {
    name: string;
    age: number;
    location?: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof [];        // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string | number

(5) typeof类型保护,instanceof类型保护

  • typeof
    • 类型保护
    • typeof 特点
      • typeof 在 JS 中可以判断数据类型
      • typeof 在 TS 中用来获取 ( 变量的声明类型 ),主要针对 ( 对象 ) 和 ( 函数 )
  • instanceof
    • 类型保护
    • instanceof 类型保护是通过 ( 构造函数 ) 来细化类型的一种方式
    • instanceof右侧要求是一个构造函数,ts会将其细化为
      • 1.此构造函数的prototype属性的类型,如果它的类型不是any
      • 2.构造签名所返回的类型的联合
typeof
---
1. typeof可以判断数据的类型
2. 针对 ( 对象 ) 和 ( 数组 ) 可以 ( 可以获取变量的声明类型 )


3. 对象
interface TestObj {
  name: string;
}
const testObj: TestObj = {
  name: 'woow_wu7'
}
type TestObj2 = typeof testObj // TestObj2的类型就是 -> TestObj


4. 函数
function go(address: string): string[] {
  return [address]
}
type Go = typeof go // Go的类型就是 -> (address: string) => string[]

(6) 类型谓词 is

  • 1.什么是 ( 类型保护 ) ?
    • 要定义一个类型保护,我们只需定义一个函数,( 该函数的返回值 ) 是一个 ( 类型谓词 is )
  • 2.类型谓词的语法
    • parameterName is Type
    • parameterName必须是来自于当前函数签名里的一个参数名 - 即必须是函数的参数
    1. 一些is使用的注意点

(7) in

  • 2021/08/31更新
  • in运算符作用主要有两个
    • 判断对象中是否存在某个key注意包括 ( 自身属性 ) 和 ( 继承属性 )
    • 可以用来遍历枚举类型
in
---

type T = 'name' | 'age';
type Obj = { 
  [k in T]: any
}
// 1. in 用来遍历枚举类型 -> type Obj = { name: any, age: any}

const obj: Obj = {
  name: 'woow_wu7',
  age: 20
}
console.log('name' in obj)

// 2. in 用来判断 obj 对象中是否存在 name 属性,缺点是不能识别 ( 自身属性 ) 还是 ( 继承属性 )

var obj = {};
'toString' in obj // true,即非自身属性时,继承的属性也会返回true

(8) as - 类型断言

  • 类型断言可以用来 ( 手动指定一个值的类型 ),语法是 ( 值 as 类型 ) 或者 ( <类型>值 )
  • tsx中不能使用 <类型>值 的方式,只能只用 值 as 类型 的方式
  • 类型断言的作用
    • 将一个 ( 联合类型 ) 断言为 ( 其中一个类型 )
    • 将一个 ( 父类 ) 断言为 ( 更加具体的子类 )
    • 将 ( 任何一个类型 ) 断言为 ( any )
    • 将 ( any ) 断言为 ( 一个具体的类型 )
  • 类型断言的注意点
    • 类型断言只能欺骗ts,但是不能避免运行时的错误

(9) const断言 - value as const

  • as const
    • 称为const断言,表示使用最窄或具体的类型,如果不选择断言,则可能因为更广的范围而产生一个错误推断
  • 特点
    • 该表达式中的 ( 字面类型不应被扩展 )
    • 对象字面量获取只读属性
    • 数组字面量成为只读元组
1 对象只读属性
---
const obj = {name: 'woow_wu7'} as const
obj.name = '' // 无法分配到 "name" ,因为它是只读属性
2. 数组是只读数组
---
const arr = [1,3] as const 
arr[1]= 2 
// const arr: readonly [1, 3]
// 无法分配到 "1" ,因为它是只读属性

arr.push(1)
// 类型“readonly [1, 3]”上不存在属性“push”

(10) 非空断言 a!

  • a! 将从值域中排除 ( nullundefined )
const a: number|undefined = undefined;
const b = a!

// 结果:b的类型就是 number
// 原因:a! 非空断言表示 ( a的非空,b的类型中不再包含null和undefined)

(11) readonly 和 const 的区别

  • 两者都不能被修改
  • 变量用const,对象的属性用readonly

(12) ( 类型别名type ) 和 ( 接口interface ) 的区别

  • type
    • type类型别名会给 ( 类型起一个新名字 )
    • type可以作用于原始值,联合类型,元组,以及其他任何需要手写的类型,而instance用于对象
    • type不会新建一个类型,它创建了一个新的名字来引用那个类型
    • type也可以是泛型
  • type 和 interface 的区别
    • interface是新建一个类型,而type不会新建类型,只是给类型取了一个新的名字
    • type可以作用于原始值,联合类型,元组等其他需要手写的类型,而interface用于对象
    • type不能被 extends继承 和 implements实现

(13) 枚举

  • 枚举分类:
    • ( 数字枚举 ), ( 字符串枚举 ), ( 异构枚举-混合数字和字符串 ), ( const常量枚举 )
    • ( 外部枚举 )
  • 反向映射
    • 数字枚举成员还具有 - ( 反向映射 )
    • 其实字符串枚举也可以,不过key和value要一样
enum Good {
  name = 1,
  age = 3
}
const number = Good.name // 1
const string = Good[number] // 'name' -> 数字枚举的反向映射

解析:
1. 数字枚举成员能 - 反向映射
2. 
问题:为什么数字枚举成员能反向映射?
回答:因为 - enum枚举,既可以做为数据,也可以作为类型
扩展:enumclass 既可以做类型,也可以做数据
3.
注意区分 ( T[K] 索引访问造作符 )

(14) jsx

  • 官网链接
  • 值类型:枚举
  • 值范围:'preserve' 'react-native' 'react'
  • 含义:指定jsx代码生成,这些模式只在代码生成阶段起作用
    • 1 preserve => 生成代码中会保留jsx后续的转换操作(比如以后还可以用babel),输出文件带有 .jsx
    • 2 react => 生成 React.createElement,在使用前不需要转化了,输出文件带 .js
    • 3 react-native => 保留了所有jsx,输出文件扩展名是 .js
  • 如何配置:在tsconfig.json文件配置项compilerOptions中通过 ( jsx选项 ) 来指定
  • as
    • jsx中的断言使用 as 语法,而不是使用<foo>bar语法

(15) extends - 继承 - 实现泛型约束

  • 如何实现泛型约束?
    • 创建一个 ( 接口 - 用来描述约束条件 ) 配合 ( extends关键字 ) 来实现 ( 泛型约束 )
  • extends到底是什么?
    • 其实 extends 就是 ( 继承 ) 的关键字,比如 ( class,interface ) 的继承
  • 泛型约束 - 即对T的约束要求 - extends
    • 在 ( 泛型约束 ) 中使用 ( 类型参数 )
    • 被约束后的泛型变量不再代表任意类型,说明泛型约束针对的是泛型的类型变量T
interface Lengthwise {
  length: number;
}

// T extends Lengthwise 
// 表示将 ( 泛型类型变量T ) 约束为 ( Lengthwise ) 类型,而 Lengthwise 中具有 length 属性
// 其实就是 T 继承了 Lengthwise 接口,就具有了 interface 中的属性
// 所以下面的函数中获取 length 属性不会再报错

function loggingIdentity<T extends Lengthwise>(arg: T): T { 
  console.log(arg.length);  // Now we know it has a .length property, so no more error
  return arg;
}

(16) 泛型

  • 泛型函数
    • 泛型是函数,该函数可以 ( 适用于多个类型 ),所以叫 ( 泛形 )
    • 泛型和any的区别: 泛型不会丢失信息
    • ( 泛型函数 ) 的使用方式
      • 1.传入所有参数,包括类型参数
      • 2.( 类型推论 ),根据参数自动确定T类型
  • 泛型变量:( 泛型变量 ) 代表的是 ( 任意类型 )
  • 泛型类型 - 泛型接口,泛型接口参数
    • 泛型函数的类型
      • let myIdentity: <T>(arg: T) => T = identity;
      • let myIdentity: {<T>(arg: T): T} = identity;
    • 可以使用不同的 ( 泛型参数名 )
    • 可以创建 (泛型接口,泛型类),但是不能创建 ( 泛型枚举,泛型命令空间 )
    • 泛型接口 - ( 泛型参数 ) 还可以作为整个 ( 泛型接口的参数 )
function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}
(1)问题:
  - 上面 ( 类型变量T ) 表示任意类型,所以取arg.length报错,因为如果T是number,是没有length属性的
  
(2)如何解决:
  - 把 ( 泛型变量T ) 当作 ( 类型 ) 的一部分使用,比如 ( T[] ),从而增加灵活性
  - 具体例子如下
  
(3) 解决办法1
function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

(4) 解决办法2 - 泛型约束
interface Lengthwise {
    length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T { 
    // !!!!!! ------- 通过 extends 来约束T的具体属性 ------- !!!!!!
    // !!!!!! ------- 通过 extends 来约束T的具体属性 ------- !!!!!!
    // !!!!!! ------- 通过 extends 来约束T的具体属性 ------- !!!!!!
    console.log(arg.length); // 泛型约束后,T就具有了length属性,所以可以访问
    return arg;
}

(17) 声明合并

  • 接口合并
    • 把双方的成员,放到一个 ( 同名的接口 ) 中
    • 非函数成员
      • 接口的非函数成员应该是唯一的,如果不唯一,那么相同key对于的类型必须一样,不一样会报错
    • 函数成员
      • 同名函数会被当作函数重载
      • 后面的接口具有更高的优先级,特例是函数的参数是单一的字符串的情况
接口声明合并 - 非函数成员
---

interface Box {
  height: number;
  width: number;
}
interface Box {
  scale: number;
  // width: string; 
  // 1. 接口非函数成员应该唯一,但这里有两个width则不唯一
  // 2. 接口非函数成员如果不唯一,则类型必须一样
  // 3. 但这里,两个非函数成员width的类型不一致,报错
  // 4. 如果这里是 width: number; 则不会报错
}

let box: Box = {height: 5, width: 6, scale: 10};
接口声明合并 - 函数成员
---

interface Cloner { clone(animal: Animal): Animal; } 
interface Cloner { clone(animal: Sheep): Sheep; }
interface Cloner { 
  clone(animal: Dog): Dog; 
  clone(animal: Cat): Cat; 
}

接口声明合并后 
interface Cloner {
 clone(animal: Dog): Dog;
 clone(animal: Cat): Cat; 
 clone(animal: Sheep): Sheep; 
 clone(animal: Animal): Animal;
} 
// 注意接口中函数属性的顺序,对比声明合并前各接口的顺序

2023-12-18 更新

(18) 泛型工具类型

// 泛型 - 工具类型
// - 详见: 本项目/2-FRONTEND/2-TS/_README.md/ # (二) 范型工具类型

type Color1 = "red" | "yellow";
interface Color2 {
  red: string;
  yellow: string;
}
enum color3 { // 枚举可以作为 ( 类型 ),也可以作为 ( 值 )
  one = 1,
  two,
  three
}
type color4 = [number, string, symbol]

// 1
// Record
// - 用于将 ( 一种类型属性 - 最终是联合类型 ) 映射到 ( 另一种类型 )
// - Record 的实现详见: 本项目/2-FRONTEND/2-TS/_README.md
// -- keyof 是 索引类型查询 操作符, 返回 T 上已知的公共属性名的 联合类型
// -- T[K] 是 索引访问 操作符,Color2["red"] = string
type TRecord1 = Record<Color1, boolean>; // 鼠标 hover 查看具体的类型
type TRecord2 = Record<keyof Color2, boolean>;
type TRecord3 = Record<keyof Color2, Color2["red"]>;
type TRecord4 = Record<Color1, Color2>;
type TRecord5 = Record<string, any>; // key是string类型,value是any类型
type TRecord6 = Record<keyof any, any>; // key是string | number | symbol,value是any类型
// --- 分割线 ---
type TRecord7 = Record<keyof typeof color3, string> // 相当于 type TRecord7 = { one: string; two: string; three: string; }
type TRecord8 = Record<`${color3}`, string> // 相当于 type TRecord8 = { 1: string; 2: string; 3: string; }
// --- 分割线 ---
type TRecord9 = Record<color4[number], string> // 相当于 type TRecord9 = { [x: string]: string; [x: number]: string; [x: symbol]: string; }
// --- 分割线 ---
const record1: TRecord1 = {
  red: true,
  yellow: true,
};
const record2: TRecord2 = {
  red: true,
  yellow: true,
};
const record3: TRecord3 = {
  red: "",
  yellow: "",
};
const record4: TRecord4 = {
  red: {
    red: "",
    yellow: "",
  },
  yellow: {
    red: "",
    yellow: "",
  },
};
const record5: TRecord5 = {
  name: "woow_wu7",
  age: 20,
};
const record6: TRecord6 = {
  // key是string | number | symbol,value是any类型
  name: "woow_wu7",
  age: 20,
  10: "number",
};

// 2
// Partial
// - 将 ( 类型 ) 定义的 ( 所有属性 ) 都修改为 ( 可选的 )
type TPartial1 = Partial<Color2>;
type TPartial2 = Partial<Record<"a" | "b", boolean>>;
const partial1: TPartial1 = {
  red: undefined, // red可以是 string 或 undefined
  // yellow: "", 可选了,即可以没有 yellow 和 red 属性
};
const partial2: TPartial2 = {
  // a: true,
  // b: false,
  // c: true, // a 和 b 属性可选,但是不能超过a和b的范围,即一个新属性 c 就会报错
};

// 3 
// Required
interface Color {
  a?: number;
  b?: string;
  c: string;
}
type TR = Required<Color>
const tr: TR = {
  a: 1,
  b: '2',
  c: '3'
}

// 4
// Pick
// - 从类型定义的属性中,选取 ( 指定一组的属性 ),返回一个 ( 新的类型定义 )
// - 从字面意思也能知道是 ( 摘取部分属性 )
// - 注意区分 Pick 和 Omit 和 Exclude 的区别
// -------------------------------------------------------------  Pick 和 Omit 刚好相反
type TPick1 = Pick<Color2, "red">;
type TPick2 = Pick<Color2, "red" | "yellow">;
type TPick3 = Pick<Record<"a" | "b", number>, "b">;
type TPick4 = Pick<Record<"a" | "b" | "c", string>, "a" | "c">;
type TPick5 = Pick<Record<keyof Color2, number>, "red">;
const pick1: TPick1 = {
  red: "", // 从 Color2 中选取 red 属性
  // yellow: ''// 报错,不能将类型“{ red: string; yellow: string; }”分配给类型“Pick<Color2, "red">”。对象字面量只能指定已知属性,并且“yellow”不在类型“Pick<Color2, "red">”中
};
const pick2: TPick2 = {
  red: "", // 从 Color2 中选取 red 和 yellow 属性
  yellow: "",
};
const pick3: TPick3 = {
  b: 1,
};
const pick4: TPick4 = {
  a: "",
  c: "",
};
const pick5: TPick5 = {
  red: 1,
};

// 5
// Omit
// - 忽略某个属性
// - 注意区分 Pick 和 Omit 和 Exclude 的区别
// ------------------------------------------------------------- Pick 和 Omit 刚好相反
type TOmit1 = Omit<Color2, "red">;
type TOmit2 = Omit<Record<"a" | "b" | "c", boolean>, "a" | "b">;
const omit1: TOmit1 = {
  // 忽略 red 属性,则只剩下 yellow 属性
  yellow: "",
};
const omit2: TOmit2 = {
  c: true,
};

// 6
// Exclude
// - Exclude 就是将前面类型的与后面类型对比,( 过滤出前面独有的属性 )
// - 注意区分 Pick 和 Omit 和 Exclude 的区别
const exclude1: Exclude<"a" | "1" | "2", "a" | "y" | "z"> = "1"; // str 的类型是 "1" | "2",即从前面中去除后面中有的属性

// 7
// ReadOnly
// - 将类型 T 中包含的属性设置为readonly,并返回一个新类型
const readonly1: Readonly<Color2> = {
  red: "",
  yellow: "",
};
readonly1.red = "11"; // 不能修改,只读,这里报错
// 扩展
// 除了使用 Readonly<Color2>能做到只读外,也可以直接设置 interface Color2 { readonly red: string; }

// 8
// ReadonlyArray
// 8.1
type TReadonlyArray = ReadonlyArray<any>;
const readonlyArr: TReadonlyArray = ["1", 1];
readonlyArr[0] = "11"; // 报错,类型“TReadonlyArray”中的索引签名仅允许读取。
// 8.2
// 在React的useEffect函数签名中
// function useEffect(effect: EffectCallback, deps?: DependencyList): void
//   - type EffectCallback = () => (void | (() => void | undefined))
//   - type DependencyList = ReadonlyArray<any>
// 7.3
function foo1(arr: ReadonlyArray<string>) {
  arr.slice(); // okay
  arr.push("hello!"); // error!,只读
}
// 在最新的 typescript3.4 版本中可以使用下面的写法
function foo2(arr: readonly string[]) {
  arr.slice(); // okay
  arr.push("hello!"); // error! 只读
}
function foo3(arr: readonly string) {
  // 报错,仅允许对数组和元组字面量类型使用 "readonly" 类型修饰符。ts(1354)
}

// 9
// Parameters
function fn8(arg: { a: number; b: string }): void {}
type TP1 = Parameters<typeof fn8>;
// 相当于
// type TP1 = [
//   arg: {
//     a: number;
//     b: string;
//   }
// ];

// 9
// ReturnType
function fn9(s: string) {
  return { a: 1, b: s };
}
type T14 = ReturnType<typeof fn9>; // { a: number, b: string }
type T10 = ReturnType<() => string>; // string -- 这里传入的是 函数签名,即传入是 函数的类型
type T2 = ReturnType<(s: string) => number[]>; // number[]
type T11 = ReturnType<(s: string) => void>; // void
type T12 = ReturnType<<T>() => T>; // {}
type T13 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]
type T15 = ReturnType<any>; // any
type T16 = ReturnType<never>; // any
type T17 = ReturnType<string>; // Error
type T18 = ReturnType<Function>; // Error

// 10
// Uppercase
// Lowercase
type Name = "woow_wu7";
type UpperName = Uppercase<Name>; // 相当于 type UpperName = "WOOW_WU7"

// 11
// InstanceType
class Fn1 {}
type TInstance = InstanceType<typeof Fn1>; // 相当于 type TInstance = Fn
type TInstance2 = InstanceType<any>; // any
type TInstance3 = InstanceType<never>; // any
type TInstance4 = InstanceType<string>; // Error
type TInstance5 = InstanceType<Function>; // Error

// 12
// Awaited
type AAA1 = Awaited<Promise<string>>; // string
type AAA2 = Awaited<Promise<Promise<number>>>; // number - 不管嵌套有多深,都可以得到参数类型
type AAA3 = Awaited<boolean | Promise<number>>; //  number | boolean

(二) tsconfig.json

(2.0) tsconfig.json - 顶层属性

compilerOptions ----> 编译选项

extends ------------> 引入其他配置文件,继承配置
exclude ------------> 编译器需要排除的文件,或文件夹
include ------------> 编译器需要编译的文件,或文件夹

typeAcquisition 
  子属性如下
  - enable: boolean是否开启自动引入库类型定义文件.d.ts
  - include: array允许自动引入的库名
  - exclude: array排除的库名
  // acquisition 是收购,获取的意思

files -> 表示编译器需要编译的单个文件列表
references -> 指定依赖工程
compileOnSave -> 可以让IDE在保存文件的时候根据`tsconfig.json`重新生成文件

(2.1) 如何生成 tsconfig.json 文件

  • tsc --init 会生成一个具有默认配置的tsconfig.json文件
  • 如果一个目录下存在tsconfig.json文件,那么意味着这个目录是typescript项目根目录
  • tsconfig.json文件中指定了用来编译这个项目的 ( 根文件 ) 和 ( 编译选项 )

(2.2) tsconfig.json 配置项

  • 1.如果在 tsconfig.json 中,只写一个 {},则ts会编译所有此目录和子目录中的.ts文件
  • 2.匹配
    • **/* 表示匹配所有 ( 文件夹 ) 和 (文件 )
    • folder/**/* 表示匹配folder中的所有文件夹和文件
compilerOptions

  target 
  // target 
  // 含义:将ts编译成什么版本的js文件 
  // 可选值:"ES3", "ES5", "ES6"/ "ES2015", "ES2016", "ES2017","ESNext"
  // 默认值:"ES3"
  // "ESNext" 表示tc39最新的ES proposed features
  
  module
  // module
  // 含义:将ts编译成js文件时,js文件使用什么(模块系统)
  // 可选值:"None", "CommonJS", "AMD", "System", "UMD", "ES6", "ES2015","ESNext"
  // 默认值根据 --target或者target 选项不同而不
  // - target=es6时,module=es6
  // - target不是es6时,module=commonjs
  // - 比如:
  // - module=es6时,打包后的js文件中,使用import
  // - module=commonjs时,打包后的js文件中,使用require
  
  lib
  // lib
  // 含义:编译过程中需要引入的 ( 库文件 ) 的列表 
  // 值类型:string[],
  // 默认值:
  //   - 默认值是根据--target选项不同而不同
  //   - target=es5时 -> 默认值是 ['DOM', 'ES5', 'ScriptHost']
  //   - target=es6时 -> 默认值是 ['DOM', 'ES6', 'ScriptHost', 'DOM.Iterable']
  // 可选值:可选的值有很多,常用的有ES5,ES6,ESNext,DOM,DOM.Iterable、WebWorker、ScriptHost等
  
  
  allowJs
  checkJs
  // 1
  // allowJs
  // 值类型:boolean ,默认值是false
  // 含义:是否允许编译javascript文件,true则表示js后缀的文件也会被typescrpt编辑器编译
  // 例子
  // allowJs=true,你在ts文件中引入了一个js文件就(不会报错)
  // allowJs=flase,你在ts文件文件中引入了一个js文件会(报错)
  // 2
  // checkJs
  // 值类型:boolean,默认值是false
  // 含义:是否在 .js 文件中报告错误,与 ( checkJs 和 allowJs ) 一起配合使用
  
  
  noImplicitAny
  // noImplicitAny
  // 类型值:boolean,默认是false
  // 含义:有 隐含any时是否报错
  // 单词:implicit 是隐式的意思
  // 例子
  // function setName(name) {}
  // - 如果 noImplicitAny: false 时,name参数不会报错
  // - 如果 noImplicitAny: true 时,name参数会报错,因为name推测出any类型
  
  noUnusedLocals + noUnusedParameters
  // 1
  // noUnusedLocals
  // 类型值:boolean,默认值是false
  // 含义:有未使用的 ( 局部 ) 变量时,是否报错
  // 注意点:注意这里是局部变量,如果一个变量是非局部变量,比如导出的模块变量 export const a = 1,即使没用到也不会报错!!!!!
  // 2
  // noUnusedParameters
  // 类型值:boolean,默认值是false
  // 含义:有未使用的参数时,是否报错
  // 例子
    // function notUse(age: number, who: string) { // 当设置 noUnusedParameters: true 时,who报错,因为参数未使用
    //   console.log(`age`, age)
    // }
  
  baseUrl
  // baseUrl
  // 含义:用于解析(非相对模块名称)的(基目录),也可以认为是指定(根目录)
  // 配合:baseUrl + paths 可以实现类似alias的功能
  // 原因:当配置了 paths 时,一定需要配置 baseUrl
  
  paths
  // paths
  // 值类型:Object
  // 含义:( 模块名 ) 或 ( 路径映射 ) 的列表,类似于alias
  // 特点:需要搭配 ( baseUrl )
  // 例子1
  // "baseUrl": "./",
  // "paths": { "@/*": ["src/*"] } 
  // 例子2
  // "baseUrl": "./",
  //   "paths": {
  //      "*": ["types/*"]
  //   },
  //  "esModuleInterop": true, 
  // 例子2表示:寻在声明文件需要到 当前目录/types目录中去寻找
  // -- 例子3 -- 下面三种写法等价
  // "baseUrl": "src", // 
  // "paths": {
  //   "@/*": ["/*"]
  // }
  // "baseUrl": ".",
  // "paths": {
  //   "@/*": ["src/*"]
  // }
  // "baseUrl": "./src",
  // "paths": {
  //  "@/*": ["/*"]
  // }
  // -- 例子4 -- 官网的配置
  - 遇到问题
  - 问题:当在webpack配置了别名后,ts报错找不到模块
  - 回答:
    - **paths**:因为webpack知道了别名路径,但是ts并不知道这是设置了别名,所以需要设置tsconfig.json文件中的 `paths`
    - **baseUrl**: 当设置了 paths时,就必须设置baseUrl
   {
      "compilerOptions": {
        "baseUrl": ".", // this must be specified if "paths" is specified.当指定paths的时候,就必须指定baseUrl
        "paths": {
          "jquery": ["node_modules/jquery/dist/jquery"] // this mapping is relative to "baseUrl" 该映射是相对于 baseUrl 的
          "@/*": "src/*"
        }
      }
    }
    官网说明:https://www.typescriptlang.org/tsconfig#paths

  
  
  esModuleInterop
  // esModuleInterop
  // 含义:`esModuleInterop`选项的作用是支持使用`import d from 'cjs'`的方式引入`commonjs`包
  // 解释:本来 commmonjs只支持require的方法,esModuleInterop=true,则可以使用import的方式
  // interop是相互操作的意思
  
  
  typeRoots
  types
  // typeRoots 和 types
  // 值类型:两者都是array
  // - typeRoots数组成员是(@types包的文件夹路径)
  // - types数组成员是(npm包名)
  // 场景:默认所有可见的 @types包 会在编译过程中被包含进来
  // - typeRoots场景:如果指定了typeRoots,则只有typeRoots数组(指定的文件夹)中的(@types包会被包含进来)
  // - types场景:如果指定了types,则只有types数组中的(npm包会包含进来)
  // 案例
  // "typeRoots": ["./typings"], // 只有typings文件夹下的@types包会包含进来,node_modules中的则不会包含进来
  //  "types": ["jquery"], // jquery库npm包会包含进来
  // 官网说明
  // - https://www.tslang.cn/docs/handbook/tsconfig-json.html#types-typeroots-and-types
   
  
  jsx
  // jsx
  // 值类型:枚举
  // 值范围:'preserve' 'react-native' 'react'
  // 含义:指定jsx代码生成,这些模式只在代码生成阶段起作用
  // 1 preserve => 生成代码中会保留jsx后续的转换操作(比如以后还可以用babel),输出文件带有 .jsx
  // 2 react => 生成 React.createElement,在使用前不需要转化了,输出文件带 .js
  // 3 react-native => 保留了所有jsx,输出文件扩展名是 .js
    模式             | 输入        | 输出                         | 输出文件扩展名 |
    | -------------- | --------- | ---------------------------- | ------- |
    | `preserve`     | `<div />` | `<div />`                    | `.jsx`  |
    | `react`        | `<div />` | `React.createElement("div")` | `.js`   |
    | `react-native` | `<div />` | `<div />`                    | `.js`   |

  
  outDir // 输出文件夹
  rootDir // 输入文件夹
  
  moduleResolution 
  // moduleResolution
  // 值类型:string类型的枚举,支持 ( node ) ( classic )
  // 含义:如何处理模块
  // 大白话:遇到 import {} from ... 时应该如何去寻找模块
  // 一般情况下都选择 node
  // moduleResolution: 'node' 
  
  removeComments // ------------- boolean,是否移除代码中的注释 !
  strictNullChecks // ----------- boolean,是否开启null和undefined检查 !
  allowSyntheticDefaultImports // boolean,是否允许从没有设置默认导出的模块中默认导入 !
  strict // --------------------- boolean,是否启用 ( 所有 ) 严格类型检查选项,相当于启用 noImplicitAny noImplicitThis alwaysStrict strictNullChecks strictFunctionTypes strictPropertyInitialization !
  importHelpers // -------------- boolean,是否从 ( tslib ) 导入辅助工具函数,比如 _extends _rest 等 !
  
  sourceMap // ------------------ boolean,是否生成目标文件的sourceMap文件
  skipLibCheck // --------------- boolean,是否跳过lib库检查
  noImplicitReturns // ---------- boolean,是否在函数的每个分支都有返回值
  


---- 分割线 ----
include
exclude
  // include 和 exclude
  // 值类型:Array
  // 含义
  // - 在未设置include时,编译器默认包含(当前目录和子目录)的所有typescript文件(.ts, .d.ts 和 .tsx)
  // - 当(allowJs=true)时,还包括所有的js文件(.js和.jsx)
  // 例子
  // "include": ["src/**/*"] // 表示编译src/二级目录/三级目录中的所有(三级目录)中的typescript文件
  
  
  
---- 分割线 ---- 
2022120更新
----

1
resolveJsonModule 
- 表示从 .json 文件中导入,导出其类型
// settings.json ---> { "dry": false, "debug": false }
// import settings from "./settings.json";
// settings.debug === true;  // OK
// settings.dry === 2;  // Error: '===' 不能用于比较 boolean 和 number 类型

2
isolatedModules 
- 是否将每个文件作为单独的模块,默认为true
- 不可以和declaration同时设定
// isolated 表示单独,分离,隔离

3
noEmit
- 不编译输出文件
- 用ts新建一个项目,发现build时,没有输出,最后发现时tsconfig.json中noEmit选项的原因

4
skipLibCheck
- 是否跳过lib库检查,即跳过声明文件的类型检查
- 如果我们开启了这个选项,则可以节省编译期的时间,但可能会牺牲类型系统的准确性。在设置该选项时,推荐值为true
  • 具体的tsconfig.json配置说明
{
  "compilerOptions": {
    /* 基本选项 */
    "target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'("ESNext"表示最新的ES语法,包括还处在stage X阶段)
    "module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                             // 指定要包含在编译中的库文件
    "allowJs": true,                       // 允许编译 javascript 文件
    "checkJs": true,                       // 报告 javascript 文件中的错误
    "jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,                   // 生成相应的 '.d.ts' 文件
    "sourceMap": true,                     // 生成相应的 '.map' 文件
    "outFile": "./",                       // 将输出文件合并为一个文件
    "outDir": "./",                        // 指定输出目录
    "rootDir": "./",                       // 用来控制输出目录结构 --outDir.
    "removeComments": true,                // 删除编译后的所有的注释
    "noEmit": true,                        // 不生成输出文件
    "importHelpers": true,                 // 从 tslib 导入辅助工具函数
    "isolatedModules": true,               // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).

    /* 严格的类型检查选项 */
    "strict": true,                        // 启用所有严格类型检查选项
    "noImplicitAny": true,                 // 在表达式和声明上有隐含的 any类型时报错
    "strictNullChecks": true,              // 启用严格的 null 检查
    "noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误
    "alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

    /* 额外的检查 */
    "noUnusedLocals": true,                // 有未使用的变量时,抛出错误
    "noUnusedParameters": true,            // 有未使用的参数时,抛出错误
    "noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误
    "noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

    /* 模块解析选项 */
    "moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)。默认是classic
    "baseUrl": "./",                       // 用于解析非相对模块名称的基目录
    "paths": {},                           // 模块名到基于 baseUrl 的路径映射的列表
    "rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容
    "typeRoots": [],                       // 包含类型声明的文件列表
    "types": [],                           // 需要包含的类型声明文件名列表
    "allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。

    /* Source Map Options */
    "sourceRoot": "./",                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置
    "inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
    "inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

    /* 其他选项 */
    "experimentalDecorators": true,        // 启用装饰器
    "emitDecoratorMetadata": true,         // 为装饰器提供元数据的支持
    "strictFunctionTypes": false           // 禁用函数参数双向协变检查。
  },
  /* 指定编译文件或排除指定编译文件 */
  "include": [
      "src/**/*"
  ],
  "exclude": [
      "node_modules",
      "**/*.spec.ts"
  ],
  "files": [
    "core.ts",
    "sys.ts"
  ],
  // 从另一个配置文件里继承配置
  "extends": "./config/base",
  // 让IDE在保存文件的时候根据tsconfig.json重新生成文件
  "compileOnSave": true // 支持这个特性需要Visual Studio 2015, TypeScript1.8.4以上并且安装atom-typescript插件
}

(三) 声明文件 ( .d.ts )

(3.1) 声明文件总结

  • 库的分类
    • 库分为 ( 全局库 ) 和 ( 模块化库 )
  • 什么是 UMD 模块
    • UMD模块指的是:既可以作为模块使用,又可以作为全局模块使用的库
  • 声明文件中可以是( 值,或类型 )
    • interface type 对应类型
    • let const var function namespace 对应值
    • class enum 可以是值,也可以是类型

(3.2) 如何编写一个 - ( 全局声明文件 )

  • 除了 interfacetype,其他全局类型都需要通过 declare 来声明
  • namespace 相当于就是一个 ( 对象 ),namespace可以嵌套
  • 如果有两个同名的interface,则会 ( 声明合并 )
1. 全局变量
declare var foo: number;
declare let foo: number;
declare const foo: number;

2. 全局函数
declare function greet(greeting: string): void;

3. 函数重载
- 说明:`getWidget`函数接收一个数字,返回一个组件,或接收一个字符串并返回一个组件数组
- 声明:
declare function getWidget(n: number): Widget; 
declare function getWidget(s: string): Widget[];

4. 接口
如果有两个同名的interface,则会声明合并
interface GreetingSettings { 
  greeting: string; 
  duration?: number; 
}

5. 别名
type GreetingLike = string | (() => string)


6. 命名空间 ( 相当于一个对象 ),( 注意这里是对象,而不是对象的类型,对象的类型可以用interface来声明 )
declare namespace GreetingLib {
  interface LogOptions {
      verbose?: boolean;
  }
  interface AlertOptions {
      modal: boolean;
  }
}


7. 类 ( 声明的类 - 既是数据也是类型 )
( 即可以是值也可以是类型的,除了class类,还有enum枚举 )
declare class Greeter {
  constructor(greeting: string);
  greeting: string;
}

(3.3) 如何编写一个 - ( 模块声明文件 )

  • 1.模块化库需要修改tsconfig.json文件中的 baseUrlpath 以及 ``
{ 
  "compilerOptions": {
     "baseUrl": "./",
     "paths": {
         "*": ["types/*"]
     },
     "esModuleInterop": true, 
     // `esModuleInterop`选项的作用是支持使用`import d from 'cjs'`的方式引入`commonjs`包
  }
}
表示声明文件要去:当前文件夹下的,types文件夹中寻在
  • 2.模块声明和全局声明基本一样,只是模块是import和export的,所以使用需要import,而声明需要export
  • 3.当然模块的类型不止是esmodule,其他的模块类型遵循相应模块的与法规则即可

(3.4) 声明文件的规范

这里只记录一些平时不怎么注意的点

  • 不要在 ( 回调函数中 ) 使用 ( 可选参数 ),因为大多数情况回调函数的参数都是确定的,除非真的有可选的情况
  • 不要因为 ( 回调函数参数个数不同 ) 而写 ( 不同的重载 )
/* 错误 */ 
declare function beforeAll(action: () => void, timeout?: number): void;  
declare function beforeAll(action: (done: DoneFn) => void, timeout?: number): void;

*应该*只使用最大参数个数写一个重载:
/* OK */ 
declare function beforeAll(action: (done: DoneFn) => void, timeout?: number): void;

(四) React中使用 TS 注意点

1 
React.BaseSyntheticEvent
----
const a = (e: React.BaseSyntheticEvent) => {
    const html = e?.target?.innerHTML;
    // 这里需要使用到 innerHtml 属性,所以不能是 React.MouseEvent<HTMLDivElement>
    // React.BaseSyntheticEvent 是react合成事件
    // synthetic 是合成的的意思,adj
  };
  
  

2. 泛型函数的两种写法
- function a<T>(params: T) { .... }
- const a = <T>(params: T) => { .... }



3. ref
3.1
const inputWrapRef: React.MutableRefObject<HTMLLIElement> = useRef<HTMLLIElement>();
3.2
interface InputComponentProps {
  inputRef: React.MutableRefObject<Input>;
  value: string;
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;
  inputWrapRef: React.MutableRefObject<HTMLLIElement>;
}
3.3
const inputValueRef = useRef<string>()
注意:这里ref如果是绑定的input的值,则应该是string类型



4. event
4.1 const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {}
4.2 const onClick = (e: React.MouseEvent<HTMLInputElement>) => {}
4.3 其他事件(e: React.xxEvent <ReactNode>)
4.4
interface TagComponentProps {
  onClose: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
}

(五) 2022-02-03 更新

(1)
import type 
- import type 用来指明 仅仅导入/导出声明
- 解决以下问题:
  - import { MyThing } from './some-module.js'; 
  - 这个到底是导入的值,还是类型;!!! 如果是类型在编译成js的时候是会被删除的

资料