TypeScript

错误出现的越早越好。

第一章 出现

1.1 JavaScript 遗留问题

  • javaScript 是一个没有类型的脚本语言。出现问题必须再运行时才能发现。不能再代码编译期间发现错误。
  • JavaScript 不适合开发大型项目,一旦项目庞大后,因为宽松的校验就会导致传入参数类型不一致。

1.2 TypeScript 出现

  • 2014 微软 发布的 TypeScript。为javaScript 增加类型检测
  • 它可以编译成普通、干净、完整的javaScript 代码

第二章 环境

2.1 安装typeScript

  • typeScript:npm install typescript -g

2.2 编译

  • tsc *.ts(ts)文件
  • 会在生成一个js文件

2.3 webpack 开发环境配置

自动将 ts —》 js

第一步

  • 创建一个文件夹,该文件夹下包含 src 文件夹,src 中有个main.ts

第二步

  • 在根目录,初始化创建package.json
    • npm init

第三步

  • 安装webpack,webpack-cli
    • npm install webpack webpack-cli -D

第四步

  • 在根目录中的webpack.config.js 中配置

  • 配置webpack 文件入口,和出口

    • const path = require('path');
      
      module.exports = {
          // 文件入口配置
          entry: "./src/main.ts",
          // 出口配置
          output: {
              path: path.resolve(__dirname, "./dist"),
              filename: "bundle.js"
          }
      }
      
  • 安装ts-loader和局部 typeScript

    • npm install ts-loader typescript -D

    • 配置 ts-loader

      • const path = require('path');
        
        module.exports = {
            // 文件入口配置
            entry: "./src/main.ts",
            // 出口配置
            output: {
                path: path.resolve(__dirname, "./dist"),
                filename: "bundle.js"
            },
            module: {
                rules: [
                    {
                        test: /\.ts$/,
                        loader: 'ts-loader'
                    }
                ]
            }
        }
        
  • 生成tsconfig.json

    • tsc --init
  • 配置后缀名自动添加

    • resolve: {
          // 因为框架依赖其他文件
          extensions: [".ts", ".js", ".cjs", ".json"]
      },
      
  • 搭建本地服务 webpack-dev-server 注意版本

    • 安装npm install webpack-dev-server -D

    • 配置脚本:

      • "scripts": {
            "test": "echo \"Error: no test specified\" && exit 1",
            "build": "webpack",
            "serve": "webpack serve"
        },
        
  • 安装设置默认模板 html-webpack-plugin

    • npm install html-webpack-plugin -D

    • 配置插件

      • const HtmlWebpackPlugin = require("html-webpack-plugin");
        plugins: [
            new HtmlWebpackPlugin({
                // 使用的模板地址,自己创建的
                template: "./index.html"
            })
        ]
        
  • 配置开发模式 mode

    • mode: "development",
      devtool: "source-map", // 生成映射文件
      
  • 完成后的webpack.config.js

    • const path = require('path');
      const HtmlWebpackPlugin = require("html-webpack-plugin");
      module.exports = {
          mode: "development",
          devtool: "source-map", // 生成映射文件
          // 文件入口配置
          entry: "./src/main.ts",
          // 出口配置
          output: {
              path: path.resolve(__dirname, "./dist"),
              filename: "bundle.js"
          },
          devServer: {
      
          },
          resolve: {
              extensions: [".ts", ".js", ".cjs", ".json"]
          },
          module: {
              rules: [
                  {
                      test: /\.ts$/,
                      loader: 'ts-loader'
                  }
              ]
          },
          plugins: [
              new HtmlWebpackPlugin({
                  // 使用的模板地址
                  template: "./index.html"
              })
          ]
      }
      

2.4 ts-node 使用直接运行ts

安装npm install ts-node tslib @types/node -g

使用ts-node *.ts

2.5 掘金小册TS开发环境配置

2.5.1 VS Code 配置与插件

插件

  • TypeScript Importer

    • 作用:收集项目种所有的类型定义,打出 : 时,来进行类型自动补全。并且选则后会自动导入该类型。
  • Move Ts

    • 作用:通过修改ts文件路径,这个插件会自动调整到对应的目录结构。
  • ErrorLens

    • 作用:能够把VS Code 底部报错信息直接显示到代码文件中的对应的位置。

环境配置

  • Function Like Return Types: 显示推导得到的返回值类型
  • Parameter Names: 显示函数入参的名称
  • Parameter Types: 显示函数入参的类型
  • Variable Types: 显示变量类型
2.5.2 ts 执行环境配置

ts-node:能够直接执行 ts 文件。

ts-node-dev: 监听TS文件改变并重新执行。

安装

  • npm i ts-node typescript ts-node-dev -g

初始化项目

  • tsc --init

创建ts文件并执行

  • ts-node index.ts

ts-node 常用配置

  • -P,--project: 指定 tsconfig 文件位置。默认情况下 ts-node 会查找项目下的 tsconfig.json 文件, 如果是其他文件名,就需要自己指定了。
  • -T, --transpileOnly: 禁用掉执行过程中的类型检查过程,这能够让文件执行速度更快,且不会被类型报错卡住。
  • --swc: 在 transipileOnly 的基础上,还会使用 swc 来进行文件编译,进一步提升执行速度。
  • --emit: 如果你不仅是想要执行,还想顺便查看下产物,可以使用这一选项来把编译产物输出到 .ts-node 文件夹下(需要同时与 --compilerHost 选项一同使用)。

通过 ts-node-dev 监听项目改变

  • "dev": "tsnd --respawn --transpile-only src/index.ts",

方便类型检查

  • declare let foo: Foo; declare let bar: Bar; foo = bar
  • 使用 tsd包
    • expectType: 检查预期类型是否和表达式或变量类型一致
    • expectNotType: 检查预期类型与表达式类型是否不同
    • expectAssignable: 检查表达式或变量类型是否能赋值给与其的类型

第三章 数据类型

变量的声明格式:

  • var/let/const 标识符: 数据类型 = 赋值;
    
  • 基本使用

    • var name: string = "acwink";
      let age: number = 18;
      const height: number = 1.88;
      
      const message: String = new String("Hello World");
      

类型推导:如果未显示的定义类型注解,默认情况下,类型是初始化赋值的类型

  • let foo = "1231"; // string type
    

3.1 js和ts共有的基本数据类型

3.1.1 number 类型
  • 不区分浮点数和整数,其他和js一样

    let num: number = 123
    
3.1.2 boolean 类型
  • let flag: boolean = true
    
3.1.3 string 类型
  • let message: string = "123123";
    let message: string = `name: ${name}`;
    
3.1.4 array 类型
  • 需要确定数组中存放的数据类型。

  • 一个数组中存放的数据类型应该是确定的。

  • const names1: Array<string> = [];  // 不推荐,在react,jsx中存在冲突
    
    const name2: string[] = []; // 推荐
    
3.1.5 元组

元组是一种指定大小的类型数组, 使用操过数组下标的索引值访问,ts会报错

const arr:[string, number, any] = ["acwink", 18, "123"];
const arr1:[name: string, age: number, male?: boolean] = ["acwink", 18, true];
3.1.6 object 、 Object 、{} 类型

object 类型在ts中能够赋值任何对象类型,object 代表所有非原始类型的类型,即数组、对象与函数这些。当我们知道某个变量具体类型时,更推荐用 Record<string, unknown> / Record<string, any> 表示对象,unknown[] / any[] 表示数组,(...args: any[]) => any 表示函数

Object: 代表所有的类型,任何类型都可以给它赋值。Ts 引入 object 就是为了修复这个问题。

{}:此类型就像 new Object() 但是不能对其做任何赋值操作,它可以赋值任何 非 null / undefined 的值

  • const a1: object = [];
    const a2: object = () => {};
    const a3: object = new Boolean();
    

在ts中 {} 这种类型只能在声明的时候赋值一次,后面任何方法都无法修改,因为{}类型上没有任何属性

const a: {} = {name: "acwink"}
3.1.6 null 类型
  • const n1: null = null;
    
3.1.7 undefined 类型
  • const n2: undefined = undefined;
    
3.1.8 Symbol 类型
  • 这里的symbol 与js不同,所有的 symbol 都指向同一个 TS 类型, 也就是说 在类型层面上,并没有满足每个 symbol 类型独一无二

  • let title1: symbol = Symbol("title");
    let title2: symbol = Symbol("title");
    
  • 为了实现类型层面独一无二的 sybmol, ts 引入 unique 关键字修饰 sybmol,(js 通过 Symbol.for(“xxx”)复用Symbol) 类型层面复用 symbol, typeof symbol类型名

  • const uniqueSymbolFoo: unique symbol = Symbol("acwink");
    // 不兼容
    const uniqueSymbolBar1: unique symbol = uniqueSymbolFoo;
    
    // 兼容写法
    const uniqueSymbolBar2: typeof uniqueSymbolFoo = uniqueSymbolFoo;
    
3.1.9 字面量类型

字面量类型:比原始类型更精准

const str: "acwink" = "acwink";
const age: 18 = 18;
const bool: true = true;
const obj: {name: "acwink", age: 18} = {name: "acwink", age: 18};
3.1.10 模板字符串类型

基本使用

type World = "World";

type Greeting = `Hello ${World}`; // Hello World

// 配合泛型
type Greet<T extends string | number | boolean | null | undefined | bigint> =
  `Hello ${T}`;

