本文来自React技术揭秘
我们可以从官网看到React的理念:
我们认为,React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式。它在 Facebook 和 Instagram 上表现优秀。
那么该如何理解快速响应?可以从两个角度来看:
- 速度快
- 响应自然
React是如何实现这两点的呢?
理解“速度快”
每当聊到一款前端框架,拉出来比比渲染速度成了老生常谈。
这里提供了各种框架渲染速度的对比
我们经常用“前端三大框架”指React、Vue和Angular。相比于使用模版语言的Vue、Angular,使用原生js(JSX仅仅是js的语法糖)开发UI的React在语法层面有更多灵活性。
然而,高灵活性意味着高不确定性。考虑如下Vue模版语句:
<template>
<ul>
<li>0</li>
<li>{{ name }}</li>
<li>2</li>
<li>3</li>
</ul>
</template>
当编译时,由于模版语法的约束,Vue可以明确知道在li中,只有name是变量,这可以提供一些优化线索。
而在React中,以上代码可以写成如下JSX:
<ul>{
data.map((name, i) => <li>{i !== 1 ? i : name}</li>)
}</ul>
由于语法的灵活,在编译时无法区分可能变化的部分。所以在运行时,React需要遍历每个li,判断其数据是否更新。
基于以上原因,相比于Vue、Angular,缺少编译时优化手段的React为了速度快需要在运行时做出更多努力。
比如
- 使用
PureComponent或React.memo构建组件 - 使用
shouldComponentUpdate生命周期钩子 - 渲染列表时使用
key - 使用
useCallback和useMemo缓存函数和变量
由开发者来显式的告诉React哪些组件不需要重复计算、可以复用。
在后面源码的学习中,我们会看到这些优化手段是如何起作用的。比如经过优化后,React会通过这个方法跳过一些本次更新不需要处理的任务。
理解“响应自然”
设想以下场景:
有一个地址搜索框,在输入字符时会实时显示地址匹配结果。
当用户输入过快时可能输入变得不是那么流畅。这是由于下拉列表的更新会阻塞线程。我们一般是通过debounce或 throttle来减少输入内容时触发回调的次数来解决这个问题。
但这只是治标不治本。只要组件的更新操作是同步的,那么当更新开始直到渲染完毕前,组件中总会有一定数量的工作占用线程,浏览器没有空闲时间绘制UI,造成卡顿。
React核心团队成员Dan在介绍React为什么会并发(Concurrent Mode)更新组件时说:
当输入字符时,用户在意下拉框是否一瞬间就出现么?并不在意。
如果我们能稍稍延迟下拉框更新的时间,为浏览器留出时间渲染UI,让输入不卡顿。这样的体验是更自然的。
为了实现这个目标,需要将同步的更新变为可中断的异步更新。在浏览器每一帧的时间中,预留一些时间给JS线程,React利用这部分时间更新组件(可以看到,在源码中,预留的初始时间是5ms)。
当预留的时间不够用时,React将线程控制权交还给浏览器使其有时间渲染UI,React则等待下一帧时间到来继续被中断的工作。
一切,都是为了自然的体验。
这里提供2个同步更新 vs 可中断的异步更新的Demo:
总结
通过以上内容,我们可以看到,React为了践行“构建快速响应的大型 Web 应用程序”理念做出的努力。这其中有些优化手段可以在现有架构上增加,而有些(如:异步可中断更新)只能重构整个架构实现。
即使过程中有如此大的改动,从13年第一次Commit到如今2020年,this.setState却始终不变,出色的完成着开发者交代的工作。
React 13年第一次 commit
最后再让我们看看,Dan回答网友关于
React发展方向的提问Dan回答
相比于新增feature,React更在意底层抽象的表现力。结合理念,相信你已经明白这意味着什么了。