React: React Typescript CheatSheet

TypeScript


TypeScript

1. 删除对象中某些key

const makeKeyRemover =
  <Key extends string>(keys: Key[]) =>
  <Obj extends object>(obj: Obj): Omit<Obj, Key> => {
    return {} as any;
  };
 
  
 
const keyRemover = makeKeyRemover(['a', 'b']);
const x = keyRemover({ a: 1, b: 2, c: 3 });
x.c;

2.自定义类型错误信息

type checkArgs<Args> = Args extends any[] ? '不可以输入array类型' : Args;
 
function foo<Arg>(args: checkArgs<Arg>) {}
 
  
foo(123);
 
foo([1]); // 类型“number[]”的参数不能赋给类型“"不可以输入array类型"”的参数。

3. 获取对象/数组/枚举的key和vlaue类型

// 对象
interface Colors {
  foo: 'blue';
  var: 'red';
}
type ColorValues = Colors[keyof Colors];
 
// 数组
type Letters = ['a', 'b', 'c'];
type Letter = Letters[number];
 
// 枚举
enum Colors {
  RED = 'red',
  GREEN = 'green',
  BLUE = 'blue'
}
 
type ColorKey = keyof typeof Colors; // "RED" | "GREEN" | "BLUE"
type ColorValue = `${Colors}`; // "red" | "green" | "blue"
 
// 新学到的写法
type K = {
	a: '1',
	b: '2',
	c: '3'
}
type O = K[typeof K]; // O = '1' | '2' | '3'

4. 定义全局变量类型的几种方式(Window、Global、Node、ProcessEnv)

Vite Monorepo TypeScript 环境变量处理

declare global {
	/* global 扩展 */
	var __DEV__: boolean;
	function isCool(): string;
	
	/* window 扩展 */
	interface Window {
		wow(): boolean;
	}
 
	/* Node 扩展 */
	namespace Node {
		/* process env */
		interface ProcessEnv {
			IS_DEV: boolean;
		}
	}
}
  
 
/**
* globalThis 一个新的提案
* 引入了一种在任何 JavaScript 环境下访问全局this的机制
* 不管是浏览器 Web、Node 还是 Deno 中,都可以使用 globalThis 来访问全局变量
* 旨在通过定义标准的全局属性来整合越来越分散的访问全局对象的方式
* window this self frames global
*/
globalThis.__DEV__ = true;
globalThis.isCool = () => 'A';
 
window.wow = () => true;
 
process.env.IS_DEV;
 
// export 用于指定文件为模板 消除报错
export {};
 

5. Import type 两种用法


最开始TypeScrript支持使用 import type 的方式单独引入类型,并且不会再打包的时候,将代码打包进入,例如 import 枚举值的时候

import type { FC } from 'react'

但是这样有一个问题,就是如果既需要引入Type又需要引入其他模块的时候,需要写两个 import

import type { FC } from 'react';
import { useState } from 'react';

这样写会比较麻烦,所以后面 ts 又出了括号内的 type:

import { useState, type FC } from 'react';

如果只需要引入类型的时候使用 import type {} 'module' ,又有成员导入又有type导入的时候,使用括号内的 import { type A } from 'module'.

6. v4.9 satisfies 特性

satusfies 主要提供两个方面的功能

  • 类型安全检查,确保变量匹配指定的类型,比as更安全
  • 使 TypeScript 根据提供给此变量的值然后推断出具体类型
// 定义CityName联合类型
type CityName = "New York" | "Mumbai" | "Logos";
 
// CityCoordiates 是一个Object类型
type CityCoordiates = {
    x: number;
    y: number;
}
 
// City 可能是字符串联合类型,也可能是Object类型
type City = CityName | CityCoordiates;
 
type User = {
    birthCity: City;
    currentCity: City;
}

上面的User是一个Object类型,下面两个字段,birthCitycurrentCity,这两个字段都有可能是City或者CityCoordiatees 类型
定义 user变量:

const user:User = {
	birthCity: 'New York',
    currentCity: {
        x: 100,
        y: 200
    }
}

user变量类型是满足User类型的,因为我们指定了 User类型,所以最终创建的变量类型为:

user.birthCity // City
user.currentCity // City

这种情况下如果我们想要对某个属性调用方法,TypeScript是无法知道属性具体是哪个类型的,到底是 CityName联合类型还是CityCoordiates对象类型,因此无法通过类型检查

// 为user.birthCity 调用字符串方法
// err: Property 'toUpperCase' does not exist on type 'City'.
// err: Property 'toUpperCase' does not exist on type 'CityCoordiates'.
// 虽然我们定义的user.birthCity是一个字符串,但是TypeScipt判断它既可以是字符串
// 也可能是一个对象,所以无法调用toUpperCase方法
user.birthCity.toUpperCase();

在没有satisfies之前,没有更好的办法处理这种问题,具体来说就是不可能同时:

  1. 确保变量匹配指定的类型
  2. 使TypeScript根据提供给此变量的值然后推断出具体的类型

使用satisfies处理

const user = {
	birthCity: 'New York',
    currentCity: {
        x: 100,
        y: 200
    }
} satisfies User;

通过 satisfies处理之后的 user类型为

const user2: {  
	birthCity: "Mumbai";  
	currentCity: {  
		x: number;  
		y: number;  
	};  
}

可以看到 TypeScript根据我们定义的变量自动推导出了具体的属性类型,下面代码不会报错

user.birthCity.toUpperCase();
user.currentCity.x.toFixed(2);

如果我们提供的变量不满足 satisfies指定的类型时,TypeScript会抛出错误, 所以使用 satisfies来替代as会更加的安全。

const user = {
	// error: birthCity 不满足 User.birthcity 定义的联合类型
	// // error The expected type comes from property 'birthCity' which is declared here on type 'User'
	birthCity: 123456,
	currentCity:{
		x: 1,
		y: 1
	}
} satisfies User;

目的在于不改变变量类型的情况下对变量强制执行约束,比 as更加严谨,可替换as使用。

type RGB = readonly [red: number, green: number, blue: number];
type Color = RGB | string;
const myColor: Color = 'red';
 
// 不知道 myColor 是元祖还是 string,无法安全的调用 toUpperCase 方法
myColor.toUpperCase();
 
// as 不会判断类型是否满足约束条件,属于强制转换
const myColor2 = 1 as unknown as Color;
 
// 通过 as 强制装换变量类型,同样无法知道其真实类型是什么
myColor2.toUpperCase();
 
// satisfied 不强制转换,会验证变量类型是否满足要转换的类型条件
const myColor3 = 'red' satisfies Color;
// 当需要强制转换的类型不满足时,会报错
const myColor4 = 123 satisfies Color;

7. 利用 declare Global 定义全局工具类

一些通用的TS 工具函数,经常会使用到,但是又不想每次都 import 一下,可以将其定义在 declare global 中,可以新建一个文件,专门存放这些需要全局使用的工具类

// util.ts
declare global {
	type Maybe<T> = T | undefind | null
}

来源:https://twitter.com/mattpocockuk/status/1593584053042630657


8. Type

8.1 条件类型

在类型系统中充当“if语句”。通过泛型创建,然后通常用于减少类型并集中的选项数量。

type Bird = { legs: 2 };
type Dog = { legs: 4 };
type Ant = { legs: 2 };
type Wolf = { legs: 4 };
 
// 通过extends获取有四条腿的类型
type HasFourLegs<Animals> = Animals extends { legs: 4 } ? Animals : never;
 
type Animals = Bird | Dog | Ant | Wolf;
type FourLegsAnimals = HasFourLegs<Animals>;
// type FourLegsAnimals = Dog | Wolf

8.2 模板字符串组合类型

模板字符串可用于组合多个字符串类型为一个新的类型

type Langs = 'en' | 'zh' | 'pt';
type FooterLocaleIDs = 'header' | 'footer';
 