// 使用 Greet 传入的泛型会被转换成字面量类型
type Greet1 = Greet<"acwink">; // "Hello acwink"
type Greet2 = Greet<18>; // "Hello 18"
type Greet3 = Greet<true>; // "Hello true"
type Greet4 = Greet<null>; // "Hello null"
type Greet5 = Greet<undefined>; // "Hello undefined"
type Greet6 = Greet<0x1f>; // "Hello 31"

// 直接为插槽传入一个类型,它会保持原样
// 所有 Hello 开头的字面量类型都会被视为 Hello ${string} 的子类型
type Greeting1 = `Hello ${string}`; // `Hello ${string}`

// 通过字面量类型,声明版本号
type Version = `${number}.${number}.${number}`;

const v1: Version = "1.1.1";

// 利用自动分发特性实现
// type SKU =
//   | 'iphone-16G-official'
//   | 'xiaomi-16G-official'
//   | 'honor-16G-official'
//   | 'iphone-16G-second-hand'
//   | 'xiaomi-16G-second-hand'
//   | 'honor-16G-second-hand'
//   | 'iphone-64G-official'
//   | 'xiaomi-64G-official'
//   | 'honor-64G-official'
//   | 'iphone-64G-second-hand'
//   | 'xiaomi-64G-second-hand'
//   | 'honor-64G-second-hand';
type Brand = "iphone" | "xiaomi" | "honor";
type Memory = "16G" | "64G";
type ItemType = "official" | "second-hand";

type SKU = `${Brand}-${Memory}-${ItemType}`;

// 通过泛型传入联合类型也会有分发效果
type SizeRecord<Size extends string> = `${Size}-Record`;

type Size = "Small" | "Middle" | "Large";

type UnionSizeRecord = SizeRecord<Size>;

// 由于模板字符串类型的最终参会还是字面量类型,所有只要插槽位置的类型匹配,字符串字面量类型就可以认为是模板字符串类型的子类型

const greet = (to: string): `Hello ${string}` => `Hello ${to}`;

结合索引类型

interface Foo {
  name: string;
  age: number;
  job: any;
}

type ChangeListener = {
  on: (change: `${keyof Foo}Changed`) => void;
};

declare let listener: ChangeListener;
// 参数提示约束为:"nameChanged" | "ageChanged" | "jobChanged"
listener.on("ageChanged");

结合映射类型

重映射:模板字符串类型和映射类型更好的协作引入的新语法。

模板字符串类型和映射类型结合,可以实现修改对象属性名。

// as 语法的作用,将映射的键名作为变量,映射到一个新的字符串类型
// string & K 的作用确保字符串模板类型的插槽值一定是字符串类型,因为模板字符串插槽中并不支持 Symbol 类型,
type CopyWithRename<T extends object> = {
  [K in keyof T as `modified_${string & K}`]: T[K];
};

interface Foo {
  name: string;
  age: number;
}

/*
  {
    modified_name: string;
    modified_age: number;
  }
*/
type CopiedFoo = CopyWithRename<Foo>;

// 专用类型工具 Uppercase、Lowercase、Capitalize、Uncapitalize
type Heavy<T extends string> = `${Uppercase<T>}`;
type Respect<T extends string> = `${Capitalize<T>}`;

type HeavyName = Heavy<"acwink">; // "ACWINK"
type RespectName = Respect<"acwink">; // "Acwink"

type CopyWithRename2<T extends object> = {
  [K in keyof T as `modified${Capitalize<string & K>}`]: T[K];
};

/*
{
  modifiedName: string;
  modifiedAge: number;
}
*/
type CopiedFoo2 = CopyWithRename2<Foo>;


模板字符类型与模式匹配

模式匹配核心概念:对符合约束的某个类型结构,提取其某个位置的类型,比如函数结构中的参数类型与返回值类型

type ReverseName<Str extends string> =
  Str extends `${infer First} ${infer Last}`
    ? `${Capitalize<Last>} ${First}`
    : Str;

type ReversedTomHardy = ReverseName<"Tom hardy">; // "Hardy Tom"
type ReversedAcwink = ReverseName<"Ac Wink">; // "Wink Ac"

// 除了和infer进行模式匹配外,还可以声明一个泛型进行模式匹配
declare function handler<Str extends string>(arg: `Guess who is ${Str}`): Str;

handler("Guess who is Acwink"); // "Acwink"
handler(`Guess who is `); // ""
handler("Guess who is  "); // " "
handler("Guess who was"); // Error
handler(""); // error

3.2 ts 特有的数据类型

3.2.1 any 类型
  • 一些情况下,无法确定该变量类型时或者该变量会发生类型变化,则使用any

  • 当不想给该变量添加类型的时候,就用any

  • any 就相当于 js中的变量

  • any 类型可赋值给任意类型

  • 任意类型都可赋值给any

  • let message: any = "Hello World";
    message = 123;
    message = {};
    
    const val1: string = message;
    const val2: number = message;
    const val3: () => {} = message;
    const val4: {} = message;
    
    
3.2.2 unknown 类型
  • 用于描述 当前变量 不确定 类型, 但是只能赋值给 any类型 和 unknow类型。

  • function foo() {
        return "abc";
    }
    
    function bar() {
        return 123;
    }
    
    let flag = true;
    let result: unknown;
    
    if (flag) {
        result = foo();
    } else {
        result = bar();
    }
    
    console.log(result);
    
    
    let unkonwVar: unknown = "acwink";
    
    unkonwVar = false;
    unkonwVar = "acwink";
    unkonwVar = {
      site: "juejin",
    };
    
    
    unkonwVar = () => { }
    
    const val1: any = unkonwVar;
    const val2: unknow = unkonwVar;
    
    
3.2.3 void 类型
  • 用于指定一个函数没有返回值

  • function sum(num1: number, num2: number): void {
        console.log(num1 + num2);
    }
    
    sum(1, 2);
    
3.2.4 never 类型
  • 是所有类型的子类型

  • 就是永远没有返回值的函数,也就是该函数在死循环。

  • function menu(): never {
        while(true) {
            
        }
    }
    
    function ex(): never {
        throw new Error();
    }
    
    
  • 应用场景

    • 当用户修改传入的参数类型的时候,使其报错,然后让用户将代码修改完整

    • function handleMessage(message: string | number | boolean) {
          switch (typeof message) {
              case 'string':
                  console.log("string处理message");
                  break;
              case 'number':
                  console.log("number处理message");
                  break;
              
              default:
                  const check: never = message;
          }
      }
      
      handleMessage("sdfs");
      
3.2.5 tuple 类型
  • 多种元素的组合

  • 元组类型可以确定里面的每个数据的类型

    • const info: [string, number, number] = ["acwink", 18, 1.88];
      
3.2.6 对象类型
  • function printPotin(point: {x: number, y: number}) {
        console.log(point.x);
        console.log(point.y);
    }
    
    printPotin({x:1, y:2});
    
    // 设置属性可选,z 加上 ? 就能设置
    function printPotin(point: {x: number, y: number, z?: number}) {
        console.log(point.x);
        console.log(point.y);
        console.log(point.z);
    }
    
    printPotin({x:1, y:2});
    printPotin({x:1, y:2, z:1});
    
3.2.7 联合类型

把其他类型结合起来,构成新的类型

  • // id 可以 是 number 或 string
    function printID(id: number | string) {
        console.log(id);
    }
    
    printID(2312);
    printID("dfafdas");
    

例如:我们需要为一个接口定义类型, 这时联合类型在code 和 status 中起作用了

interface Res {
    code: 10000 | 10001 | 50000;
    status: "success" | "failure";
    data: any
}

我们也可以把各种类型混合在一起组成联合类型

interface Tmp {
    mixed: true | string | 18 | {} | (() => {}) | (1 | 2)
}
3.2.8 交叉类型
  • 交叉类型:多种类型叠加在一起成为一种类型,它包含所需要的所有类型特征, A B 类型需要同时满足

  • type IBookList = Array<
        {author: string;} & 
        ({type: 'history'; range: string;} | {type: "story"; theme: string;})
    >
    
    // 例如
    interface NameStruct {
      name: string;
    }
    
    interface AgeStruct {
      age: number;
    }
    
    type ProfileStruct = NameStruct & AgeStruct;
    
    const profile: ProfileStruct = {
      name: "acwink",
      age: 18,
    };
    
    
    type StrAndNum = string & number; // never: 描述这种类型根本不存在
    
    // 两个联合类型实现交叉类型,也就是求交集
    type UnionIntersection1 = (1 | 2 | 3) & (1 | 2); // 1 | 2
    type UnionIntersection2 = (string | number | symbol) & string; // string
    
3.2.9 函数类型
type Fish = {
    swwing: () => void // 这个是函数类型
}
3.2.10 Record 类型
<T extends Record<string, key>> // 要求传入的泛型 必须为key:value的对象类型
3.2.11 索引类型

包含三个部分:索引签名类型,索引类型查询、索引类型访问

索引签名类型

这个类型用于指定key的类型

// 下面两个类型,我们只能声明键的类型为 string
interface AllStringTypes1{
  [key: string]: string;
}
type AllStringTypes2 = {
  [key: string]: string;
}
// 因为对象访问时,会将这些key转换成字符串索引
const foo1: AllStringTypes1 = {
  acwink: "599",
  599: "acwink",
  [Symbol("ddd")]: "symbol",
};

