Open worldzhao opened 2 years ago
import React, { HTMLAttributes, PropsWithChildren } from "react"; interface IHelloProps extends HTMLAttributes<HTMLDivElement> { name: string; } const Hello: React.FC<PropsWithChildren<IHelloProps>> = ({ name, children, ...rest }) => { return ( <div> <div {...rest}>{`Hello, ${name}!`}</div> {children} </div> ); };
PropsWithChildren
IHelloProps
children
React.FC
Props
react@16
html
className
style
extends HTMLAttributes<HTMLDivElement>
HTMLDivElement
HTMLInputElement
不推荐 React.FC?
Remove React.FC from Typescript template #8177
在这个 PR 里移除了 CRA 默认模板的 React.FC,主要有以下几点理由:
<Select.Option>
好处则是提供了返回值约束。
站在今天回看:
所以是否使用 React.FC 可以自行选择:
若对于返回值有明确的类型要求,配置了 typescript 规则,那么可以使用 React.FC,其他时候可以直接定义 Props interface,如下所示:
import React, { HTMLAttributes, PropsWithChildren } from "react"; interface IHelloProps extends HTMLAttributes<HTMLDivElement> { name: string; } const Hello = ({ name, children, ...rest }: PropsWithChildren<IHelloProps>) => { return ( <div> <div {...rest}>{`Hello, ${name}!`}</div> {children} </div> ); }; // 作为命名空间 Hello.World = () => <div>hello, world</div>;
React 提供了 forwardRef 函数用于转发 Ref,该函数也可传入泛型参数,如下:
forwardRef
import { forwardRef, PropsWithChildren } from "react"; interface IFancyButtonProps { type: "submit" | "button"; } export const FancyButton = forwardRef< HTMLButtonElement, PropsWithChildren<IFancyButtonProps> >((props, ref) => ( <button ref={ref} className="MyClassName" type={props.type}> {props.children} </button> ));
用于获取组件 Props 的工具泛型,与之类似的还有:
import { DatePicker } from "@douyinfe/semi-ui"; type SemiDatePikerProps = React.ComponentProps<typeof DatePicker>; export const DisabledDatePicker: React.FC = () => { const disabledDate: SemiDatePikerProps["disabledDate"] = (date) => { // ... }; return <DatePicker disabledDate={disabledDate} />; };
使用第三方库组件时,不要使用具体 path 去引用类型(若第三方组件后续升级修改了内部文件引用路径,会出现错误)。
import { InputProps } from "@douyinfe/semi-ui/input"; // × import { InputProps } from "@douyinfe/semi-ui"; // √
若入口文件未暴露对应组件的相关类型声明,使用 React.ComponentProps
React.ComponentProps
import { Input } from "@douyinfe/semi-ui"; type InputProps = React.ComponentProps<typeof Input>;
另外一个例子:
某些场景传入的参数为联合类型,需要基于一些手段将其类型收窄(Narrowing)。
function printAll(strs: string | string[] | null) { if (strs && typeof strs === "object") { // strs 为 string[] for (const s of strs) { console.log(s); } } else if (typeof strs === "string") { // strs 为 string console.log(strs); } }
function isFish(pet: Fish | Bird): pet is Fish { return (pet as Fish).swim !== undefined; } if (isFish(pet)) { pet.swim(); } else { pet.fly(); }
思考一下 Lodash 的 isBoolean/isString/isArray...等函数,再思考一下使用 isEmpty 有什么不对。
isBoolean
isString
isArray
isEmpty
interface LoDashStatic { isBoolean(value?: any): value is boolean; isString(value?: any): value is string; isArray(value?: any): value is any[]; isEmpty(value?: any): boolean; // 这里的定义会使得业务中时使用出现什么问题? }
笔者不用 redux,此处仅做演示
TS Playground - An online editor for exploring TypeScript and JavaScript
interface ActionA { type: "a"; a: string; } interface ActionB { type: "b"; b: string; } type Action = ActionA | ActionB; function reducer(action: Action) { switch (action.type) { case "a": return console.info("action a: ", action.a); case "b": return console.info("action b: ", action.b); } } reducer({ type: "a", a: "1" }); // √ reducer({ type: "b", b: "1" }); // √ reducer({ type: "a", b: "1" }); // × reducer({ type: "b", a: "1" }); // ×
以非常熟悉的 window.addEventListener 为例:
window.addEventListener
// e 为 MouseEvent window.addEventListener("click", (e) => { // ... }); // e 为 DragEvent window.addEventListener("drag", (e) => { // ... });
可以发现 addEventListener 的回调函数入参类型(event)会随着监听事件的不同而不同,addEventListener 的函数签名如下:
addEventListener
addEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
type 为泛型 K,约束在 WindowEventMap 的 key 范围内,再基于 K 从 WindowEventMap 推导出 ev 事件类型即可。
type
WindowEventMap
当然你也可以选择使用联合类型,就像 redux action 那样。
了解完 TypeScript 基础内容(keyof/in/extends/infer)后,可自行尝试实现内置工具泛型,实现一遍理解更深刻。
interface Person { name: string; age: number; address?: string; }
type PartialPerson = Partial<Person>; // ↓ type PartialPerson = { name?: string | undefined; age?: number | undefined; address?: string | undefined; };
type RequiredPerson = Required<Person>; // ↓ type RequiredPerson = { name: string; age: number; address: string; };
type PersonWithoutAddress = Pick<Person, "name" | "age">; // ↓ type PersonWithoutAddress = { name: string; age: number; };
type PersonWithOnlyAddress = Omit<Person, "name" | "age">; // ↓ type PersonWithOnlyAddress = { address?: string | undefined; };
该泛型实现需要掌握 Distributive Conditional Types
type T = Exclude<1 | 2, 1 | 3>; // -> 2
type T = Extract<1 | 2, 1 | 3>; // -> 1
declare function f1(arg: { a: number; b: string }): void; type T = Parameters<typeof f1>; // ↓ type T = [ arg: { a: number; b: string; } ];
declare function f1(): { a: number; b: string }; type T = ReturnType<typeof f1>; // ↓ type T = { a: number; b: string; };
把一个个工具泛型理解成函数,类型作为入参和返回值即可,通过 cmd + 左键点击具体工具泛型阅读具体实现也可。
React
React.FC
PropsWithChildren
为IHelloProps
注入children
类型React.FC
声明组件,通过泛型参数传入组件Props
类型react@16
类型定义中React.FC
自带children
类型,无需额外处理(即可省略第 1 步)html
属性,如className
、style
等,可以直接extends HTMLAttributes<HTMLDivElement>
,其中HTMLDivElement
可替换为所需要的类型,如HTMLInputElement
不推荐 React.FC?
Remove React.FC from Typescript template #8177
在这个 PR 里移除了 CRA 默认模板的 React.FC,主要有以下几点理由:
<Select.Option>
好处则是提供了返回值约束。
站在今天回看:
所以是否使用 React.FC 可以自行选择:
若对于返回值有明确的类型要求,配置了 typescript 规则,那么可以使用 React.FC,其他时候可以直接定义 Props interface,如下所示:
React.forwardRef
React 提供了
forwardRef
函数用于转发 Ref,该函数也可传入泛型参数,如下:React.ComponentProps
用于获取组件 Props 的工具泛型,与之类似的还有:
使用第三方库组件时,不要使用具体 path 去引用类型(若第三方组件后续升级修改了内部文件引用路径,会出现错误)。
若入口文件未暴露对应组件的相关类型声明,使用
React.ComponentProps
另外一个例子:
类型收窄
某些场景传入的参数为联合类型,需要基于一些手段将其类型收窄(Narrowing)。
使用 type predicates: is
思考一下 Lodash 的
isBoolean
/isString
/isArray
...等函数,再思考一下使用isEmpty
有什么不对。类型安全的 redux action
TS Playground - An online editor for exploring TypeScript and JavaScript
多参数类型约束
以非常熟悉的
window.addEventListener
为例:可以发现
addEventListener
的回调函数入参类型(event)会随着监听事件的不同而不同,addEventListener
的函数签名如下:type
为泛型 K,约束在WindowEventMap
的 key 范围内,再基于 K 从WindowEventMap
推导出 ev 事件类型即可。常用工具泛型
把一个个工具泛型理解成函数,类型作为入参和返回值即可,通过 cmd + 左键点击具体工具泛型阅读具体实现也可。
推荐阅读