TypeScript小笔记

2021-11-15 14:35:21 浏览数 (1)

陆陆续续从文档上手TypeScript,发现仍然还是有很多不懂。

比如各种框架的常用类型,ts中内置的常用类型,以及一些容易被忽略和遗忘的点,陆陆续续顺手把他们写到文章中记录起来。

TS

容易忽略的关键字

keyof

The keyof operator takes an object type and produces a string or numeric literal union of its keys

keyof操作符会将一个对象类型(注意这里是类型并不是值)的key组成联合类型返回。

代码语言:javascript复制
interface IProps {
  name: string;
  count: number;
}
type Ikea = keyof IProps; // Ikea = 'name' | 'count'

function testKeyof(props: Ikea): void { }
复制代码

extends

定义

Tsextends除了用在继承上,还可以表达泛型约束,通过extends关键字可以约束泛型具有某些属性。

其实extends关键字表示约束的时候,就是表示要求泛型上必须实现(包含)约束的属性。

Demo

比如

代码语言:javascript复制
function loggingIdentity<T>(arg: T): T {
  console.log(arg.length) // Ts语法错误 T可以是任意类型,并不存在length属性
  return arg
}
复制代码

我们定义一个接口来描述约束条件,创建一个包含 .length 属性的接口,使用这个接口和 extends 关键字来实现约束:

代码语言:javascript复制
interface Lengthwise {
  length: number
}

// 表示传入的泛型T接受Lengthwise的约束
// T必须实现Lengthwise 换句话说 Lengthwise这个类型是完全可以赋给T
function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length) // OK
  return arg
}
复制代码

现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:

代码语言:javascript复制
loggingIdentity(3);  // Error
复制代码

我们需要传入符合约束类型的值,必须包含必须的属性:

代码语言:javascript复制
loggingIdentity({length: 10, value: 3}) // OK
复制代码
常用

你可以声明一个类型参数,且它被另一个类型参数所约束。 比如,现在我们想要用属性名从对象里获取这个属性。 并且我们想要确保这个属性存在于对象 obj 上,因此我们需要在这两个类型之间使用约束。

代码语言:javascript复制
function getProperty<T, K extends keyof T> (obj: T, key: K ) {
  return obj[key]
}

let x = {a: 1, b: 2, c: 3, d: 4}

getProperty(x, 'a') // okay
getProperty(x, 'm') // error
复制代码

表示传入的两个参数,第二个参数被约束成为只能传入objkey类型。

内置类型

ReturnType<Type>

定义

Constructs a type consisting of the return type of function Type.

ResultType<type>接受传入一个函数类型为泛型,返回值为函数的返回类型。

Demo
代码语言:javascript复制
type T0 = ReturnType<() => string>; // type T0 = string

type T1 = ReturnType<(s: string) => void>; // type T1 = void

复制代码

查阅ReturnType源代码中的类型定义,发现使用了infer类型定义。

infer

定义

infer表示在 extends 条件语句中待推断的类型变量,必须联合extends类型出现。

demo

代码语言:javascript复制
type ParamType<T> = T extends (...args: infer P) => any ? P : T;


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

type Func = (user: User) => void;

type Param = ParamType<Func>; // Param = User
type AA = ParamType<string>; // string
复制代码

其实碰到infer关键字简单的将它理解为一个等待推断的类型(我现在不知道,得根据条件(extends)来判断)就可以了。

重点是:

  1. infer跟随extends成双出现。
  2. infer P表示类型P是一个待推断的类型。(不使用infer直接P会报错)

Record<Keys,Type>

构造一个对象类型,其属性键为Keys,属性值为Type。此实用程序可用于将一种类型的属性映射到另一种类型。

代码语言:javascript复制
type keys = 'name' | 'title' | 'hello';

interface values {
  name: string;
  label?: number;
}

// Record内置类型可以将 传入的keys联合类型遍历作为key 
// 为每一个key的value赋值为 values从而形成一个全新的对象类型返回
const b: Record<keys, values> = {
  name: {
    name: 'wang',
    label: 1,
  },
  title: {
    name: 'hellp',
  },
  hello: {
    name: 'nihao',
  },
};

