likes
comments
collection
share

TS-实现 Pick

作者站长头像
站长
· 阅读数 47

TS-实现 Pick

题目链接

实现 TS 内置的 Pick<T, K>,但不可以使用它。

从类型 T 中选择出属性 K,构造成一个新的类型

例如:

interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}

一、关键词说明

type :类型保护

type 作用就是给类型起一个新名字(别名),支持基本类型、联合类型、元祖及其它任何你需要的手写类型,常用于联合类型,与接口一样,用来描述对象或函数的类型

typeof就是js中的typeof, ts会根据你代码中出现的typeof来自动推断类型:

 // 通常type是这样定义类型的
  type PersonType = {
    name: string
    age: number
  }

  let people = {
    name: 'people',
    age: 20,
  }
  //typeof 可以由一个普通对象来定义一个类型A出来,然后用这个类型A去约束其他的变量
  type PersonType1 = typeof people 
  let p1: PersonType = {
    name: 'man',
    age: 12,
  }

pick

Pick在 TS 中的源码实现:

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

它的作用是从 T 中将所有的 K提取出来,并生成一个新的类型。下面示例中定义的User类型包含了两个必选属性 stature 和 age。所以,将people指定为User类型之后,就肯定必须包含这两个属性,否则就会报类型检查错误:

interface User {
  stature: number
  age: number
}

const people: User = {
  stature: 186,
  age: 20,
}

但是,如果我想让people只包含User类型的 stature 属性,就可以用Pick这样来实现。它就是告诉 TS 仅仅将 stature 属性从User中提取出来即可。

// success,使用 Pick 生成的新类型确实只包含 stature 属性
const people: Pick<User, 'stature'> = {
  stature: 185,
}

// error,使用 Pick 生成的新类型中并不包含 age 属性
const people: Pick<User, 'stature'> = {
  age: 17,
}

PS :Pick是将类型中的所有的属性中某一个属性单独提取出来。

interface

interface官方链接

它相当于类型中的 JS 对象,用于对函数、类等进行结构类型检查,所谓的结构类型检查,就是两个类型的结构一样,那么它们的类型就是兼容的,这在计算机科学的世界里也被成为 “鸭子类型”。

提示 什么鸭子类型? 当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。

现在我们要这个 todo对象 做一个类型注解,根据之前提到的 “鸭子类型” 的方式,我们可以定义一个 Interface 来为它做注解:

interface Todo {
  content: string;
  user: string;
  time: string;
  isCompleted: boolean;
}

const todo: Todo = {
 // ...
}

type生成interface

 interface IPerson {
    name: string
    age: number
    like: Array<string>
  }
  interface IPickedPerson extends Pick<IPerson, 'name' | 'like'> { } //生成新的接口
  const p: IPickedPerson = { name: 'zs', like: ['a', 'b'] }

二、pick 使用方式

场景:从一个复合类型中,取出几个想要的类型的组合。

// 原始类型
interface TState {
	name: string;
	age: number;
	like: string[];
}
// 如果只想要name和age最直接就是再定义一个
interface TSingleState {
	name: string;
	age: number;
}
// 这样的弊是在Tstate发生改变的时候,TSingleState并不会跟着一起改变,所以应该使用Pick
interface TSingleState extends Pick<TState, "name" | "age"> {};

一般来说我们使用extends是用来继承的,但是写在泛型里的作用又是什么呢? 泛型约束官方文档

之前的一个例子,我们有时候想操作某类型的一组值,并且我们知道这组值具有什么样的属性。 在 demo例子中,我们想访问valuelength属性,但是编译器并不能证明每种类型都有length属性,所以就报错了。

function demo<T>(value: T): T {
    console.log(value.length);  // Error: T doesn't have .length
    return value;
}

相比于操作any所有类型,我们想要限制函数去处理任意带有.length属性的所有类型。 只要传入的类型有这个属性,我们就允许,就是说至少包含这一属性。 为此,我们需要列出对于T的约束要求。

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

interface Lengthwise {
    length: number;
}

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

//现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:
demo(3);  // Error, number doesn't have a .length property
//需要传入符合约束类型的值,必须包含必须的属性:
demo({length: 10, value: 3});

三、题解

interface Todo {
  title: string
  description: string
  completed: boolean
}
// 从 T 中取出 一系列 K 的属性
type MyPick<T, K extends keyof T> = { [S in K]: T[S] }

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}

在泛型中使用extends并不是用来继承的,而是用来约束类型的。所以这个K extends keyof T,应该是说key被约束在T的key中,不能超出这个范围,否则会报错的。

转载自:https://juejin.cn/post/7005376193738309663
评论
请登录