从 React 源码的类型定义中,我学到了什么?

Posted 前端真好玩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从 React 源码的类型定义中,我学到了什么?相关的知识,希望对你有一定的参考价值。

今天看了下 React 的类型定义,也就是 @types/react 包下的 index.d.ts,发现了一些有趣的写法。

这篇文章就分享下这些写法,估计大部分人都不知道:

提取可选索引的值

首先,我看到了这样一段类型逻辑:

这段逻辑就是取索引类型的 ref 索引的值,但是是通过模式匹配的方式,把提取的类型放到 infer 声明的局部变量 R 里返回的。

简化一下就是这样的:

提取 Props 的 ref 索引的值的类型返回。

我在想,这么麻烦干什么,直接 Props[\'ref\'] 不就能拿到 ref 索引的值么?

于是我就改成了这样:

然后试了下:

不对呀,人家这是可选索引,值的类型是包含 undefined 的联合类型。

那就 Exclude 下不就行了:

这样也比那个 infer 的方式简洁呀,为啥 React 类型定义都是用的 infer 取的可选索引的类型呢?

后来我突然想到,如果这个 ref 值的类型就是 undefined 呢?

我试了下:

确实,我那样写是有问题的,如果值的类型本来就是 undefined,Exclude 掉 undefined 后就是 never 了,而人家那种方式就没问题:

于是我就加一下 undefined 的处理:

这样就行了。

对比了下两种写法:

确实还是 React 的那种写法更简洁。

对了,那上面那层判断呢?

这个判断没必要的吧,如果没有 ref,那 Props[\'ref\'] 不就是返回 never 么,没必要单独判断呀?

然后我就看到了这样一段注释:

在 ts 3.0 中,如果索引类型没有对应的索引,那返回的类型是 而不是 never。

原来如此,这个 \'ref\' extends keyof Props 是为了做兼容的呀,不是没意义。

这就是我从这个类型中学到的两个知识点:

  • 索引访问 Obj[Key] 和 infer 提取和都可以取到索引类型的某个索引的值,但是当处理可选索引的时候,用 infer 更简洁一些,因为前者要取出类型之后再单独处理下 undefined,而后者在 infer 的时候就顺便处理了 undefined。

  • ts 3.0 中如果索引类型没有对应的索引,返回的是 不是 never,如果对兼容性要求高的话,可以用 \'xx\' in keyOf Obj 的方式做下兼容

  • 我们从这个类型里学到了不少东西,再来看下第二个类型:

    索引类型和 any、never 的处理

    然后我又看到了这样一个类型,

    先试一下它的功能,传入两个索引类型:

    看下结果:

    这是些啥啊,谁能看得懂呀。

    其实这只是因为 TS 没有计算出最终的类型而已,用到的时候才会计算,所以我们可以这样处理下:

    Copy 的高级类型是通过映射类型的语法构造了一个新的索引类型,它的功能是原封不动的复制一个索引类型。

    类型参数 Obj 约束为索引类型,也就是 Record<string, any>。key 保持不变,也就是 Key in keyof Obj,value 也保持不变,也就是 Obj[Key]。

    因为重新生成的类型的过程中要做计算,所以那个类型就能提示出最终的结果了:

    所以说,这个类型的作用是两个索引类型 A,B,只有 A 中有的就保留,A、B 都有的变为可选,B 有但 A 没有的变为可选。

    那这段逻辑具体是怎么用 TS 实现的呢?

    我们先来过一下 TS 这些内置的高级类型:

    Pick

    Pick 的作用是通过映射类型的语法构造一个新的索引类型,根据传入的 Key 对索引做下过滤:

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

    测试下:

    Partial

    Partial 也是通过映射类型的语法构造一个新的索引类型,但是会把索引变为可选:

    type Partial<T> =  [P in keyof T]?: T[P]; ;

    测试下:

    Extract

    Extract 是取两个联合类型都包含的部分,也就是取交集:

    type Extract<T, U> = T extends U ? T : never;

    测试下:

    Exclude

    Exclude 是从联合类型 A 中去掉联合类型 B 中的类型,也就是取差集:

    type Extract<T, U> = T extends U ? T : never;

    测试下:

    学会了用 Pick、Partial、Exclude、Extract 这些高级类型,那上面的那段逻辑我们就知道怎么实现了:

    只有 A 中有的就保留的逻辑是:Pick<A, Exclude<keyof A, keyof B>>

    A、B 都有的变为可选:Partial<Pick<A, Extract<keyof A, keyof B>>>

    B 中有但 A 中没有的也变为可选:Partial<Pick<B, Exclude<keyof B, keyof A>>>

    这样,这个类型的主要逻辑我们就理清了:

    把三部分计算结果的索引类型取交叉类型,就会合并到一起。

    那前面那两个判断是啥?

    P extends any 还有这个 string extends keyof P,这俩都是做啥的?

    P extends any 这个是因为联合类型当作为类型参数出现在条件类型左边时,会把每个类型单独传入做计算,最后把计算结果合并成联合类型,这个特性叫做分布式条件类型。

    比如这样一个联合类型:

    type Union = \'a\' | \'b\' | \'c\';

    我们想把其中的 a 大写,就可以这样写:

    type UppercaseA<Item extends string> = 
        Item extends \'a\' ?  Uppercase<Item> : Item;

    因为 Item 是类型参数,出现在了条件类型的左边,而且传入的是联合类型,那就触发分发特性,会把每个类型单独传入做计算,然后把结果合并成联合类型。

    所以这里的 P extends any 的作用就是触发联合类型特性的,从而让这个类型能正确处理联合类型。不然联合类型整个传入的话,后面怎么做计算。

    这里的 P extends any 换成 P extends P 也可以,都是一样的作用。

    那后面那段代码 string extends keyof P 是啥意思?

    这个我确实想了一段时间,如果 a: 1, b: 2 这样的索引类型,keyof 的结果是 \'a\' | \'b\',而如果是数组类型,那 keyof 的结果是 0 | 1 | \'length\' | \'toString\' | ...

    什么类型的 keyof 结果是 string 呢?

    突然,我想起了前几天学到的一个知识点:用 keyof any 代替 string | number | symbol 更灵活:

    而且我试了下 never 的 keyof 结果也是这个:

    所以说 string extends keyof P 就可以排除 any 和 never 的情况!

    妙呀,还能这么区分 any 和 never。

    所以说,这个类型的逻辑我们已经理清了:

    这个类型的功能是保留只有 A 有的索引,把 A、B 都有的索引变为可选,把只有 B 有的索引变为可选。

    而且处理了联合类型的情况。

    如果传入的是 any 或者 never,不做处理,直接返回。

    这个类型里我们也学到了不少东西。

    总结

    我看了下 @types/react 的类型定义,学到了不少东西:

  • 可选索引的值的提取,用 infer 比 Obj[key] 更方便,因为前者只需要 Obj[Key] extends xxx?: infer Value| undefined提取 Value 的类型,而后者需要先排除值的类型就是 undefined 的情况,然后再用 Exclude<Obj[Key], undefined> 去掉类型中的 undefined。

  • ts 3.0 中取索引类型没有的索引会返回 而不是 never,需要兼容的话可以单独做下判断:\'xxx\' in keyof Obj。

  • 处理索引类型可以综合用 Pick、Partial、Exclude、Extract 等内置高级类型对每一部分索引做处理,然后取交叉类型来合并到一起。

  • P extends any 和 P extends P 的作用是触发联合类型的分发特性的,加上这段处理才能正确处理联合类型,会把每个类型单独传入做计算,最后把结果合并成联合类型。

  • string extends keyof Obj 可以判断出 any 和 never 类型,只有这两种类型取 keyof 的结果是 string | nubmer | symbole,包含 string。

  • 而且,还讲了一个小技巧:

    ts 类型只有计算的时候才会求值,如果是索引类型,可以用映射类型的语法创建个一摸一样的索引类型,因为用到了,就会做计算,从而就可以显示出最终的类型。

    不得不说,React 类型定义做的挺完善的,考虑到了各种类型的处理,也考虑到了低版本的兼容,从中还是能学到不少东西的。

    从源码的角度看 React JS 中批量更新 State 的策略(上)


    在之前的文章「」与 「」 中,我们分别看到了 React JS 中 setState 的异步表现,并从源码的角度简单地了解了 React 中 setState 的设计结构以及原理。

    这篇文章继上篇文章后,继续从源码的角度来了解 React 中对 State 批量更新策略的定义。

    同样,源码的部分为了保证格式正常就直接截图了,查看源码点击「阅读原文」在博客中点击对应的链接直接跳转至 GitHub 查看即可。


    1. setState 的源码实现


    在 setState 的源码实现中,传递过来的参数就被定义成了 partialState,从参数名以及参数的说明中就可以看到,这只是 state 的一部分。
    默认都会调用 this.updater.enqueueSetState(this, partialState) 将 state 放进更新队列中去。
    而如果有传递回调函数过来的话,会执行 this.updater.enqueueCallback(this, callback),涉及到回调函数的部分,我们后续文章会继续进行分析。


    2. 更新队列 ReactUpdateQueue 的定义


    在上面的 setState 定义中,我们可以看到有一个 updater 的调用。
    具体的定义如下。

    从源码的角度看 React JS 中批量更新 State 的策略(上)

    而初始的 updater 的定义如下。

    从源码的角度看 React JS 中批量更新 State 的策略(上)

    这样最终的更新队列都指向了 ReactUpdateQueue 的源码定义中。


    3. ReactUpdateQueue 中的 enqueueSetState


    我们可以看到 ReactUpdateQueue 中的 enqueueSetState 的定义。


    从源码的角度看 React JS 中批量更新 State 的策略(上)


    继续去跟 enqueueUpdate(internalInstance) 的实现。

    从源码的角度看 React JS 中批量更新 State 的策略(上)


    4. ReactUpdates 中的 enqueueUpdate


    到这里,我们回到了上篇文章看到 ReactUpdates 的位置。

    从源码的角度看 React JS 中批量更新 State 的策略(上)

    我们继续看函数开始的部分,注入的两个部分。

    从源码的角度看 React JS 中批量更新 State 的策略(上)

    注入了两个部分,ReactReconcileTransaction 以及 batchingStrategy

    ReactReconcileTransaction 主要用于在更新 state 时,页面 UI 元素的修正以及在执行生命周期函数时,处理好生命周期函数与其他用户自定义函数之间的执行顺序与逻辑,具体的实现可以参考它的源码。

    到这里,我们从 setState 的定义开始,梳理好了在进行更新策略前的逻辑部分,下篇文章,我们将直达 batchingStrategy 的内部实现,看看 React 的更新逻辑策略的实现。



    加入 DevOpenClub Pro 知识星球

    高质量、有价值的交流社区永远都不会是你所在的 QQ 群或者微信群;

    获取每年所有原创开发视频教程的第一手更新;

    每日分享高质量的国外技术开发头条信息与资源;

    获取 [学软件开发 & 学英语] 相关国外超清视频的第一手百度云更新;

    获取百度云中分享的超清视频课程以及源码等相关资料;

    获取其他高质量软件开发行业新闻、技术文章、教学视频分享;

    第一时间获取视频更新提醒及观看密码;

    遇到相关技术问题可以进行快速提问以及讨论交流;

    群中认识更多的朋友以及合作开发项目的机会;

    认识更多的行业朋友,或者交流自己的创业小项目。





    以上是关于从 React 源码的类型定义中,我学到了什么?的主要内容,如果未能解决你的问题,请参考以下文章

    从 12.9K 的前端开源项目我学到了啥?

    我从小程序学到了什么

    【原创】react-源码解析 - forward-ref&context(4)

    React 函数未定义

    数学学习到底应该学到什么

    50天用JavaScript完成50个web项目,我学到了什么?