// 索引类型可以和具体类型声明并存, 但是索引类型的值需要包含,具体类型声明的值
interface AllStringTypes23 {
  propA: number;
  propB: boolean;
  [key: string]: boolean | number;
}

// 常用于js重构
interface AnyTypeHere {
    [key: string]: any;
}

索引查询类型 keyof

keyof 操作符。它将对象中所有属性提取出来并转换成字面量类型组成联合类型

interface Foo {
  acwink: 1;
  18: 2;
}

type FooKeys = keyof Foo; // 18 | "acwink"

const obj1: FooKeys = 18;
const obj2: FooKeys = "acwink";


// keyof any 产生一个联合类型, 它是由一切可能组成的对象键的类型组成 
type autoTypes = keyof any; // string | number | symbol
const obj1: autoTypes = "acwink";
const obj2: autoTypes = 18;
const obj3: autoTypes = Symbol("xxx");
const obj4: autoTypes = {}; // Error


type IKeys = keyof {a: string; b: number;} 
// 相当于 ||
type IKeys = "a" | "b";

type IPartial<T extends Record<string, any>> = {
    [P in keyof T]?: T[P];
}
// 标识 P 类型 在 T 对象的 keys 中

索引类型访问

我们可以通过索引访问来获取,访问字段/类型的值

interface NumberRecord {
  acwink: boolean;
  [key: string]: number | boolean;
}

type PropType1 = NumberRecord["acwink"]; // boolean
type PropType2 = NumberRecord[string]; // number | boolean


// 结合 keyof 一次性获取该类型的所有值的类型, 并组成联合类型
type PropTypeUnion = NumberRecord[keyof NumberRecord]; // number | boolean

映射类型:类型编程的第一步

in 就是 映射类型, 它会将联合类型的每一个成员映射出来,并将其值设置为 指定的类型

这个类型时一个确切的工具, 键名 =》 键值类型, 多个键名映射到同一个键值类型

type Stringfy<T> = {
  [K in keyof T]: string;
};

/*
  {
    name: string;
    age: string;
    info: string;
  }
*/
type UserInfoType = Stringfy<{ name: string; age: number; info: any }>;

// 克隆一个类型
// K in 映射类型、 keyof T、[K in keyof T]的 [] 索引签名类型
// T[K] 索引类型访问
type Clone<T> = {
  [K in keyof T]: T[K];
};

type UserInfo = {
  name: string;
  age: number;
  info: {
    phone: number;
    balance: number;
  };
};


type cpUserInfo = Clone<UserInfo>;

3.3 其他类型相关

3.3.1 类型别名
type A = string;
type StatusCode = 200 | 301 | 400 | 500 | 502;
type PossibleDataTypes = string | number | (() => unknow);

type Handler = (e: Event) => void;
const clickHandler: Handler = (e) => {};


type ObjType = {
    name: string;
    age: number;
}

// 融入泛型的类型别名
type Factory<T> = T | number | number;
const foo: Factory<boolean> = true;

// 一般情况下不会直接使用工具类型直接做类型标注,而是在声明一个类型别名
type FactoryWithBool = Factory<boolean>;
const foo: FactoryWithBool = true;

// 一个有意义的工具类型, 这个工具类型可以处理存在null的情况
type MaybeNull<T> = T | null;
function process(input: MaybeNull<{handler: () => {}}>) {
    input?.handler();
}


type MaybeArray<T> = T | T[];
function ensureArray<T>(input: MaybeArray<T>): T[] {
    return Array.isArray(input) ? input:[input];
}


3.3.2 as 类型断言

在判断断言是否成立,即差异是否能接受时,实际上判断的即是这两个类型是否能够找到一个公共的父类型

  • 当能确定,某个变量一定是某种类型,那么可以使用类型断言。下面就确定el是img标签这个类型,所以使用断言。
const el = document.getElementById("why") as HTMLImageElement;
el.src = "url";
  • 案例二
class Person {

}

class Student extends Person {
    studying() {

    }
}
// 当确定了 p 是学生类型,则可以使用断言
function sayHeloo(p: Person) {
    (p as Student).studying();
}
3.3.3 非空类型断言
function printMessage(message?: string) {
    // 叹号表示 message一定有值, 让编译能够通过
    console.log(message!.length);
}

printMessage("123");
3.3.4 可选链的使用
  • 可选链使用 ?.

  • 作用:当对象属性不存在时,会短路,直接返回undefined,如果存在,那么才会继续执行;

  • type Person ={
        name: string,
        friend?: {
            name: string,
            age?: number
        }
    }
    
    const info: Person = {
        name: "acwink",
        // friend: {
        //     name: "kobe"
        // }
    }
    
    
    
    // 另外一个文件中,别人取出信息
    console.log(info.name);
    // friend 不存在,那么就不会去取 name了, 直接返回undefined
    console.log(info.friend?.name);
    
3.3.5 !! 的作用
  • 把其他值转换成布尔类型, 本身就是js的,取反的取反

  • const message = "Hello World";
    
    const flag = !!message;
    
3.3.6 ?? 的作用
  • 也是 js 的运算符,用于替代以前的逻辑与, xx || {}

  • 空值合并操作符

  • let message: string | null = null;
    
    const content = message ?? "你好呀李迎合"; // 如果message是null 则使用 ?? 后面的值
    console.log(content);
    
3.3.7 字面量类型
  • const message: "HHH" = "HHH";
    
    // 字面量类型的意义,就是必须结合联合类型, 有点像枚举类型 
    let algin: 'left' | 'right' | 'center' = 'left';
    

3.4 类型的安全

3.4.1 类型查询操作符 typeof

TS 扩展了 typeof 的功能,使得能够返回值的类型。

const str = "acwink";
const obj = { name: "acwink" };
const nullVar = null;
const undefineVar = undefined;

const func = (input: string) => {
  return input.length > 10;
}

type Str = typeof str; // "acwink"
type Obj = typeof obj; // {name: string}
type Null = typeof nullVar; // null
type Undefined = typeof undefineVar; // undefined
type func = typeof func; // (input: string) => boolean


const func2: typeof func = (name: string) => {
    return name === "acwink";
}


// RetureType 返回函数类型的返回值类型 
type FuncRetureType = ReturnType<typeof func>; // boolean
3.4.2 类型守卫

类型守卫指的是ts的类型推导能力,它能够根据代码的逻辑不断尝试收缩类型范围,这一能力被称为 类型的控制分析能力

function foo(input: string | number ) {
  // 这个判断语句内部 input 将会被ts推断为 string 类型
  if (typeof input === "string") {}
  // input 被推断为 number类型
  if (typeof input === 'number') {}
}

通过类型守卫能力,实现类型不匹配的时候报错,实现如下

function bar(strOrNumOrBool: string | number | boolean) {
  switch (typeof strOrNumOrBool) {
    case "string":
      console.log("string");
      break;
    case "number":
      console.log("number");
      break;
    case "boolean":
      console.log("boolean");
      break;
    default:
      // 走到这个逻辑,说明传入的类型是错误的,这个时候我们需要报错
      const _exhaustiveCheck: never = strOrNumOrBool;
      throw new Error(`Unknown input type: ${_exhaustiveCheck}`);
  }
}

值得注意的是:类型推断,并不能跨函数推断

function isString(input: unknown): boolean {
  return typeof input === "string";
}


function baz(input: string | number) {
  if (isString(input)) {
    // 报错 string | number 类型不存在 replace方法
    input.replace();
  }
}

为了解决这个问题,通过引入 is 关键字,显示的提供类型信息,这样的函数称为类型守卫,注意:类型守卫不会对内部的逻辑进行类型检查关联,也就是说它只看返回值 input is string 部分,和类型断言类似

// 类型守卫
function isString(input: unknown): input is string {
    return typeof input === "string";
}

类型守卫可以和对象类型联合类型等一起使用

// 用于判断false 
export type Falsy = false | "" | 0 | null | undefined;
export const isFalsy = (val: unknown): val is Falsy => !val;

// 用于判断原始类型
// 不包括常用的 symbol 和 bigint
export type Primitive = string | number | boolean | undefined;
export const isPrimitive = (val: unknown): val is Primitive =>
  ["string", "number", "boolean", "undefined"].includes(typeof val);
3.4.3 基于 in 和 instanceof 的类型保护

in : 是 js 的概念,用于判断某个属性是否在对象的原型链上

通过使用in操作符,判断类型间不同的属性就能够,实现类型推断收缩范围

interface Bar {
  bar: string;
  barOnly: boolean;
  shared: number;
}

interface Foo {
  foo: string;
  fooOnly: boolean;
  shared: number;
}
/*
	这里使用 foo 进行类型推断
	如果使用 shared 将不能推断出具体的类型,因为两个类型都有shared
*/
function handle(input: Foo | Bar) {
  if ("foo" in input) {
    input.fooOnly;
  } else {
    input.barOnly;
  }
}

instanceof 实现类型保护

class FooBase {}
class BarBase {}
class Foo extends FooBase {
  fooOnly() {}
}
class Bar extends BarBase {
  barOnly() {}
}

function handle(input: Foo | Bar) {
  if (input instanceof FooBase) {
    input.fooOnly();
  } else {
    input.barOnly();
  }
}
3.4.3 类型断言守卫

运行时:如果断言结果不存在,抛出一个错误,则断言下方的代码就成了 Dead Code

