专栏首页全栈前端精选@types react 中值得注意的 TS 技巧

@types react 中值得注意的 TS 技巧

1 引言

从 @types/react 源码中挖掘一些 Typescript 使用技巧吧。

2 精读

泛型 extends

泛型可以指代可能的参数类型,但指代任意类型范围太模糊,当我们需要对参数类型加以限制,或者确定只处理某种类型参数时,就可以对泛型进行 extends 修饰。

问题:React.lazy 需要限制返回值是一个 Promise<T> 类型,且 T 必须是 React 组件类型。

方案:

function lazy<T extends ComponentType<any>>(
  factory: () => Promise<{ default: T }>
): LazyExoticComponent<T>;

T extends ComponentType 确保了 T 这个类型一定符合 ComponentType 这个 React 组件类型定义,我们再将 T 用到 Promise<{ default: T }> 位置即可。

泛型 extends + infer

如果有一种场景,需要拿到一个类型,这个类型是当某个参数符合某种结构时,这个结构内的一种子类型,就需要结合 泛型 extends + infer 了。

问题:React.useReducer 第一个参数是 Reducer,第二个参数是初始化参数,其实第二个参数的类型是第一个参数中回调函数第一个参数的类型,那我们怎么将这两个参数的关系联系到一起呢?

方案:

function useReducer<R extends Reducer<any, any>, I>(
  reducer: R,
  initializerArg: I & ReducerState<R>,
  initializer: (arg: I & ReducerState<R>) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];

type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any>
  ? S
  : never;

R extends Reducer<any, any> 的意思在上面已经提过了,也就是 R 必须符合 Reducer 结构,也就是 reducer 必须符合这个结构,之后重点来了:initializerArg 利用 ReducerState 这个类型直接从 reducer 的类型 R 中将第一个回调参数挖了出来并返回。

ReducerState 定义中 R extends Reducer<infer S, any> ? S : never 的含义是:如果 R 符合 Reducer<infer S, any> 类型,则返回类型 S,这个 SReducer<infer S> 也就是 State 位置的类型,否则返回 never 类型。

所以 infer 表示待推断类型,是非常强大的功能,可以指定在任意位置代指其类型,并配合 extends 判断是否符合结构,可以使类型推断具备一定编程能力。

要用 extends 的另一个原因是,只有 extends 才能将结构描述出来,我们才能精确定义 infer 指代类型的位置。

类型重载

当一个类型拥有多种使用可能性时,可以采用类型重载定义复数类型,Typescript 作用时会逐个匹配并找到第一个满足条件的。

问题:createElement 第一个参数支持 FunctionComponent 与 ClassComponent,而且传入参数不同,返回值的类型也不同。

方案:

function createElement<P extends {}>(
  type: FunctionComponent<P>,
  props?: (Attributes & P) | null,
  ...children: ReactNode[]
): FunctionComponentElement<P>;
function createElement<P extends {}>(
  type: ClassType<
    P,
    ClassicComponent<P, ComponentState>,
    ClassicComponentClass<P>
  >,
  props?: (ClassAttributes<ClassicComponent<P, ComponentState>> & P) | null,
  ...children: ReactNode[]
): CElement<P, ClassicComponent<P, ComponentState>>;

createElement 写两遍及以上,并配合不同的参数类型与返回值类型即可。

自定义类型收窄

我们可以通过 typeofinstanceof 做一些类型收窄工作,但有些类型甚至自定义类型的收窄判断函数需要自定义,我们可以通过 is 关键字定义自定义类型收窄判断函数。

问题:isValidElement 判断对象是否是合法的 React 元素,我们希望这个函数具备类型收窄的功能。

方案:

function isValidElement<P>(
  object: {} | null | undefined
): object is ReactElement<P>;

const element: string | ReactElement = "";

if (isValidElement(element)) {
  element; // 自动推导类型为 ReactElement
} else {
  element; // 自动推导类型为 string
}

基于这个方案,我们可以创建一些很有用的函数,比如 isArrayisMapisSet 等等,通过 is 关键字时其被调用时具备类型收窄的功能。

用 Interface 定义函数

一般定义函数类型我们用 type,但有些情况下定义的函数既可被调用,也有一些默认属性值需要定义,我们可以继续用 Interface 定义。

问题:FunctionComponent 既可以当作函数调用,同时又能定义 defaultProps displayName 等固定属性。

方案:

interface FunctionComponent<P = {}> {
  (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
  propTypes?: WeakValidationMap<P>;
  contextTypes?: ValidationMap<any>;
  defaultProps?: Partial<P>;
  displayName?: string;
}

(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null 表示这种类型的变量可以作为函数执行:

const App: FunctionComponent = () => <div />;
App.displayName = "App";

3 总结

看完文章内容,相信你已经可以独立读懂 @types/react 这个包的所有类型定义!

更多基础内容可以阅读 精读《Typescript2.0 - 2.9》 与 精读《Typescript 3.2 新特性》,由于 TS 更新频繁,后续 TS 技巧可能继续以阅读源码方式进行,希望这次选用的 React 类型源码可以让你印象深刻。

本文分享自微信公众号 - 全栈前端精选(isNealyang)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Vue 项目里戳中你痛点的问题及解决办法(上)

    作者:愣锤 https://juejin.im/post/5b174de8f265da6e410e0b4e

    Nealyang
  • 手把手教你实现全栈博客项目(2)-- 前端react-xxx、路由配置

    项目地址:https://github.com/Nealyang/React-Express-Blog-Demo

    Nealyang
  • Vue 项目里戳中你痛点的问题及解决办法(下)

    作者:愣锤 https://juejin.im/post/5b174de8f265da6e410e0b4e

    Nealyang
  • 值类型和引用类型的区别,struct和class的区别

    C#值类型和引用类型 1、简单比较   值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。   值类型(value type):...

    Christal_R
  • MySQL数字类型学习笔记

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    SmileNicky
  • double相加(減)结果会有些误差

    前提介绍       今天在调试代码的时候发现了一个double类型数据相减的有趣问题,148163.1 - 82692.09大家猜猜结果等于多少,经过调试最终...

    用户1168362
  • Qt入门之基础篇 ( 一 ) :Qt4及Qt5的下载与安装

    转载请注明出处:CN_Simo. 导语: Qt是一个跨平台的C++图形界面应用程序框架。它提供给开发者建立图形用户界面所需的功能,广泛用于开发GUI程序,也可用...

    CN_Simo
  • JAVA高并发网络编程之TCP和UDP协议(八)

    1、创建服务器套接字---分配内存、初始化 2、服务器套接字--侦听 3、建立与客户端配套的客户端套接字 4、与客户端通讯(可以多客户端) 5、关闭、销毁[服务...

    IT故事会
  • crs_register/crs_unregister 注册与移除RAC服务

        crs_register命令主要是将资源注册到CRS。该方法通常结合crs_stat -p 或者crs_profile先创建配置文件。同时crs_reg...

    Leshami
  • 使用VS2010开发Qt程序的一点经验

    导读      相比于Qt Creator,我更喜欢用VS2010来进行开发。虽然启动时间相对较慢,但是VS下强大的快捷键和丰富的插件,以及使用多年的经验,都让...

    24K纯开源

扫码关注云+社区

领取腾讯云代金券