React学习日记(一):框架原理

190 阅读5分钟

原先在博客上有过更新,之后会陆续更新一些对框架源码的解读。


前言

文章会围绕React前端组件化开发框架原理进行讨论。


提示:以下是本篇文章正文内容,下面案例可供参考

一、React设计思想

1.原生JS

React相比于vue来说更接近原生JS,因为在react内部,jsx模板经babel转化后是一个对象,所有的操作都是基于这个对象和其对应的fiber结构来操作的,而vue.js通过编译将templete模板转换成渲染函数(render),执行渲染函数就可以得到一个虚拟节点树。

2.实现和更新原理

React将每个节点转化为fiber对象,最终形成一个fiber树结构,来依次渲染。通过两个fiber的对比来实现更新。这里要说到几个diff算法,分别是tree diff,component diff和element diff。同时更新过程可能会被打断,让优先级更高的任务优先执行(例如浏览器渲染)

  • TREE DIFF 核心是层次遍历和层级监控。如果同层有节点不同则会删除它的子树进行重新渲染,所以设置state要使粒度合适的小一些。能带来性能上的提升。
  • COMPONENT DIFF 如果某两个节点不是一个类型则进行TREE DIFF,否则进行ELEMENT DIFF。
  • ELEMENT DIFF 先根据每个节点的ID进行移位检查比较是不是同一个ID,并得到它如何进行增删查改。

3.单项数据绑定

父传子用props,有子传父的方法,不支持双向绑定,需要手动配置。

4.模板的格式和转换

用jsx指定模板,jsx类似JavaScript的扩展,在函数组件返回jsx,或者class组件的render方法内返回jsx,jsx将转换成对象。

5.关注运行时

React 更像是一种编程运行时。

React 程序通常会输出一个会随时间变化的树。 它有可能是 DOM 树 、iOS 视图层、PDF 原语 ,或者是 JSON 对象 。不过通常我们希望用它来展示 UI 。我们称它为“宿主树”,因为它往往是 React 之外宿主环境中的一部分 —— 就像 DOM 或 iOS 。宿主树通常有它自己的命令式 API 。而 React 就是它上面的那一层。

所以 React到底有什么用呢?非常抽象,它可以帮助你编写可预测的,并且能够操控复杂的宿主树进而响应像用户交互、网络响应、定时器等外部事件的应用程序。

他有以下两个特性

稳定性。 宿主树是相对稳定的,大多数情况的更新并不会从根本上改变其整体结构。如果应用程序每秒都会将其所有可交互的元素重新排列为完全不同的组合,那将会变得难以使用。那个按钮去哪了?为什么我的屏幕在跳舞?

通用性。 宿主树可以被拆分为外观和行为一致的 UI 模式(例如按钮、列表和头像)而不是随机的形状。

这些原则恰好适用于大多数 UI 。 不过当输出没有稳定的“模式”时 React 并不适用。 (其实我没有很理解……可以查看这篇文章,写的很好把React作为UI运行时来使用

6.实例

(1)创建更新阶段

先执行React.createElement()函数将转化为一个ReactElement对象后,然后再执行ReactDOM.render()函数,这样就开始了ReactDOM的逻辑了。在ReactDOM.render里会经过一些列的初始化,创建一个fiber树。 我们可以把每个fiber理解为一个DOM对象的映射,绝大多数DOM对象都有一个其对应的fiber对象的(文本节点是没有fiber对象的,react直接通过nodeValue来设置了)。

创建一个React组件的方法

ReactDOM.render(<App/>,root)

babel转换后

React.createElement(React.createElement(App, null), null)

返回了一个对象

{
    $$typeof:Symbol(react.element),//表示当前对象是一个ReactElement对象
    type:App,
    key:null,
    ref:null,
    props:{},
    _owner:null

在这里插入图片描述

执行完创建更新阶段,之后就进入了React的任务调度阶段了。

(2)任务调度阶段

React中的任务分为同步和异步任务,如果是同步任务则会直接跳过这个阶段进入render阶段,对于异步任务来说,它会在浏览器渲染完之后利用空余的时间进行更新。

任务调度会利用requestAnimationFrame这个原生的API接口,requestAnimationFrame函数会在每次浏览器前执行的,执行的时候react利用了postMessage函数在任务队列里插入了一个函数,这样等到浏览器重绘完成后就会执行这个任务了,然后会触发相应的逻辑,执行第三步骤render阶段的相关操作。

总的来说会对fiber树单向遍历,父到子,子到孙,采用事件切片的任务调度方式,优先级高的任务和渲染先做,空闲时间做一些diff算法。

(3)render阶段

render阶段会依次遍历在第一步生成的fiber结构,利用深度优先遍历的算法,先遍历整个fiber树最左侧的fiber对象,然后再遍历到右侧的,最终回到最底层的根fiber对象,中间根据不同的组件类型做不同的处理,这个阶段也是整个React最难理解的一个阶段,因为有非常多的处理函数,还可以被高优先级的任务给打断,例如浏览器重绘,自定义事件等。这个阶段完成后在最顶层的fiber的firstEffect和lastEffect上设置一个链表,指向所有需要在commit阶段进行处理的fiber。

render采用深度优先遍历算法遍历形成的fiber结构。

(4)commit阶段

这个阶段比较简单,就是遍历第三步最后生成的Effect链,依次在每个fiber上执行收尾的工作。