// nodejs 
import assert from "assert";

let name: any = "acwink";
assert(typeof name === "number", "name not is number");

// number 类型
name.toFixed();

ts中的写法

断言配合类型守卫使用,可以使得守卫参数在内部断言成指定类型

let names: any = "acwink";

function assertIsNumber(val: any): asserts val is number {
  if (typeof val !== "number") {
    throw new Error("Not a number");
  }
  val.toFixed();
}

assertIsNumber(names);

3.4.4 条件类型

在条件类型中,泛型通常充当判断条件和返回值

T extends Condition 表示传入的类型 T 需要满足条件 Condition

type IsEqual<T> = T extends true ? 1 : 2;

type A = IsEqual<true>; // 1;
type B = IsEqual<false>; // 2
type C = IsEqual<"acwink">; // 2

条件类型就和三元表达式类似

TypeA extends TypeB ? Res1 : Res2

这里的 extends 表示的是兼容,A 兼容 B 则为 真,并不需要完全相等

infer

此关键值用来提取传入的泛型类型信息

比如我们传入的是一个函数类型,我们需要提取它的返回值类型,就通过下面这么写

type FunctionReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : never;
// 这里的R就是我们传入函数类型的返回值
type StringReturn = FunctionReturnType<(...args: any[]) => string>; // string

// 例子二:
type Swap<T extends any[]> = T extends [infer A, infer B] ? [B, A] : T;

type SwapResult1 = Swap<[1, 2]>; // [2, 1] 兼容类型 [infer A, infer B] 交换返回,A,B 是传入的两个值的推断值
type SwapResult2 = Swap<[1, 2, 3]>; // [1, 2, 3] 因为不兼容条件所以直接返回


// 通过rest操作符来处理任意长度的情况
type SwapStartAndEnd<T extends any[]> = T extends [
  infer Start,
  ...infer Left,
  infer End
]
  ? [End, ...Left, Start]
  : T;


// 数组到联合类型的转换
type ArrayItemType<T> = T extends Array<infer ElementType>
  ? ElementType
  : never;

type ArrayItemTypeResult1 = ArrayItemType<[]>; // never
type ArrayItemTypeResult2 = ArrayItemType<string[]>; // string
type ArrayItemTypeResult3 = ArrayItemType<[string, number]>; // string | number

// infer 和接口类型使用
type PropType<T, K extends keyof T> = T extends { [Key in K]: infer R }
  ? R
  : never;

type PropTypeResult1 = PropType<{ name: string }, "name">; // string
type PropTypeResult2 = PropType<{ name: string; age: number }, "name" | "age">; // string | number

// 反转键名与值
type ReverseKeyValue<T extends Record<string, unknown>> = T extends Record<
  infer K,
  infer V
>
  ? Record<V & string, K>
  : never;

type ReverseKeyValueResult1 = ReverseKeyValue<{ key: "value" }>; // {value: "key"}



// infer 与 Promise 使用
type PromiseValue<T> = T extends Promise<infer V> ? V : T;
type PromiseValueResult1 = PromiseValue<Promise<number>>; // nubmer, 满足兼容性
type PromiseValueResult2 = PromiseValue<number>; // number, 不满足兼容性,并没有通过 infer提取

                                         
                                         


infer 用递归解决深层嵌套

// infer 与 Promise 使用
type PromiseValue<T> = T extends Promise<infer V> ? PromiseValue<V> : T;
type PromiseValueResult1 = PromiseValue<Promise<Promise<object>>>; // object

3.4.5 分布式条件类型

也称为条件类型的分布式特性,只不过是条件类型在满足一定情况下的执行逻辑而已。

条件类型分布式起作用的条件:

  • 类型参数需要是个联合类型

  • 类型参数需要通过泛型参数的方式传入,不能直接进行条件判断

  • 条件类型中的泛型参数不能被包裹

  • 执行效果:将联合类型拆开分别做条件判断

    • type Condition<T> = T extends boolean ? "Y" : "N";
      
      type ConditionRes = Condition<number | boolean>; // "N" | "Y"
      // ConditionRes = (number extends boolean ? "Y" : "N") | (boolean extends boolean ? "Y" :  "N")
      

阻止触发分布式特性

// 将传入的类型 交上一个 {} 并不会影响比较结果,同时又阻止分布式将联合类型分开判断
type NoDistribute<T> = T & {};
type Wrapper<T> = NoDistribute<T> extends boolean ? "Y" : "N";
type Res1 = Wrapper<number | boolean>;
("N");

比对两个联合类型,阻止分布式,通过元组包裹

type CompareUnion<T, U> = [T] extends [U] ? true : false;

type CompareRes1 = CompareUnion<1 | 2, 1 | 2 | 3>; // true;
type CompareRes2 = CompareUnion<1 | 2, 1>; // false

因为泛型传入 never会直接返回 never,所以我们要判断是否为 never类型,需要配合元组使用

type IsNever<T> = [T] extends [never] ? true : false;

type IsNeverRes1 = IsNever<never>; // true;

any: 作为判断参数,都会满足判断两方的条件,如果作为 判断条件 则会进行判断,它的条用不限制方式

never: 只有在传入给泛型时,直接跳过判断返回 never,如果直接判断可以判断,作为判断条件也可以直接判断

判断any和unknown

// 判断类型是否时Any
type IsAny<T> = 0 extends 1 & T ? true : false;

type IsAnyRes = IsAny<any>;

// 判断是否为 unknown
type IsUnknown<T> = unknown extends T
  ? IsAny<T> extends true
    ? false
    : true
  : false;

第四章 函数

3.1 函数类型

  • js 中函数是重要组成部分,并且函数可以作为一等公民。

  • 函数作为参数的时候

    • function foo() {
      
      }
      
      function bar(fn: () => void) {
          fn();
      }
      
      bar(foo);
      
  • 箭头函数声明(函数类型签名)类型,默认情况下会被推断出来

  • const add: (num1: number, num2: number) => number = (num1: number, num2: number) => {
        return num1 + num2;
    }
    // 上面代码可读性差,
    const add = (num1: number, num2: number): number => {
        return num1 + num2;
    };
    
    type FuncAdd = (num1: number, num2: number) => number;
    const add: FuncAdd = (num1, num2) => num1 + num2;
    

如果只描述这个函数的类型结构

  • interface IADD {
        (num1: number, num2: number): number;
    }
    
    const add:IADD = (num1: number, num2: number) => {
        return num1 + num2;
    }
    
  



**函数类型的使用**

* ~~~typescript
  function calc(n1: number, n2: number, fn: (num1: number, num2: number) => number) {
      return fn(n1, n2);
  }
  
  const result1 = calc(20, 30, function(a1, a2) {
      return a1 + a2;
  });
  console.log(result1);
  const result2 = calc(20, 30, function(a1, a2) {
      return a1 * a2;
  });
  console.log(result2);
   

3.2 函数返回值类型和返回值类型注解

  • 可选类型必须写在必选类型后面
function foo(sum: number, a?:number): number { // a 参数是可选类型,可选类型必须写在必选类型后面
    return sum;
}

3.3 匿名函数的参数类型

因为item是来自names中的,所以匿名函数的参数类型可以不用写类型注解,因为上下文能够推导出来item的类型。

const names = ["abc", "cba", "nba"];
names.forEach(function(item) {

});

3.4 函数的重载

也就是函数同名,参数个数或参数类型不同。

  • 先声明函数重载,再用 any实现

  • // 这边是设置重载,重载签名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 result = add(20, 30);
    const result2 = add('2131', '12312');
    
    
    export {}; 
    
  • 联合类型和函数重载对比

    • // 实现方式一
      function getLength(args: string | any[]) {
          return args.length;
      }
      
      // 实现方式二
      function getLength(args: string): number;
      function getLength(args: any[]): number;
      
      function getLength(args: any) {
          return args.length;
      }
      
      export {}; 
      

第五章 类

5.1 类定义

class Person {
    // 声明属性
    name: string = "";
    age: number = 0;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    eating() {
        console.log(this.name + " eating");
    }
}

const p = new Person("acwink", 18);

5.2 类的继承

  • 使用关键字 extends

  • class Person {
        name: string = "";
        age: number = 0;
    
        constructor(name: string, age: number) {
            this.name = name;
            this.age = age;
        }
    
        eating() {
            console.log("eating");
        }
    
    }
    
    class Student extends Person {
        
        sno: number = 0;
    
        constructor(name: string, age: number, sno: number) {
            super(name, age);
            this.sno = sno;
        }
        
        eating(): void {
            super.eating();
            console.log("student eating");
        }
    
        studying() {
            console.log("studying");
        }
    }
    
    class Teacher extends Person {
        title: string = "";
    
        constructor(name: string, age: number, title: string) {
            super(name, age);
            this.title = title;
        }
    
        eating(): void {
            super.eating();
            console.log("teacher eating");
        }
    
        teaching() {
            console.log("teacher")
        }
    }
    
    const stu = new Student("acwink", 18, 2131231);
    const tea = new Teacher("zhang", 30, "语文老师");
    
    stu.eating();
    tea.eating();
    
    export {}; 
    

如果要覆盖父类的方法,可以通过关键字 override 来确保覆盖的时父类方法, 如果 override 修饰父类没有的方法则会报错

class Base {
  printWithLove() {}
}


class Derived extends Base {
  override printWithLove() {

  }
}

