本专栏致力于每周分享一个项目,如果本文对你有帮助的话,欢迎点赞或者关注☘️
React18 源码系列会随着学习 React 源码的实时进度而实时更新:约,两天一小改,五天一大改。
作为框架使用者,我们只需了解其设计动机。
作为源码研究者,我们需以框架开发者的角度来看待其设计理念。
React 是什么
首先来看使用原生 javascript 和使用 react 完成同一个功能需求的代码对比,初步体验一下:
<html>
<head>
<title>Test</title>
</head>
<body>
<div id="root">
<div id="content" onclick="changeText()">Click Me, Count: 0</div>
</div>
</body>
<style>
#content{
font-size:22px;
border: 1px solid gray;
cursor: pointer;
padding: 6px 10px;
display: inline-block;
border-radius: 10px;
}
</style>
<script lang="javascript">
let count = 0
function changeText(){
count ++
// 手动去操作DOM。
document.getElementById('content').innerText = "Click Me, Count: " + count
}
</script>
</html>
<html>
<head>
<title>Test</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<style>
#content{
font-size:22px;
border: 1px solid gray;
cursor: pointer;
padding: 6px 10px;
display: inline-block;
border-radius: 10px;
}
</style>
<body>
<div id="root"></div>
<script type="text/babel">
function MyFunctionComponent() {
let [count, setCount] = React.useState(0)
// React版本的代码实现只关注了变量count值的变化,并没有手动去操作DOM。
return <div id="content" onClick={()=>setCount(++count)}>Click Me, Count: {count}</div>
}
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<MyFunctionComponent />);
</script>
<!--
Note: this page is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
-->
</body>
</html>
React本质上在根据数据的变化来操作DOM,即数据驱动视图,根据数据的变化去自动更新视图,避免了开发人员去手动做作一些低效的操作Dom的行为。React 通过一些技术,如并发处理更新、基于 Fiber 的调度算法、流式渲染等实现比手动使用原生Dom操作更加高效的React Dom操作,本质上也是围绕Dom操作所做的优化。
React 将用户界面分解为组件, 鼓励创建可重用的 UI 组件来构建用户界面,组件包含动态呈现的数据、操作数据的方法、界面UI的抽象。使用原生Javascript 和 HTML 来呈现页面则表现出逻辑和视图相分离的特点,不利于组织代码和模块化。
项目历史
新特性、逻辑上数据结构与算法的优化、计算机性能的提升、Javascript语言能力榨取计算机的性能的能力不断推动了Vue、React这些框架的不断发展变化 。
获取项目历史渠道:1.官方的changelog,2.官方的Blog文章。
多说一句:对底层原理的掌握的程度决定了我们发展的上限:操作系统、组成原理、计算机网络、编译原理、数据结构与算法。
React 0.3.0 - 0.14.0 (2013.5.29 ~ 2015.10.7)——基础功能的打磨过程
React 15 (2016.4.7) ——基本功能、基本理念成熟,是一个基础实现版本的顶峰
React 15 架构可以分为两层:1.reconciler 2.renderer
reconciler(协调器): 负责找出变化的组件
每当用户通过 the.setState、this.forceUpdate、reactDom.render等 API 触发更新,有更新发生的时候,reconciler 都会调用函数(class)组件的 render 方法,将返回的 JSX 转化成虚拟 DOM,将虚拟 DOM 和上次更新时的虚拟 DOM 对比, 通过对比找出本次更新中变化的虚拟 DOM,通知 Renderer 将变化的虚拟 DOM 渲染到页面上。
reconciler 中的 mountComponent 和 updateComponent 都会递归更新子组件,更新一旦开始,中途就无法中断,一旦递归更新时间超过了 16ms,用户交互就会卡顿。而 Reconciler 和 Renderer 是交替工作的,比如我们更新一个ul列表,当第一个li在页面上已经更新并渲染之后,第二个变化的li才进入Reconciler,如果这时候发生了更新的中断,后续的更新无法继续,会出现页面上只更新了一部分 li 的情况,于是react决定重写整个架构,推出 React 16。
renderer(渲染器):负责将变化的组件渲染到页面上
在每次更新发生时,Renderer 接到 Reconciler 通知,将变化的组件渲染在当前宿主环境。这里需要注意 React 支持跨平台,所以不同平台会有不同的Renderer。
- ReactDOM 渲染器,渲染浏览器环境组件。
- ReactNative 渲染器,渲染 App 原生组件。
- ReactTest 渲染器,渲染出纯 Js 对象用于测试。
- ReactArt 渲染器,渲染到 Canvas, SVG 或 VML (IE8)
React 16 (2017.9.26) ——引入 Fiber、重构 React 15 整个架构 、提升整体性能
React 16 架构可以分为三层:1.Scheduler 2. Reconciler 3.Renderer
Scheduler(调度器): 调度任务的优先级,高优任务优先进入Reconciler
如果我们以浏览器是否有剩余时间作为任务中断的标准,Scheduler 则为我们提供了一种在浏览器空闲的时候提醒我们触发继续更新回调的机制。部分浏览器已经实现了这个 API 即requestIdleCallback,由于浏览器兼容性以及浏览器切换 tab之前 tab 注册的 requestIdleCallback 触发的频率会变得很低等诸多不稳定的原因,React 实现了功能更完备的requestIdleCallbackpolyfill,这就是 Scheduler。除了在空闲时触发回调的功能外,Scheduler还提供了多种为任务调度优先级的功能。
Reconciler(协调器):负责找出变化的组件
新的 Reconciler 内部采用了Fiber的架构。reconciler 中以往的 mountComponent 和 updateComponent 中的更新工作从递归变成了可中断的循环。每次循环都会调用 shouldYield 判断当前是否有剩余时间。Reconciler 与 Renderer 不再是交替工作。当 Scheduler 将任务交给 Reconciler 后,Reconciler 会为变化的虚拟 DOM 打上代表增/删/更新的标记,整个 Scheduler 与 Reconciler 的工作都在内存中进行。只有当所有组件都完成Reconciler 的工作,才会统一交给 Renderer。
Renderer(渲染器): 负责将变化的组件渲染到页面上
Renderer根据Reconciler为虚拟 DOM 打的标记,同步执行对应的 DOM 操作。
其中红框中的步骤随时可能由于 1.有其他更高优任务需要先更新 2.当前帧没有剩余时间 等原因被中断,由于红框中的工作都在内存中进行,不会更新页面上的 DOM,所以即使反复中断,用户也不会看见更新不完全的 DOM。
由于Scheduler和Reconciler是平台无关的,所以React为他们单独发了一个包react-Reconciler,我们可以用这个包自己实现一个ReactDOM。
React 17 (2020.10.20)——事件委托机制变化,New Jsx Transform,局部优化、发展新特性
React 18(2022.3.29)——Concurrent、Suspense、Transitions,局部优化、发展新特性
React 18 为未来的技术扩展奠定了基础,如支持更多的并发场景、改进服务端渲染 SSR 等,增强了框架的灵活性。
React接近于原生库,专注于提供一个非常基础的UI模型、提供更底层的原生实现,基于此你可以构建出一套属于自己的抽象。
- 可以在服务器上运行 React,以实现 SEO、性能、代码共享和整体灵活性。
- 可以在 Web Worker 中运行 React 应用程序,并使用 React 通过 Objective-C 桥接器驱动本机 iOS 视图。
- 事件在所有浏览器(包括 IE8)中以一致、符合标准的方式运行,并自动使用事件委托。
- 视图可以使用canvas而不是 HTML呈现。
项目背景
从官网看React的理念:“我们认为,React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式。它在 Facebook 和 Instagram 上表现优秀。” React 18 重点通过更好的资源管理、调度策略来提高用户的感知性能,解决影响了快速响应的两个瓶颈:1.CPU的瓶颈,2.网络IO的瓶颈。而落实到实现上,则需要将同步的更新变为可中断的异步更新。
解决 CPU 瓶颈的关键在于:将同步的更新转化为可中断的异步的更新。首先在组件变得复杂节点数量高达上千的时候,很容易遇到 ****CPU ****的瓶颈,由于JS脚本执行和浏览器的布局和绘制不能同时进行,而主流浏览器刷新频率为 ****60HZ,也就是说,在理想情况下,我们应该在 ****16.6ms ****内完成 ****JS ****脚本绘制、浏览器布局和绘制,当 ****JS ****脚本执行过长,超出了 ****16ms,那么在本次刷新中,就没有时间执行样式布局和绘制了,这就造成了页面掉帧,形成了用户视觉感知上的卡顿。由于渲染函数的动态特性使得其难以优化,最后 React对于这块的解决方案就是不把重心放在加快 vdom 本身的速度,而是在于如何提高感知性能。React ****为了在提高感知性能的方向上去解决这个问题,于是决定在浏览器刷新的每一帧中,预留5ms给js执行脚本更新组件等,当预留的时间不够用的时候,React ****就会将线程的控制权交给浏览器让其有时间布局和绘制 ****UI,减少掉帧的可能性,React 则等下一帧时间到来继续被中断的工作。这种将长任务分拆到每一帧中,称为时间切片。通过使用ReactDom.unstable_createRoot(rootEl).render()启用 Concurrent mode 并发模式就可以启动时间切片。
解决网络IO的关键在于:在网络延迟客观存在无法避免的情况下通过加载占位渐少用户对网络延迟的感知。 如果用户的请求时间超过一个范围,前端就可通过 suspense 和配套的 hook 即useDeferredValue 来显示loading的效果。而在源码内部,同样需要将同步的更新转化为可中断的异步更新。
Concurrent Mode 是 React 未来的发展方向,而 Hooks 是能够最大限度发挥 Concurrent Mode 潜力的Component 构建方式。
职责范围
React的职责范围是缩小的职责范围,和 Vue相反。好处是刚开始学习 React 的时候需要理解的概念很少,具有更好的灵活性,库只提供了部分底层的原生实现,但我们可以构建任意复杂的系统。基于这些,React为我们提供了一个非常活跃的生态系统,我们可以看到他拥有一些非常灵活的工具,然后我们可以基于他发挥出更大的创造力,React 核心团队可以拥有更小的维护层面,以便他们可以专注于他们认为更加重要的事情、探索一些比较有创意的点子。缺点是需要用户或者社区去构建大量的抽象概念,以便在此提高使用 React开发的效率。而且React 生态系统发展太快可能会导致碎片化和使用者的不断流失,因为小职责范围的框架总是有着将问题抛给社区的特征。
项目生态
React 的职责范围是缩小的职责范围是 React 整个生态系统非常活跃的原因所在。React的社区环境像是一个商城系统,围绕着react的核心模型自底向上建立起来一整套生态系统,并拥有大量的插件、工具和第三方库,便于开发者构建各种应用。
项目应用
React 18 适用于构建高性能的 、 需要频繁更新 、 高交互性、支持大量并发用户、需要快速响应用户的Web 应用程序,如在线购物平台、社交网络、在线视频播放器、在线游戏等。对于那些不需要频繁更新、用户交互较少的应用,如简单的博客网站或静态页面,使用 React 18 可能显得过于复杂。
核心理念
React核心理念,是指React不会轻易发生变化的内容、是其之所以存在的基础原理。
梳理一下深入 React 18 源码前需了解的核心理念,后续小节中会给出每个核心理念的详细描述:
- 组件生命周期
- 虚拟 DOM的实现
- diff算法
- Fiber
- 组件渲染与更新
- Hooks
参考链接
- react技术揭秘
- react学习资源:
关于作者
作者:Wandra
内容:算法 | 趋势 |源码|Vue | React | CSS | Typescript | Webpack | Vite | GithubAction | GraphQL | Uniqpp。
专栏:欢迎关注呀🌹
本专栏致力于每周分享一个项目,如果本文对你有帮助的话,欢迎点赞或者关注☘️