type All = `${Langs}_${FooterLocaleIDs}_id`;
 
// type All = "en_header_id" | "en_footer_id" | "zh_header_id" | "zh_footer_id" | "pt_header_id" | "pt_footer_id"

8.3 映射类型

允许修改输入类型的结构,返回新类型

type Name = 'Yang' | 'Li';
type Artist = { name: Name, age: number };
 
/*
* 将一个对象转换成一个 key 为函数名,value 类型为参数的函数对象
*/
type Subscriber<Type> = {
  [Property in keyof Type]: (newValue: Type[Property]) => void
}
 
type ArtistSub = Subscriber<Artist>;
// 等价于
type ArtistSub = {
    name: (newValue: Name) => void;
    age: (newValue: number) => void;
}

8.4 可区分联合类型

👉 TypeScript 可区分联合类型

9. 根据不同的type限制不同的类型选项

typeA 是,可以输入的key只能是 A的类型,为 B是只能是B的类型选项

type AProps = {
  type: 'A'
  a: string;
};
 
type BProps = {
  type: 'B',
  b: number
};
 
type ConfigProps = AProps | BProps;
 
const config: ConfigProps = {
  type: 'A',
  a: '',
  // b: 2 // Error
};
 
const config2: ConfigProps = {
  type: 'B',
  b: 2,
  // a: '', // Error
};

10. (美化)将交集类型提示平铺展示

Matt Pocock 在 Twitter: “Here’s a quick thread on a super useful type helper you’ve probably never heard of (nope, not even advanced folks). https://t.co/HSCQueVNXO” / Twitter

 
/**
 * 类型提示是一个交集类型
 */
export type Intersected = {
  a: number;
} & {
  b: string;
} & {
  c:boolean;
};
 
export type Prettify<T> = {
  [K in keyof T]: T[K];
} & {}
 
/**
 * 转换之后类型被平铺
 */
 
type Result = Prettify<Intersected>;

11.类型守卫用法

类型守卫 | 深入理解 TypeScript
类型守卫推荐使用 instanceof

function fn(a) {
	if (a instanceof Object) {
		..
	}

	if (a instanceof Function) {
		..
	}
	
	..
}

12.cosnt 类型断言 & const 类型参数

在 5.0 之前可以使用的 as const 类型断言,这种断言只能针对对象类型,无法对函数类型进行处理

const obj = { name: 'Yang' } as const;
 
// 使用前: { name: string }
// 使用后: { readonly name: string }

TypeScript 5.0 提供了 const 类型参数

function fn<T>(input: T) {
	return T;
}
 
const obj = fn({ name: 'Yang' }); // { name: string }

如果不想得到的obj 对象中 name 的类型为 string, 而是字符串Yang,可以先通过 as const 类型断言来转换 input 的类型,再传入函数

const obj = fn({ name: 'Yang' } as const); // { readonly name: Yang }

这种写法只能针对对象,在函数调用之前处理,如果想在函数内部处理,可以通过 5.0 的 const 类型参数

function fn<const T>(input: T) {
	return input;
}
 
const obj = fn({ name: 'Yang' }); // { readonly name: Yang }

13.使用$字符串匹配类型

从对象中获取以on开头的所有键。
image.png

14. 模板字符串高级使用

模板字符串高级用法

15. string & {} 文本类型与字符串类型联合

Icon 组件定义 Color 类型如下,本意是既可以传入 primarysecondary ,也可以传入任意颜色,但是实际上只有 string 类型生效, primarysecondary 失效

type Color = "primary" | "secondary" | string ;

解决方案
将 string 类型与一个空对象相交

type Color = "primary" | "secondary" | ( string & {});

原因:

当字符串类型文字类型与string类型创建联合时, Typescript 会立即将类型扩展为 string 类型。因此在使用 Color 类型之前,Typescript 就已经忘记了 primary 与 secondary。但是通过 string 类型与空对象相交,可以欺骗 TypeScript 保留字符串文字类型更长时间。