5.3 类的多态

  • 多态的目的,是为了实现更加通用的代码。

  • class Animal {
        action() {
            console.log("animal runnging");
        }
    }
    
    class Dog extends Animal {
        action(): void {
            console.log("dog running");
        }
    }
    
    class Fish extends Animal {
        action(): void {
            console.log("fish swimming");
        }
    }
    
    
    function makeAction(animals: Animal[]) {
        animals.forEach(animal => {
            animal.action();
        });
    }
    
    makeAction([new Dog(), new Fish()]);
    
    export {}; 
    

5.4 类的成员修饰符

5.4.1 privated
  • 只能在当前类中能访问,不能在外部和子类中访问。

  • class Person {
        private name: string = "";
        public name1: string = "";
        protected name2: string = "";
    }
    
5.4.2 public
  • 能在外部和子类中访问

  • class Person {
        private name: string = "";
        public name1: string = "";
        protected name2: string = "";
    }
    
5.4.3 protected
  • 不能再外部访问,只能再类和子类中能访问

  • class Person {
        private name: string = "";
        public name1: string = "";
        protected name2: string = "";
    }
    
5.4.4 属性的只读属性
  • 只读属性只能再构造器中赋值,赋值过后就不能修改了

  • class Person {
        readonly name: string;
    
        constructor(name: string) {
            this.name = name;
            this.name = "21312";
        }
    
    }
    
    console.log(new Person("dafsa").name);
    
5.4.5 静态属性和成员
class Student {
    static time: Date;
    static printDate() {
        console.log(Student.name);
    }
}

Student.printDate();

5.5 属性的get、set

  • class Person {
        private _name: string;
    
        constructor(name: string) {
            this._name = name;
        }
    
        set name(newName) {
            this._name = newName;
        }
    
        get name() {
            return this._name;
        }
    }
    

5.6 抽象类

  • 也就是只设定规范,不进行实现和使用的类。让其子类实现它的方法

  • 通过关键字 abstract, 抽象类中的方法必须被子类实现。

  • abstract class AbsBar {
      abstract absProps: string;
      abstract get absGetter(): string;
      abstract absMethod(name: string): string;
    }
    
    class Bar implements AbsBar {
      absProps: string = "acwink";
    
      get absGetter() {
        return "acwink";
      }
    
      absMethod(name: string) {
        return name;
      }
    }
    
    class Ab extends AbsBar {
      absProps: string = "acwink";
      get absGetter() {
        return "";
      }
      absMethod(name: string) {
        return name;
      }
    }
    

5.7 接口

  • 关键字 interface, 用来定义规范。接口只能被类来实现

  • interface IShape {
        getArea(): () => number;
    }
    
    class Rectangle implements IShape{
        private width: number;
        private height: number;
    
        constructor(width: number, height: number) {
            this.width = width;
            this.height = height;
        } 
    
        getArea():number {
            return this.width * this.height;
        }
    }
    
    class Circle implements IShape{
        private r: number;
        constructor(r: number) {
    
            this.r = r;
        }
    
        getArea(): number{
            return this.r * this.r * 3.14;
        }
    }
    
    function makeArea(a: IShape) {
        return a.getArea();
    }
    
    console.log(makeArea(new Circle(10)),
    makeArea(new Rectangle(10, 20)));
    
5.7.1 通过接口 定义索引类型
  • 一个类可以实现多个接口,只能继承一个

  • // interface 定义索引类型
    interface IndexLanguage {
        [index: number]: string;
    }
    
    const frontLanguage: IndexLanguage = {
        0: "HTML",
        1: "CSS",
        2: "JavaScript",
        3: "Vue"
    }
    
    
5.7.2 接口和type区别
  • 接口重复定义会合并,type不能重复定义

第六章 枚举

通过枚举类型将一些变量约束在一个命名空间下, 枚举类型区别雨对象类型,枚举类型时双向指定(仅有值为数字的枚举成员才能够进行这样的双向枚举),对象类型时单向指定

enum PageUrl {
  Home_Page_Url = "url1",
  Setting_Page_Url = "url2",
  Share_Page_Url = "url3",
}
// 如果没有声明枚举类型的值,默认会从 0 开始分配值
enum Items {
	Foo,
    Bar,
    Baz
}


// 常量枚举:只能通过枚举成员的方式访问枚举值,不能通过值的方式访问, 并且编译后不会存在Items
const enum Items {
    Foo,
    Bar,
    Baz
}

第七章 泛型

使用泛型的情况:当泛型被内部逻辑消费或者被返回值消费时使用,不要为了使用泛型而用泛型。

7.1 认识泛型

  • 让调用者指定函数类型, 不传递类型会自动类型推导

  • function sum<T>(a: T): T {
        return a;
    }
    console.log(sum<number>(20));
    
    console.log(sum<{name: string}>({name: "acwink"}));
    
7.1.1 泛型设置默认值

当泛型没有被传递,那么泛型的值就是设置的默认值

type Factory<T = boolean> = T | number | string;
7.1.2 对象类型中的泛型
// 描述了一个通用的响应类型结构,并预留出了响应的数据
interface IRes<TData = unknown> {
  code: number;
  error?: string;
  data: TData;
}

interface IUserProfileRes {
  name: string;
  homepage: string;
  avatar: string;
}

async function fetchUserProfile(): Promise<IRes<IUserProfileRes>> {
  return {
    code: 0,
    data: {
      name: "acwink",
      homepage: "/home",
      avatar: "xxx.com",
    },
  };
}

7.1.3 泛型的类型自动提取
function handle<T>(input: T): T {
  return input;
}

const Iname = handle("acwink"); // "acwink"
const age = handle(18);	// 18



7.1.4 箭头函数的泛型
// 箭头函数泛型的写法
// 箭头函数泛型的写法
const handle = <T extends any>(input: T): T => input;

const handle = <T>(input: T): T => input;


7.1. 5 类中的泛型

类可以声明泛型,类中的方法也可以单独声明泛型

class Queue<TElementType> {
  private _list: TElementType[];
  constructor(initail: TElementType[]) {
    this._list = initail;
  }

  // 入队一个队列泛型的子集元素
  enqueue<IType extends TElementType>(ele: IType): TElementType[] {
    this._list.push(ele);
    return this._list;
  }

  // 入队一个任意类型元素(无需为队列子类型)
  enqueueWithUnknownType<Type>(element: Type): (Type | TElementType)[] {
    return [...this._list, element];
  }

  // 出队
  dequeue(): TElementType[] {
    this._list.shift();
    return this._list;
  }
}

7.2 多个泛型

  • function foo<T, E, O> (a: T, b: E, c: O, ...args: T[]) {
    
    }
    
7.2.1 多泛型关联

只的是多个泛型通过条件执行逻辑运算

// 多泛型关联
type Conditional<Type, Condition, TruthyResult, FalsyResult> =
  Type extends Condition ? TruthyResult : FalsyResult;
7.2.2 多泛型依赖

指的前面定义的泛型,被后面的泛型依赖

type Partial<T, K extends keyof T> = {
    [P in K]?: T[P];
}

type ProcessInput<
  Input,
  SecondInput extends Input = Input,
  ThridInput extends Input = SecondInput
> = number;


7.3 泛型接口和泛型类

7.3.1 泛型接口
  • interface IPerson<T1 = string, T2 = number> {
        name: T1;
        age: T2;
    }
    
    const p: IPerson<string, number> = {
        name: "why",
        age: 123
    }
    
    
    
7.3.2 泛型类
  • class Point<T> {
        x: T;
        y: T;
        z: T;
    
        constructor(x: T, y: T, z: T) {
            this.x = x;
            this.y = y;
            this.z = z;
        }
    }
    
    const p1 = new Point<string>("123", "123", "123");
    const p2 = new Point(1,2,3); // 类型推导
    
7.3.3 泛型默认类型
  • type IGetRepeatArr<T = number> = (target: T) => T[];
    const getRepeatArr: IGetRepeatArr = (target) => new Array(100).fill(target);
    // 这里不传泛型默认为 number
    

7.4 泛型约束

当我们需要让某个工具类型被使用时需要满足一定的条件就需要用到泛型约束。

例如:通过 extends 关键字来约束传入的类型必须符合要求 A extends B 简单来说:A 中必须包含 B,B是A的子集

type Test<T extends { name: string; age: number }> = T | "number";
type A = Test<{ name: "acwink"; age: 18; kk: string }>;

// 在这个例子中,泛型ResCode 首先必须满足 传入类型或父类型是一个 number,然后通过内部进行条件判断,判断传入的值是否存在联合类型中,通过条件是否成立,返回对应的类型
type ResStatus<ResCode extends number = 10000> = ResCode extends
  | 10000
  | 10001
  | 10002
  ? "success"
  : "failure";

type Res1 = ResStatus<100>; // "failure"
type Res2 = ResStatus<10000>; // "successs"
type Res3 = ResStatus; // "success"


  • 对类型做约束与类型缩小不一样

  • interface ILength {
        length: number
    }
    // 要求传入的类型必须有 length 属性
    function getLength<T extends ILength>(arg: T) {
        return arg.length;
    }
    
    getLength(1);//报错
    getLength("12312");
    getLength([1,2]);
    
    

