聊聊React中的隐藏彩蛋功能

开发 前端
本文将向你介绍3个文档中未提及的隐藏彩蛋功能。

大家好,我卡颂。

React的代码量可以说是相当庞大。在如此庞大的库中是否存在「文档中未提及,但是实际存在的功能」呢?

答案是肯定的。

本文将向你介绍3个文档中未提及的隐藏彩蛋功能。

ref cleanup

在当前React中,Ref存在两种数据结构:

  1. <T>(instance: T) => void
  2. {current: T}

对于大部分需求,我们会使用第二种数据结构。同时,他也是useRef、createRef返回的数据结构。

第一种数据结构主要用于DOM监控,比如在下面的例子中,div的尺寸会反映到height状态中:

function MeasureExample() {
  const [height, setHeight] = useState(0);

  const measuredRef = useCallback(node => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  }, []);

  return (
    <div ref={measuredRef}>Hello 卡颂</div>
  );
}

但在上面的例子中,DOM的尺寸变化无法实时反映到height状态。为了反映实时变化,需要使用监控DOM的原生API,比如:

  • ResizeObserver[1],监控DOM尺寸变化。
  • IntersectionObserver[2],监控DOM可视区域变化。
  • MutationObserver[3],监控DOM树变化。

这些API通常是事件驱动,这就涉及到「当不需要监控后,解绑事件」。

既然事件绑定是在ref回调中进行的,很自然的,解绑事件也应该在ref回调中进行。比如,用ResizeObserver改造上述例子:

function MeasureExample() {
  const [entry, setEntry] = useState();
  
  const measuredRef = useCallback((node) => {
    const observer = new ResizeObserver(([entry]) => {
      setEntry(entry)
    })

    observer.observe(node)
    // 解绑事件
    return () => {
      observer.disconnect()
    }
  }, [])

  return (
    <div ref={measuredRef}>Hello 卡颂</div>
  );
}

在这个场景中,我们希望函数类型的ref可以返回一个新函数,用于解绑事件(类似useEffect回调的返回值)。

实际上,在19年的#issues 15176[4]中就有人提出这个问题。在去年底的#pull 25686[5]中相关改动已经合并到React main分支。

当前React文档中Ref的部分还未提及这个功能改动。可能在未来的某个小版本中,会上线这个功能。

Module Component

你觉得下面的函数组件能渲染出「hello」么:

function Child() {
  return {
    render() {
      return "hello";
    }
  };
}

答案是 —— 可以,见Module Component在线示例[6]。

其实这是一种上古时期就存在的组件形式,叫做Module Component(即函数组件返回带有render属性的对象)。

当遇到Module Component,React会将对应函数组件(上例中的Child组件)转换为Class Component,后续更新流程与Class Component无异。

Module Component预计会在未来的某个版本被移除(见#pull 15145[7]),所以文档中并未提及。

要说有啥实际作用,没准可以给你同事带来点小小困惑......

开启全局并发更新

在v18中,只有使用并发特性(比如useTransiton)触发的更新才是并发更新,其他情况触发的更新都是同步更新。

如何才能不使用并发特性,又能全局开启并发更新呢?答案是在项目中加入下面这行咒语:

React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentBatchConfig.transition = {
  帅哥: '卡颂'
};

比如,对于如下例子,渲染一个耗时的列表(每个Item组件render会耗时5ms):

function App() {
  return (
    <ul className="App">
      {Array.from({ length: 100 }).map((_, i) => (
        <Item key={i} num={i}>
          {i}
        </Item>
      ))}
    </ul>
  );
}

function Item({ children }) {
  const cur = performance.now();
  while (performance.now() - cur < 5) {}
  return <li>{children}</li>;
}

并发示例地址[8]。

不加上咒语时的渲染火炬图如下,整个更新流程在一个宏任务中,耗时513ms:

图片

加上咒语时的渲染火炬图如下,整个更新流程被时间切片,每个切片5ms左右:

图片

咒语为什么会起作用呢?

在React、ReactDOM中都存在变量__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,这个变量的作用是 —— 在不同包之间共享数据。

比如,所有Hook都是从React包中导出的,但Hook的具体实现在ReactDOM包中。为了在他们之间共享Hook,就需要一个媒介,这就是__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED。

图片

类似的,「更新」相关数据也需要在React与ReactDOM间共享,其中就包括 —— 更新是否是并发更新。

当我们赋值React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentBatchConfig.transition后,React会认为当前更新是并发更新。

通过这种方式,就能全局开启并发更新。

当然,我并不建议你随意更改__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED下的数据,毕竟这个变量的名字还是挺唬人的。

总结

以上便是3个React中的隐藏彩蛋功能。其实除了他们之外,React中还有很多没有暴露出来的API,比如类似Vue中Keep-Alive的Offscreen Component。

当前要想体验Offscreen Component只能通过Suspense间接体验(Suspense能够在pending与挂载组件间切换就是利用Offscreen Component)。

还有什么你知道的React隐藏功能?

参考资料

[1]ResizeObserver:https://developer.mozilla.org/zh-CN/docs/Web/API/ResizeObserver。

[2]IntersectionObserver:https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver。

[3]MutationObserver:https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver。

[4]#issues 15176:https://github.com/facebook/react/issues/15176。

[5]#pull 25686:https://github.com/facebook/react/pull/25686。

[6]Module Component在线示例:https://codesandbox.io/s/boring-proskuriakova-u4pzyl?file=/src/App.js。

[7]#pull 15145:https://github.com/facebook/react/pull/15145。

[8]并发示例地址:https://codesandbox.io/s/zen-ben-nldijx?file=/src/index.js。

责任编辑:姜华 来源: 魔术师卡颂
相关推荐

2018-02-28 11:42:59

戴尔

2011-06-13 09:59:21

2016-11-21 11:43:11

Python

2016-12-02 10:05:49

Python彩蛋

2021-10-11 09:41:20

React位运算技巧前端

2018-04-26 13:33:20

Python语法Bug

2020-11-01 20:30:53

Windows 10Windows 7Windows

2022-04-14 11:50:39

函数组件hook

2024-01-26 08:06:43

2020-10-12 09:50:10

iOS 14苹果功能

2015-09-09 18:06:53

戴尔云计算

2022-03-31 17:54:29

ReactHooks前端

2021-05-21 09:34:40

React React 17前端

2020-11-09 14:37:03

微信彩蛋翻译

2013-11-05 10:03:22

Eclipse功能

2013-01-25 14:35:18

Windows 7

2009-10-23 14:22:59

Windows 7微软隐藏功能

2021-03-28 08:15:20

Windows 操作系统微软

2021-06-08 06:13:16

React开发开发技术

2022-03-11 10:23:02

React性能优化
点赞
收藏

51CTO技术栈公众号