复制代码

看看它的源码本质上很简单,就是遍历传入的范型T作为key,将传入的范型value作为值的类型。

Pick<Type, Keys>

Pick的定义很简单,就是从传入的Type中跳出对应的Keys属性,从而返回新的类型。

代码语言:javascript复制
interface Props {
  name: string;
  label: number;
  value: boolean;
}

type ChildrenProps = Pick<Props, 'name' | 'label'>;
复制代码

Parameters<T>

Parameters<T>用于获得函数的参数类型组成的元组类型

源码中这样定义的

代码语言:javascript复制
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any 
  ? P : never;
复制代码

Exclude<T,U>

Exclude是进行排除联合类型中的一部分内容,相对于下面的Omit操作符来说Omit是针对于key&value/接口形式的,而Exclude是针对于联合类型来操作的。

代码语言:javascript复制
let a: string | number;

type CustomType = Exclude<typeof a, string>; // number类型
复制代码

Exclude的原理

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

传入两个泛型

我们这里用 typeof a 也就是 string | number 去代表 Tstring 属性去代表第二个泛型 U

T extends U 就判断是否string | numberstring, 有string就返回never,就代表将其排除

Omit<T, K>

3.5 版本之后,TypeScript 在 lib.es5.d.ts 里添加了一个 ​Omit<T, K>​ 帮助类型。​Omit<T, K>​ 类型让我们可以从另一个对象类型中剔除某些属性,并创建一个新的对象类型。

比如:

代码语言:javascript复制
`type User = {`

`id: string;`

`name: string;`

`email: string;`

`};`

`type UserWithoutEmail = Omit<User, "email">;`

`// 等价于:`

`type UserWithoutEmail = {`

`id: string;`

`name: string;`

`};`
复制代码

new关键字

用法

tsnew()表示构造函数类型。

当我们声明一个类的时候,其实声明的是这个类的实例类型和静态类型两个类型。

  • 类的静态类型包含类的所有静态成员和构造函数
  • 类的实例类型包含类的实例方法和属性(包括原型上的实例方法和属性)。

比如

代码语言:javascript复制
// type: { new(): T }
// 表示函数接受的参数type,是一个对象,这个对象有一个构造函数返回T
// 换句话说type是一个类类型
function factory<T>(type: { new (): T }): T {
	return new type();
}

class Person {
	static myName; // 类的静态属性
	public yourName; // 类的实例属性
}

// 传入的范型Person指类的实例类型
const person = factory<Person>(Person);

复制代码

typeof 类

ts中通过typeof 类可以获得类的类类型,直接使用类作为类型此时使用的是类的实例类型。

代码语言:javascript复制
let a: Person; // Person表示类的实例类型
a.yourName;
let b: typeof Person; // typeof Person 表示类的类类型
b.myName;
let c: { new (): Person } = Person; // c为构造函数类型,c拥有一个构造函数,也就是new c() 返回的是Person的实例。表示c是Person类。
复制代码

React & TS内置类型

React.ReactNode

源码类型中关于ReactNode的类型定义

代码语言:javascript复制
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
复制代码

可以看到ReactNode是一个类型别名,他是多种类型组成的联合类型。

其中ReactChildtype ReactChild = ReactElement | ReactText;

ReactPortal定义

代码语言:javascript复制
 interface ReactPortal extends ReactElement {
        key: Key | null;
        children: ReactNode;
 }
复制代码

所以ReactNode是包含ReactElement类型的联合类型。换句话说ReactElement可以赋给ReactNode,但反过来不可以。

React.element

代码语言:javascript复制
interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
        type: T;
        props: P;
        key: Key | null;
    }
复制代码

可以看到React.Element类型接受传入两个泛型分别是jsx编译后的vdom对象的props组件本身

返回的仅仅是包含type,props,key的一个Object对象。

0 人点赞