第八章 模块化和命名空间

  • 命名空间,像c++ 的 namespace。把模块再次划分作用域

  • namespace time {
        export function format(time: string) {
            return "2222-02-22";
        }
    
        export function foo() {
            console.log("time foo");
        }
    }
    
    namespace price {
        export function format(price: number) {
            return '$' + price;
        }
    }
    
    
    
    
    export {
        time,
        price
    }
    
    
    // 使用
    
    import { time, price } from './utils/format';
    
    time.format("12312");
    time.foo();
    price.format(123);
    

第九章 类型声明

  • 文件.d.ts 是一个声明文件

  • 当一个第三方库,没有类型声明文件

  • declare module 'lodash' {
        export function join(arr: any[]): void;
    
    }
    // 声明变量
    declare let name: string;
    
    // 声明函数
    declare function whyFoo(): void;
    
    
    declare module '*.jpg'; // 让 .jpg 结尾的文件当成模块
    declare module '*.png';
    declare module '*.gif';
    
    // 声明命名空间
    declare namespace $ {
        export function ajax(settings: any): any;
    }
    

第十章 类型工具

10.1 类型别名

type A = string;
type StatusCode = 200 | 301 | 400 | 500 | 502;
type PossibleDataTypes = string | number | (() => unknow);

type Handler = (e: Event) => void;
const clickHandler: Handler = (e) => {};


type ObjType = {
    name: string;
    age: number;
}

// 融入泛型的类型别名
type Factory<T> = T | number | number;
const foo: Factory<boolean> = true;

// 一般情况下不会直接使用工具类型直接做类型标注,而是在声明一个类型别名
type FactoryWithBool = Factory<boolean>;
const foo: FactoryWithBool = true;

// 一个有意义的工具类型, 这个工具类型可以处理存在null的情况
type MaybeNull<T> = T | null;
function process(input: MaybeNull<{handler: () => {}}>) {
    input?.handler();
}


type MaybeArray<T> = T | T[];
function ensureArray<T>(input: MaybeArray<T>): T[] {
    return Array.isArray(input) ? input:[input];
}


image-20221205112047886

10.2 Pick<T, U> 类型工具

它的作用是对一个对象结构进行裁剪

type IPartial<T> = {
  [P in keyof T]?: T[P];
};
function pick<T extends object, U extends keyof T>(
  object: T,
  ...props: Array<U>
): IPartial<Pick<T, U>> {
  const newObj: IPartial<Pick<T, U>> = {};
  props.forEach((key) => (newObj[key] = object[key]));
  return newObj;
}

const userinfo = {
  name: "acwink",
  age: 18,
  info: "无敌大帅哥",
};

const newUserinfo = pick(userinfo, "name");
console.log(newUserinfo);

10.3 属性修饰工具类型

主要使用的 属性修饰、映射类型、索引类型

type Partial<T> = {
  [P in keyof T]+?: T[P];
}


type Required<T> = {
  [P in keyof T]-?: T[P];
}

type Readonly<T> = {
  +readonly [P in keyof T]: T[P];
}

// ts 并没有提供去掉可读的属性
type Mutable<T> = {
    -readonly [P in keyof T]: T[P];
}
  • +? : 没有该修饰符的属性添加上该属性
  • -?: 相当于如果该属性原来有 ? 修饰符,则去掉,让该属性变成必选
  • +readonly: 没有 readonly 修饰符的属性被添加上该属性
  • -readonly: 存在readonly 修饰符的属性被出去掉该修饰符

10.4 结构工具类型

主要使用 条件类型、映射类型、索引类型

10.4.1 结构声明工具
// K extends keyof any 指的键的类型,这里可以传入单个类型,也可以是联合类型, keyof any = string | number | symbol
// T 为属性类型
type Record<K extends keyof any, T> = {
    [P in K]: T;
};
type Dictionary<T> = {
    [index: string]: T;
};

10.4.2 结构处理工具
// 用于提取对象类型的部分属性,并返回提取属性组成的对象类型
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};
interface Foo {
  name: string;
  age: number;
}

type PickFoo = Pick<Foo, "name">; // {name: string}

// 用于移除选中的属性类型, Exclude 表示求出 不属于K类型的联合类型,取交集的反
type myOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

10.5 集合工具类型

10.5.1 并集

两个集合合并,合并时重复的部分元素只会保留一份

// 并集
type Concurrence<A, B> = A | B;
10.5.2 交集

两个集合合并,合并时只保留重复部分的元素

// 这个会用分布式特性, 将联合类型展开在运算
type Extract<T, U> = T extends U ? T : never;


// 交集
type intersection<A, B> = A extends B ? A : never;

10.5.3 差集

A B, 两个集合,将 A 中属于 B 集合中的元素剔除,剩余的元素就是差集

type Exclude<T, U> = T extends U ? never : T;


// 差集
type Difference<A, B> = A extends B ? never : A;


10.5.4 补集

A B 两个集合, B 为 A 的子集 ~AnB + B = A

// 补集
type Complement<A, B extends A> = Difference<A, B>;

10.6 模板匹配工具类型

配合infer 关键字,消费传入的泛型,infer 的位置不同匹配到的泛型信息就不同

10.6.1 对函数签名的模式匹配
// 函数通过签名
type FunctionType = (...args: any[]) => any;

// 获取参数的类型
type Parameters<T extends FunctionType> = T extends (...args: infer P) => any ? P : never;

// 获取返回值类型
type ReturnType<T extends FunctionType> = T extends (...args: any[]) => infer R ? R : never;


// 只匹配第一个参数
type FirstParameter<T extends FunctionType> = T extends (arg: infer P, ...args: any) => any ? P : never;

10.6.2 对Class签名的模式匹配
// 类通用签名,声明了可抽象化,可实例化
type ClassType = abstract new (...args: any) => any;
interface ClassType<TInstanceType = any> {
  new (...args: any[]): TInstanceType;
}

type ConstructorParameters<T extends ClassType> = T extends abstract new (...args: infer P) => any ? P : never;

type InstanceType<T extends ClassType> = T extends abstract new (...args: any) => infer R ? R : any;
10.6.3 infer 约束
type FirstArrayItemType<T extends any[]> = T extends [infer P extends string, ...args: any[]] ? P : never;

10.7 模板字符串工具类型

10.8 NonNullable 类型工具

此工具用于从联合类型中剔除 null | undefined

type NonNullable<T> = T extends null | undefined ? never : T;
// 深度剔除
type DeepNonNullable<T extends object> = {
  [K in keyof T]: T[K] extends object
    ? DeepNonNullable<T[K]>
    : NonNullable<T[K]>;
};

10.9 Nullable 类型工具

此工具类需要开启 strictNullChecks 才能使用。

此类型用于添加 null类型至联合类型

type Nullable<T> = T | null;
type DeepNullable<T extends object> = {
  [K in keyof T]: T[K] extends object ? DeepNullable<T[K]> : Nullable<T[K]>;
}

10.10 Flatten 辅助工具

用于展开交叉类型的结构,将其展开为单层的对象结构

type Flatten<T> = { [K in keyof T]: T[K] };

type MarkPropsAsOptional<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Partial<Pick<T, K>> & Omit<T, K>>;

type MarkPropsAsOptionalStruct = MarkPropsAsOptional<
  {
    foo: string;
    bar: number;
    baz: boolean;
  },
  "bar"
>;

10.11 RequiredKeys

用于获取对象结构中可选属性,并返回属性组成的联合类型。

前置:{} extends {prop?: number} ? "Y": "N" 可以视为 {}{prop?: number} 的子类型。

type RequiredKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];

10.12 OptionalKeys

type OptionalKeys<T> = {
  [K in keyof T]: {} extends Pick<T, K> ? K : never;
}[keyof T];

10.13 比较全等Equal

// 用于比较 X, Y 类型是否全等,全等就包括了readonly 和 可选修饰符等
// (<T>() => T extends X ? 1 : 2)   () => T extends Y ? 1 : 2  并不会实际执行,用于辅助判断全等
type Equal<X, Y, A = X, B = never> = (<T>() => T extends X ? 1 : 2) extends <
  T
>() => T extends Y ? 1 : 2
  ? A
  : B;

10.14 MutableKeys 所有可变属性

// 得出所有可变属性
type MutableKeys<T extends object> = {
  [P in keyof T]-?: Equal<
    { [Q in P]: T[P] },
    { -readonly [Q in P]: T[P] },
    P,
    never
  >;
}[keyof T];

10.15 ImmutableKeys 所有不变属性

// 得出所有不可变属性
type ImmutableKeys<T extends object> = {
  [P in keyof T]: Equal<
    { [Q in P]: T[P] },
    { -readonly [Q in P]: T[P] },
    never,
    P
  >;
};

10.16 PickByValueType 基于重映射实现

键名如果编程never 将不会在对象结构中出现

type PickByValueType<T extends object, Type> = {
  [K in keyof T as T[K] extends Type ? K : never]: T[K];
};

10.17 OmitByValueType 基于重映射实现

type OmitByValueType<T extends object, Type> = {
  [K in keyof T as T[K] extends Type ? never : K]: T[K];
};

第十一章 结构化类型系统

ts 比较两个类型并不是通过类型的名称进行比较的,而是比较两个类型的属性和方法是否一致。

class Cat {
    meow() {}
    eat() {}
}

class Dog{
    meow() {}
    eat() {}
}
function feedCat(cat: Cat) {
    
}

feedCat(new Dog()); // 不会报错

