TS的FunctionComponent案例

798 阅读5分钟

泛型

在 TypeScript 中,当我们想要描述两个值之间的对应关系时,会使用泛型

泛型就像一条管道,将参数的类型流入到参数体内

函数泛型参数

虽然知道返回值是string类型,但是还是可以调用parseInt方法,没有报错。 image.png

使用泛型后,类型可以深入到函数内部

//案例将类型推断为string类型,只适用于基础数据类型
function echo<T>(arg: T): T {
  return arg;
}
let test = echo("str");
test.parInt();
console.log(test);

image.png

函数多个泛型参数

//返回值自动被定义了对应类型
function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]];
}
const result = swap(["string", 123]);

image.png

promise的案例

function withAPI(url: string) {
  return fetch(url).then((resp) => resp.json());
}
withAPI("github.user").then((resp) => {});

image.png

interface GithubResp {
  name: string;
  count: number;
}

function withAPI<T>(url: string): Promise<T> {
  return fetch(url).then((resp) => resp.json());
}
// 使用接口GithubResp定义了resp的类型
withAPI<GithubResp>("github.user").then((resp) => {});

image.png

react的案例

import * as React from "react";

const Test: React.FunctionComponent = (props) => {
  console.log(props);
  return (
    <div>
      {props.name}
      {props.desc}
    </div>
  );
};

Test.displayName = "TestDis";

export default Test;

普通函数组件如果不添加类型,则props为any,也不会有props属性提示:

image.png

给普通函数组件添加React.FunctionComponent类型定义

  • 会报错 image.png

FunctionComponent添加泛型<Props>之后,props和Test类上的静态属性上都有了name和desc属性,可以自动补全属性了

  • 上面报错是有因为React.FunctionComponent是一个泛型接口,props默认类型定义为{}

image.png

涉及知识点1:接口泛型

案例

// react源代码中的定义
interface FunctionComponent<P = {}> {  //泛型在接口上默认值为{},所以不传泛型也可以走下去
    (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null; //props: P 将泛型接口接口<Props>传递给了props,并基于<Props>进行转换
    propTypes?: WeakValidationMap<P>; //propTypes: P 将接口传递给了propTypes,并基于<Props>进行转换
    contextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>; // defaultProps: P 将接口传递给了defaultProps,并基于<Props>进行转换
    displayName?: string;
}

image.png

image.png

从案例中学到:

  • 泛型接口可以定义默认值 interface FunctionComponent<P = {}> { }
  • 函数泛型接口可以将函数本身类型定义在接口内部
    • FunctionComponent为函数的接口,但是接口内部也定义了函数本身的类型
    • (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
  • 泛型P可以传递给属性,并可以基于该泛型P进行转换
    • 这就是props除了自定义的name和desc还有children属性? image.png
    //源码
    interface FunctionComponent<P = {}> {
                (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
            }
    type PropsWithChildren<P> = P & { children?: ReactNode }; //type 类型别名, &为合并类型,将P和类型{ children?: ReactNode }进行合并,所以看到props有三个属性name,desc,children
    

涉及知识点:

泛型约束 extends

function echo<T>(args:T):T{
    console.log(args.length); //报错,因为并不知道T是什么类型,T上没有length属性
    return args
}

改写

interface LengthType {
    length: number;  //只要泛型T有length属性就可以使用。 duck typing
}
function echo<T extends LengthType>(args:T):T{
    console.log(args.length); //报错,因为并不知道T是什么类型,T上没有length属性
    return args
}
echo('123');
echo({length: 123});

ts条件类型

type NoneType<T> = T extends undefined | null ? void : never;

let d1: NoneType<number>; //d1: never
let d2: NoneType<string>; //d2: never
let d3: NoneType<undefined>; //d3: void

image.png

keyof Type

  • keyof关键词后面为一个接口类型,拿到类型User的所有属性
  • 注意返回类型为"id" | "name联合类型
  • 一般结合in关键词使用
type User = { id: string; name: string; };
type UserKeys = keyof User; //"id" | "name"

image.png

in

  • 单独使用keyof Type,只能拿到一个常量类型"id" | "name"
  • 结合in使用,一行代码,可以搞定多个类型,并对类型进行转化,类型js中的循环
interface User {
  name: string;
  desc: string;
}
// 定义类型,将User中必选的类型都转化为可选
type Users = {
  [key in UserKey]?: string;
};
let users: Users = {
  
}

image.png

带着知识点继续看React.FunctionComponent关于propTypes属性的定义

interface FunctionComponent<P = {}> {
    propTypes?: WeakValidationMap<P>;
}
// 如果name是可选
interface Props {
  name?: string;
  desc: string;
}

const Test: React.FunctionComponent<Props> = (props) => {
  return (
    <div>
      {props.name}
      {props.desc}
    </div>
  );
};

Test.propTypes?.name; //name为可选的,则propTypes.name也是可选的

image.png

// name如果是必选项
interface Props {
  name: string;
  desc: string;
}

const Test: React.FunctionComponent<Props> = (props) => {
  return (
    <div>
      {props.name}
      {props.desc}
    </div>
  );
};

Test.propTypes?.name; //name是必选项,propTypes?.name也会转化为可选的

image.png 根据测试得知:propTypes属性类型是将泛型P转化为可选的

interface FunctionComponent<P = {}> {
    defaultProps?: Partial<P>;
    propTypes?: WeakValidationMap<P>;
}
type WeakValidationMap<T> = {
        [K in keyof T]?: null extends T[K]
            ? Validator<T[K] | null | undefined>
            : undefined extends T[K]
            ? Validator<T[K] | null | undefined>
            : Validator<T[K]>
    };

ts源码: 来看看它的实现方法

/**
 * Make all properties in T optional
 */
type Partial<T> = {
    [P in keyof T]?: T[P];
};

源码泛型.png 理解伪代码:

interface Props {
  name: string;
  desc: string;
}
type P = Props;
 interface FunctionComponent<P = {}> {
    defaultProps?: Partial<P>;
}
type Partial<T> = {
    [P in keyof T]?: T[P];
};

类型 keyof T = name|desc
=》使用in循环属性name|desc 

自己写个例子:

// 使用Partial<PersonInteface>后必填属性都变为可选的了
interface PersonInteface {
  name: string;
  age: number;
}
const person: Partial<PersonInteface> = {};

image.png

TS内置类型

Promise<T>

用法Promise<T>

// Promise<RespInterface>的用法, 是定义Promise返回的数据的类型为RespInterface
interface RespInterface {
  name: string;
  desc: string;
}

function Fetch<T>(params: object, method: string): Promise<RespInterface> {
  return Promise.resolve({ name: "zhangsan", desc: "desc" });
}

Partial<T>

Partial<P>将传入的属性都变为可选属性

Rquired<T>

Partial相反,将所有属性转换为必选

interface CountryInterface {
  name: string;
  population: number;
  desc?: string;
}

type OneCountryType = Required<CountryInterface>;

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

image.png

源码: -?: 表示必填

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

源码:

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

ReadOnly<T>

type t = Readonly<CountryInterface>;

image.png

源码:

type OtherReadonly<T> = {
   readonly [P in keyof T]: T[P]
}

Pick<T,U>

  • 取出接口T中属性U的类型,组成一个新的类型
  • U是单个类型或联合类型
  • U keyof T
interface CountryInterface {
  name: string;
  population: number;
  desc?: string;
}
type pType = Pick<CountryInterface, "name" | "population">;
let data: pType={};

image.png

Exclude<T,U>

  • Pick相反
  • 从类型T中剔除U属性,组成一个新的类型
  • U keyof T
type testTypes = "a" | "b" | "c";
type TypeEXs = Exclude<testT, "c">; //U可以是联合类型;type TypeEXs = Exclude<testT, "c" | "a">;
const data :TypeEXs = 'a'; //data只能是'a'或'b';

image.png

Omit<T,U>

  • 从对象类型中剔除某些类型
  • Exclude<T,U>类似,暂时没发现两则的区别
interface RespInterface {
  name: string;
  desc?: string;
  population?: number;
  a?: never;
}
type Types = Omit<RespInterface, "population">;

等价于

type Types =  {
  name: string;
  desc?: string;
  a?: never;
}