鸭子类型: 如果你看到一只鸟走起来像鸭子,游泳也像鸭子,叫的也像鸭子,那么这只鸟就是鸭子。

第十二章 类型系统层级

类型系统层级指的是,TypeScript 中所有类型的兼容关系,从最上面一层 any 类型,到最底层的 never 类型。

12.2 函数类型的层级

对于函数类型的层级比较,最主要比较的是参数类型和返回值类型

协变:A <= B, A 是 B 的子类型,当A,B 分别称为函数类型存在关系 (T -> A) <= (T -> B),如果 A <= B, 则 Wrapper<A> <= Wrapper<B>

逆变:B <= A, B 是 A 的子类型,A,B组成的函数签名关系,(A -> T) <= (B -> T) ,如果 A <= BWrapper<B> <= Wrapper<A>

Wrapper: 指的是单个类型到函数类型的包装过程

class Animal {
  asPet() {}
}

class Dog extends Animal {
  bark() {}
}

class Corgi extends Dog {
  cute() {}
}

type DogFactory = (args: Dog) => Dog;

function transformDogAndBark(dogFactory: DogFactory) {
  const dog = dogFactory(new Dog());
  dog.bark();
}

type AsFuncArgType<T> = (arg: T) => void;
type AsFuncReturnType<T> = (arg: unknown) => T;

// 成立 (T -> Corgi) <= (T -> Dog)
type CheckReturnType = AsFuncReturnType<Corgi> extends AsFuncReturnType<Dog>
  ? 1
  : 2;

// 不成立:(Dog -> T) <= (Animal -> T)
type CheckArgType = AsFuncArgType<Dog> extends AsFuncArgType<Animal> ? 1 : 2;

总结:函数类型的参数类型使用子类型你便的方式确定是否成立,而返回值类型使用子类型协变的方式确定。

在TypeScript 开启函数逆变检查,通过在TsConfig 中配置 StrictFunctionTypes

{
    compilerOptions: {
        "strictFunctionTypes": true
    }
}

协变:接收子类型不接收父类型

逆变:接收父类型但不接收子类型

双变:既可以接收父类型又可以接收子类型

在typescript 的参数类型是 双变,但是这不是正确饿行为。

  • 理由:

十三章 类型编程

思路:将复杂的工具类型,拆解为由基础工具类型、类型工具的组合

13.1 递归

// 深度设置可选属性
type DeepParital<T extends object> = {
  [K in keyof T]?: T[K] extends object ? DeepParital<T[K]> : T[K];
};
// 深度设置必填属性
type DeepRequired<T extends object> = {
  [K in keyof T]-?: T[K] extends object ? DeepRequired<T[K]> : T[K];
};
// 深度设置只读
type DeepReadonly<T extends object> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
// 深度剔除只读属性
type DeepMutable<T extends object> = {
  -readonly [K in keyof T]: T[K] extends object ? DeepMutable<T[K]> : T[K];
};

// 深度从联合类型中剔除 null | undefined
type DeepNonNullable<T extends object> = {
  [K in keyof T]: T[K] extends object
    ? DeepNonNullable<T[K]>
    : NonNullable<T[K]>;
};

// 深度添加 null 至联合类型
type Nullable<T> = T | null;
type DeepNullable<T extends object> = {
  [K in keyof T]: T[K] extends object ? DeepNullable<T[K]> : Nullable<T[K]>;
}

13.2 基于已知属性进行部分修饰

13.2.1 设置部分可选
// 由Pick 提取出选中属性,Partial 选中属性组成的对象编程可选,Omit 剔除 选中的属性,& 交叉类型 合并两个对象
// Flatten 用于把交叉类型,展开成对象结构
type MarkPropsAsOptional<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Partial<Pick<T, K>> & Omit<T, K>>;

13.2.2 设置部分必选
type MarkPropsAsRequired<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Required< Omit<T, K> & Pick<T, K>>>;
13.2.3 设置部分只读
type MarkPropsAsReadonly<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Readonly<Pick<T, K>>>;
13.2.4 设置部分可写
type MarkPropsAsMutable<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Mutable<Pick<T, K>>>;
13.2.5 设置部分可为 null
type MarkPropsAsNullable<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Nullalbe<Pick<T, K>>>;
13.2.6 设置部分不能为 null
type MarkPropsAsNonNullable<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Omit<T, K> & NonNullable<Pick<T, K>>>;

13.3 基于键值裁剪结构

13.3.1 获取对象结构中函数的属性名
type FuncStruct = (...args: any[]) => any;
type FunctionKeys<T extends object> = {
    [K in keyof T]: T[K] extends FuncStruct ? K : never;
}[keyof T];
13.3.2 基于键值类型查找属性名
// ValueType 为传入的属性值
type ExpectedPropKeys<T extends object, ValueType> = {
  [K in keyof T]-?: T[K] extends ValueType ? K : never;
}[keyof T];
13.3.3 基于键值类型提取对象结构
// 通过 ExpectedPropKeys 已经把对应键值的属性名拼接成联合类型了。通过Pick直接提取对应结构即可
type PickByValueType<T extends object, ValueType> = Pick<
  T,
  ExpectedPropKeys<T, ValueType>
>;

13.3.4 基于键值类型过滤指定类型属性名,并返回剩余属性名
type FilteredPropKeys<T extends object, ValueType> = {
  [K in keyof T]-?: T[K] extends ValueType ? never : K;
}[keyof T];

13.3.5 基于键值类型过滤执行属性类型,并返回剩余属性类型组成的对象结构
type OmitByValueType<T extends object, ValueType> = Pick<
  T,
  FilteredPropKeys<T, ValueType>
>;

13.3.6 将 ExpectedPropKyes 和 FilteredPropKeys 合并在一起
// [Value] extends [Condition] 目的是为了方式传入联合类型,导致分布式比较
type Conditional<Value, Condition, Resolve, Reject> = [Value] extends [
  Condition
]
  ? Resolve
  : Reject;
// Positive 用来判断是选则,还是剔除
type ValueTypeFilter<T extends object, ValueType, Positive extends boolean> = {
  [Key in keyof T]: T[Key] extends ValueType
    ? Conditional<Positive, true, Key, never>
    : Conditional<Positive, false, never, Key>;
}[keyof T];

type PickByValueType<T extends object, ValueType> = Pick<
  T,
  ValueTypeFilter<T, ValueType, true>
>;

type OmitByValueType<T extends object, ValueType> = Pick<
  T,
  ValueTypeFilter<T, ValueType, false>
>;

13.3.7 严格基于键值类型提取对象结构
type StrictConditional<
  Value,
  Condition,
  Resolved,
  Rejected,
  Fallback = never
> = [Value] extends [Condition]
  ? [Condition] extends [Value]
    ? Resolved
    : Rejected
  : Fallback;

type StrictValueTypeFilter<
  T extends object,
  ValueType,
  Positive extends boolean = true
> = {
  [Key in keyof T]-?: StrictConditional<
    ValueType,
    T[Key],
    Positive extends true ? Key : never,
    Positive extends true ? never : Key,
    Positive extends true ? never : Key
  >;
}[keyof T];

export type StrictPickByValueType<T extends object, ValueType> = Pick<
  T,
  StrictValueTypeFilter<T, ValueType>
>;

13.4 结构互斥类型工具

13.4.1 互斥的两个用户结构

我们可以通过把另一个用户结构特有存在的属性,设置成never,这样赋值就会报错。

import { expectType } from "tsd";
interface VIP {
  vipExpires: number;
}

interface CommonUser {
  promotionUsed: boolean;
}

type Without<T, U> = {
  [P in Exclude<keyof T, keyof U>]?: never;
};

type XOR<T, U> = (Without<T, U> & U) | (Without<U, T> & T);

type XORUser = XOR<VIP, CommonUser>;

expectType<XORUser>({
  vipExpires: 0,
});

expectType<XORUser>({
  promotionUsed: false,
});

expectType<XORUser>({});

expectType<XORUser>({
  vipExpires: 100,
  promotionUsed: true,
});

type Flatten<T> = {
  [P in keyof T]: T[P];
};

type Tmp1 = Flatten<Without<VIP, CommonUser>>;

type Tmp2 = Flatten<Tmp1 & CommonUser>;

13.5 集合工具类型进阶

type Concurrence<A, B> = A | B;
type Intersection<A, B> = A extends B ? A : never;
type Difference<A, B> = A extends B ? never : A;
type Complement<A, B extends A> = Difference<A, B>;

// 使用更精准的类型描述对象属性名
type PlainObjectType = Record<string, any>;

// 属性名并集
type ObjectKeysConcurrence<
  T extends PlainObjectType,
  U extends PlainObjectType
> = keyof T | keyof U;

// 属性名交集
type ObjectKeysIntersection<
  T extends PlainObjectType,
  U extends PlainObjectType
> = Intersection<keyof T, keyof U>;

// 属性名差集
type ObjectKeysDifference<
  T extends PlainObjectType,
  U extends PlainObjectType
> = Difference<keyof T, keyof U>;

// 属性名补集
type ObjectKeysComplement<T extends U, U extends PlainObjectType> = Complement<
  keyof T,
  keyof U
>;

// 对象层面的交、补、差
type ObjectIntersection<
  T extends PlainObjectType,
  U extends PlainObjectType
> = Pick<T, ObjectKeysIntersection<T, U>>;

type ObjectDifference<
  T extends PlainObjectType,
  U extends PlainObjectType
> = Pick<T, ObjectKeysDifference<T, U>>;
// 在属性组成的集合类型,U 的属性联合类型是 T 属性联合类型的子类型,T extends U 在属性组成的集合类型中表示:U 属性组成的联合类型是 T 属性组成的联合类型的子类型。
type ObjectComplement<T extends U, U extends PlainObjectType> = Pick<
  T,
  ObjectKeysComplement<T, U>
>;

// 对应并集
// T对象独有的部分 + U 对象独有的部分 + T U 共有的部分
// 设,以 U 同名属性类型优先

type Merge<
  T extends PlainObjectType,
  U extends PlainObjectType
> = ObjectDifference<T, U> & ObjectDifference<U, T> & ObjectIntersection<U, T>;

type Assign<
  T extends PlainObjectType,
  U extends PlainObjectType
> = ObjectDifference<T, U> & ObjectIntersection<T, U> & ObjectDifference<U, T>;

// 不完全的合并,将U和T同名属性,用U的覆盖T,U特有的属性不合并到一起
type Override<
  T extends PlainObjectType,
  U extends PlainObjectType
> = ObjectDifference<T, U> & ObjectIntersection<U, T>;

// TU两个独有的属性,合并到一起
type ITest<
  T extends PlainObjectType,
  U extends PlainObjectType
> = ObjectDifference<T, U> & ObjectDifference<U, T>;

13.6 模式匹配工具类型进阶

// 通用函数类型
type FunctionType = (...args: any[]) => any;

// 实现提取函数参数的首个参数类型
type FirstParameter<T extends FunctionType> = T extends (
  arg: infer P,
  ...args: any[]
) => any
  ? P
  : never;

// 实现提取函数参数最后一个参数
// 1. 先判断传入的函数类型,是否只有一个参数
// 2. 如果传入的参数类型是多个参数,那么就判断元组,用 infer 获取元组的最后一个元素
type LastParameter<T extends FunctionType> = T extends (arg: infer P) => any
  ? P
  : T extends (...args: infer R) => any
  ? R extends [...any, infer Q]
    ? Q
    : never
  : never;

// test
type FuncFoo = (arg: number) => void;
type FuncBar = (...args: string[]) => void;
type FuncBaz = (arg1: string, arg2: boolean) => void;

type FooLastParameter = LastParameter<FuncFoo>; // number
type BarLastParameter = LastParameter<FuncBar>; // string
type BazLastParameter = LastParameter<FuncBaz>; // boolean

// Awaited<T> 内置工具类型
// 实现一个更为严谨的Promise内部值类型提取工具
// 这个更像提取的是 Promise.then() 执行后的返回值类型
type IAwaited<T> = T extends null | undefined
  ? T
  : T extends object & { then(onfulfilled: infer F): any }
  ? F extends (value: infer V, ...args: any) => any
    ? IAwaited<V>
    : never
  : T;

13.7 基于模板字符串类型实现的工具类型

13.7.1 Include 类型工具
// 第一步判断 Str 是否为空,如果为空, 则判断Search是否为空(为了满足 "".includes("") = true)
// 否则,_Include 进行匹配判断。

type _Include<
  Str extends string,
  Search extends string
> = Str extends `${infer _R1}${Search}${infer _R2}` ? true : false;

type Include<Str extends string, Search extends string> = Str extends ""
  ? Search extends ""
    ? true
    : false
  : _Include<Str, Search>;

13.7.2 Trim 类型工具
// type TrimLeft<V extends string> = V extends ` ${infer R}` ? R : V;

// type TrimRight<V extends string> = V extends `${infer R} ` ? R : V;

// type Trim<V extends string> = TrimLeft<TrimRight<V>>;

// 上面的剔除只能剔除一个,配合递归调用就能剔除全部

type TrimLeft<V extends string> = V extends ` ${infer R}` ? TrimLeft<R> : V;

type TrimRight<V extends string> = V extends `${infer R} ` ? TrimRight<R> : V;

type Trim<V extends string> = TrimLeft<TrimRight<V>>;

13.7.3 StartsWith 和 EndsWith
// StartsWith
type _StartsWith<
  Str extends string,
  Search extends string
> = Str extends `${Search}${infer R}` ? true : false;

type StartsWith<Str extends string, Search extends string> = Str extends ""
  ? Search extends ""
    ? true
    : false
  : _StartsWith<Str, Search>;

// EndsWith
type _EndsWith<
  Str extends string,
  Search extends string
> = Str extends `${infer R}${Search}` ? true : false;

type EndsWith<Str extends string, Search extends string> = Str extends ""
  ? Search extends ""
    ? true
    : false
  : _EndsWith<Str, Search>;

10.7.4 Replace 与 ReplaceAll
// Replace
type Replace<
  Str extends string,
  Search extends string,
  Replacement extends string
> = Str extends `${infer Head}${Search}${infer Tail}`
  ? `${Head}${Replacement}${Tail}`
  : Str;

// ReplaceAll

type ReplaceAll<
  Str extends string,
  Search extends string,
  Replacement extends string
> = Str extends `${infer Head}${Search}${infer Tail}`
  ? ReplaceAll<`${Head}${Replacement}${Tail}`, Search, Replacement>
  : Str;

type ReplaceAllRes1 = ReplaceAll<"www.acwink.com", "w", "m">; // "mmm.acwink.com"

// 合并到一起, 通过 ShouldReplaceAll 控制替换一个还是替换全部
type Replace<
  Input extends string,
  Search extends string,
  Replacement extends string,
  ShouldReplaceAll extends boolean = false
> = Input extends `${infer Head}${Search}${infer Tail}`
  ? ShouldReplaceAll extends true
    ? Replace<
        `${Head}${Replacement}${Tail}`,
        Search,
        Replacement,
        ShouldReplaceAll
      >
    : `${Head}${Replacement}${Tail}`
  : Input;

13.7.5 Split
type Split<
  Str extends string,
  Delimiter extends string
> = Str extends `${infer Head}${Delimiter}${infer Tail}`
  ? [Head, ...Split<Tail, Delimiter>]
  : Str extends Delimiter
  ? []
  : [Str];

type SplitRes1 = Split<"acwink,18,fe", ",">; // ["acwink", "18", "fe"]
type SplitRes2 = Split<"acwink 18 fe", " ">; // ["acwink", "18", "fe"]
type SplitRes3 = Split<"acwink", "">; // ["a", "c", "w", "i", "k"]
13.7.6 StrLength
type StrLength<T extends string> = Split<T, "">["length"];


type StrLengthRes1 = StrLength<"acwink">; // 6
13.7.7 Join
type Join<
  List extends Array<string | number>,
  Delimiter extends string
> = List extends []
  ? ""
  : List extends [string | number]
  ? `${List[0]}`
  : List extends [string | number, ...infer Rest]
  ? // @ts-expect-error
    `${List[0]}${Delimiter}${Join<Rest, Delimiter>}`
  : string;

type JoinRes1 = Join<["ac", "win", "k"], "-">; // "ac-win-k"
13.7.8 Case 转换相关类型工具

自动处理 “-”|“_”|" " 的驼峰化工具

type PlainObjectType = Record<string, any>;

type WordSeparator = "-" | "_" | " ";

type Split<
  S extends string,
  Delimiter extends string
> = S extends `${infer Head}${Delimiter}${infer Tail}`
  ? [Head, ...Split<Tail, Delimiter>]
  : S extends Delimiter
  ? []
  : [S];

type CapitalizeStringArray<Words extends readonly any[], Prev> = Words extends [
  `${infer First}`,
  ...infer Rest
]
  ? First extends undefined
    ? ""
    : First extends ""
    ? CapitalizeStringArray<Rest, Prev>
    : `${Prev extends "" ? First : Capitalize<First>}${CapitalizeStringArray<
        Rest,
        First
      >}`
  : "";

type CamelCaseStringArray<Words extends readonly string[]> = Words extends [
  `${infer First}`,
  ...infer Rest
]
  ? Uncapitalize<`${First}${CapitalizeStringArray<Rest, First>}`>
  : never;

type CamelCase<K extends string> = CamelCaseStringArray<
  Split<K extends Uppercase<K> ? Lowercase<K> : K, WordSeparator>
>;

自动驼峰第二种写法

type DelimiterCase2CamelCase<
  S extends string,
  Delimiter extends string
> = S extends `${infer L}${Delimiter}${infer R}`
  ? `${Capitalize<Lowercase<L>>}${DelimiterCase2CamelCase<R, Delimiter>}`
  : S extends ""
  ? ""
  : Capitalize<Lowercase<S>>;

// 字符串中只能有一种符号
type AutoCamelCase<
  S extends string,
  Delimiter extends string = "_" | "-" | " "
> = Delimiter extends Delimiter
  ? S extends `${infer L}${Delimiter}${infer R}`
    ? Uncapitalize<DelimiterCase2CamelCase<S, Delimiter>>
    : never
  : never;

// 应用到对象层面上,配合重映射类型
type CamelCasedProperties<T extends PlainObjectType> = {
  [K in keyof T as CamelCase<string & K>]: T[K] extends object
    ? CamelCasedProperties<T[K]>
    : T[K];
};
// Test
expectType<
  CamelCasedProperties<{ foo_bar: string; foo_baz: { nested_foo: string } }>
>({
  fooBar: "",
  fooBaz: {
    nestedFoo: "",
  },
});