最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • react技术栈:从原理到源码

    正文概述 掘金(卖油条的。)   2021-02-16   596

    本文会讲react生态中三类、四个主要library的运行原理和必要的源码验证:

    • react
    • redux
    • mobx
    • react-router

    react参考版本为v17.0.2

    继续阅读之前,如果没看过这篇讲框架的文章,建议先去瞄几眼,后面的讨论中涉及到其中的概念便不再重复。

    本次发布主要包括前两章,其余部分后续会陆续补齐。

    1 react相关概念

    react是一个ui library,使用组件构建可交互ui,并会在数据发生变化时及时更新。

    1.1 react mental modal

    我们先看一下react最初的设计思路,感受一下递进式的设计过程。

    以下前四步在这里有完整代码示例,其余步骤的伪代码可点上面链接查看。

    1. 转换

    react将ui简化为data到另外一些data的映射,相同的输入应返回相同的输出。

    1. 抽象和组合

    一个复杂ui当然不能用一个简单的函数表示,因此可以将ui抽象为多个可复用的片段,然后可以组合起来,即使用一个函数调用另一个函数。

    1. 状态

    一个ui并不只是服务端数据或业务数据的副本,还有很多大量数据表示应用中特定场景的状态快照,比如在输入框中键入、滚动条位置。
    因此我们倾向于将数据模型设计为不可变的,并可以通过一个顶层的原子操作就刻意更新状态,代码示例中通过回调来触发重新渲染,这和react实际的数据更新检测模型不太一致,这种主动通知更新的称为push,实际为pull。

    1. 缓存

    一次次调用同一个纯函数(没有副作用的函数,且结果只受参数影响)是很浪费性能的,我们应创建一个可缓存的版本,通过保存参数和结果,只要参数不变,就不用重新执行。

    1. 逻辑和数据分离

    实际的开发中有很多地方需要管理这些状态,为了复用,我们可以使用柯里化,封装业务逻辑后再输入状态。

    1. 列表

    很多ui中会产生很多列表,为了保存这些列表项,可以维护一个map进行保存,其中key可为各项的唯一id。
    当我们需要保存很多列表项时,就需要找一个好的缓存策略,来平衡使用频率和占用内存,好在ui列表一般不会太改变,因此可以借助ui中的树来保存(比如dom树或者虚拟dom树等数据结构)

    1. Algebraic Effects

    有时候我们不希望穿过一层层抽象(这里可以理解为函数调用,延伸为抽象树的一个层级)传递每一份数据,即我们需要一个功能可以透过至少两层进行直传,这在react中称为context。
    我们可以通过Algebraic Effects的思想实现这个功能,即将做什么和怎么做解耦,如果在当前层级获取不到数据,就将控制权交给另一个获取该数据的逻辑,然后将数据返回后归还控制权继续执行原来的逻辑,可以参考什么是 Algebraic Effects(代数效应)?和Algebraic Effects,以及它在React中的应用。

    1.2 深入react编程模型

    这里参考将 React 作为 UI 运行时,会对react中的一些概念做进一步的了解。

    1. 宿主树

    react最终会输出一棵随时间变化的树,这被称为宿主树,比如dom树、json对象等,它们是所在宿主环境的一部分。
    宿主树的各个节点被称为宿主实例,比如一个dom节点;这些宿主实例有自己的属性,比如domNode.className;宿主环境会提供相关api操作这些宿主实例,比如appendChild。

    1. 渲染器

    渲染器被react用来管理宿主实例,比如react dom,会将react在内存中管理的virtual输出到对应宿主环境。
    每个渲染器都有一个入口,指定将react元素树输出到最终的宿主环境中。

    1. react元素

    在宿主环境中宿主实例是最小的构建单位,在react中对应的是react元素,是一个普通的js对象,用来描述宿主对象,react元素之间也组成了一棵树用来表示整个ui,比如

    // JSX 是用来描述这些对象的语法糖。
    // <dialog>
    //   <button className="blue" />
    //   <button className="red" />
    // </dialog>
    //以下只包含部分属性
    {
      type: 'dialog',
      props: {
        children: [{
          type: 'button',
          props: { className: 'blue' }
        }, {
          type: 'button',
          props: { className: 'red' }
        }]
      }
    }
    
    1. 协调

    即reconciliation,当调用render首次渲染时,react会维护一个react元素组成的树,当下一次状态更新时,render就会返回另一棵最新的树,react需要基于这两棵树之间的差别有效率的更新ui以保证状态和ui同步,这个过程就被称为协调,这是本文的重点,会在后文详细介绍。

    1. 组件

    上一节我们提到react将ui简化为data到另外一些data的映射,当映射的目标,也就是函数的输出为react元素时我们称这些函数为组件,另外返回组件的函数也是组件,除了函数组件,还有class组件。

    1. 控制反转

    我们的组件可以是函数,比如Form,但是实际使用时用的是<Form />,而不是Form()

    // ? React 并不知道 Layout 和 Article 的存在。
    // 因为你在调用它们。
    ReactDOM.render(
      Layout({ children: Article() }),
      domContainer
    )
    
    // ✅ React知道 Layout 和 Article 的存在。
    // React 来调用它们。
    ReactDOM.render(
      <Layout><Article /></Layout>,
      domContainer
    )
    

    这种写法可以将控制权交给react,完成后面的工作。

    1. 缓存

    当react中组件状态发生变化时,会默认协调触发setState的整个子树,有些方法可以保存之前的协调结果,比如通过React.memo或PureComponent,当props通过浅对比先后没变化时就会跳过该组件的协调。

    1. 批量更新

    react中的数据更新是异步的,当执行setState等更新数据后,不会立刻触发协调, 而是会根据内部的调度机制批量更新

    1. 副作用

    副作用是指函数做了与本身返回值无关的事,比如数据获取,手动更新dom。

    2 fiber架构

    本章参考

    • React Fiber Architecture
    • Inside Fiber: in-depth overview of the new reconciliation algorithm in React
    • In-depth explanation of state and props update in React
    • The how and why on React’s usage of linked list in Fiber to walk the component’s tree
    • 协调

    2.1 reconciliation

    协调分为render和commit两个阶段,前者用来diff两个virtual dom的区别,后者将diff出来的变化渲染到宿主环境。
    下面讲一下在react中的diff算法,这个算法基于以下两个假设

    • 两个不同类型的元素产生不同的树
    • 子元素上的key属性会在不同渲染下保持稳定

    具体为

    • 对于根节点
      • 当根节点不同时,react会拆卸原来的树并且建立新的树,原来的状态被销毁。
      • 当根节点相同时,react保留节点,对比更新的属性,然后对子树递归diff
    • 对于子结点
      • 如果是列表,即子元素相同,则应添加key属性以复用,就会按顺序对比
      • 对于组件元素,如果类型相同,则实例复用更新状态,否则不复用。

    2.2 为什么引入新架构

    前面我们讲了前端框架中的变化侦测,当应用中状态发生变化,对前后两个virtual dom时有各种diff算法,这个算法的优化效果是有限的,且由于是仍然会有造成长时间阻塞,单帧处理超过16ms,造成卡顿的风险。

    在传统的实现中,virtual dom的diff被称为stack reconciler,即以调用栈的形式同步执行,中间不能有停顿,因此可能会影响主线程的其他工作造成卡顿。

    因此这里引入fiber架构,对diff流程进行优化,即将render阶段设置为增量的,如有必要分为多个帧执行,以给一些优先级高的任务,比如动画等,充足的的时间。

    2.3 什么是fiber

    fiber是一种新数据结构的virtual dom,以链表实现。

    新的架构使用自己设计的新数据结构,新的数据结构利用window.requestIdleCallback()和window.requestAnimationFrame()将不同优先级的工作分别调度以实现目的。fiber架构中以fiber节点为单位,每个单位是一个执行单元。

    2.4 fiber reconciler的目的

    这里对fiber的目的做一下汇总

    • 将可中断的任务分片,并可重置或复用
    • 能够对不同任务调整优先级
    • 能够在父元素和子元素之间交错处理,以支持react中的布局(这个场景暂时没想到)
    • 能够在render中返回多个元素
    • 更好的支持error boundaries

    2.5 fiber reconciler相关概念

    我们用一个例子来说明整个过程,可以在这里在线演示。

    class ClickCounter extends React.Component {
        constructor(props) {
            super(props);
            this.state = {count: 0};
            this.handleClick = this.handleClick.bind(this);
        }
    
        handleClick() {
            this.setState((state) => {
                return {count: state.count + 1};
            });
        }
    
    
        render() {
            return [
                <button key="1" onClick={this.handleClick}>Update counter</button>,
                <span key="2">{this.state.count}</span>
            ]
        }
    }
    
    1. 从react元素到fiber节点

    前面我们介绍过react元素,每个react元素描述了一个宿主实例。每个react元素又对应一个fiber节点,其中保存着组件和dom的一些状态。每次组件的render方法执行时,react元素都会重新生成,而fiber节点可能会复用。

    前面一直提工作这个词,那么工作具体指的是什么呢,比如调用生命周期函数,或者更新refs,不同类型的react元素的工作是不一样的,在这个简单例子中,ClickCounter组件调用生命周期方法,span host组件同步dom,每个fiber节点上都保存着一些需要处理的工作信息,因此可以看成是一个工作单元。这些工作的执行可以被跟踪、调度、暂停和取消。

    当由react元素生成fiber节点时,除了把并在的react元素对应的fiber节点删除和复用旧节点(如有必要会修改属性),还会根据key属性在同一层级移动。

    1. fiber tree

    fiber节点构成一棵树,即之前提到的virtual dom,本例中的树是这样的

    react技术栈:从原理到源码
    树中的节点使用child, sibling and return属性连接,其中child表示子结点,sibling表示同级节点,return表示父节点。

    在一个react应用中维护了两棵fiber树,当首次渲染中,react会生成一个fiber树来对应当前ui,这棵树一般被称为current,当react修改状态引起更新,会创建另一棵树来表示下一次渲染,这棵树被称为workInProgress,两棵树使用alternate互相引用。
    所有工作都在workInProgress树上进行,这棵树可以认为是current树的草稿,react会在这个草稿上处理完所有组件,然后一口气渲染到屏幕。当其被渲染到屏幕上以后就会成为新的current树。

    我们从图上可以看出fiber树的根节点是hostRoot,其作为一个current属性挂载到fiberRoot上,而后者可以在容器的_reactRootContainer._internalRoot属性上获取,比如

    const fiberRoot = query('#container')._reactRootContainer._internalRoot
    const hostRootFiberNode = fiberRoot.current
    
    1. fiber节点

    fiber树上的各个节点都是FiberNode类型,除了前面提的alternate, effectTag and nextEffect还有以下属性

    • stateNode 表示当前节点表示的组件实例、dom节点或react element,用来存储相关状态
    • type 节点相关的函数或class定义,如果对应dom元素,值是html标签,如果是其他则是对应组件实例或函数
    • tag 定义fiber节点的类型,决定这种节点包含什么工作
    • updateQueue 状态更新队列
    • memorizedState 表示上次渲染到屏幕上的state
    • memorizedProps 表示上次渲染到屏幕上的props
    • key 在前面介绍的用于处理列表内的子元素
    1. side-effects

    前面提过副作用相关概念,react中的,我们可以把一个组件看成是使用state和props作为参数,返回ui的函数,因此与此无关的都可看作副作用,比如同步dom或者调用生命周期函数,执行副作用本身也是一种工作,在每个fiber上有个effectTag字段保存这些副作用,各个节点上的副作用用firstEffect和nextEffect连成一个链表,被称为effects list,如fiber树中 react技术栈:从原理到源码

    副作用生成的链表为

    react技术栈:从原理到源码

    2.6 fiber reconciler详情

    这里是上一节(2.5)的延续。
    reconciler分为render和commit两步,第一步的结果是一个标记了side-effects的fiber tree,这个步骤是fiber主要作用的阶段,异步处理一些和ui没关的方面,当首次渲染时同步执行;第二步总是同步的,因为整个渲染过程应该是不间断的,而不能让用户看到渲染的中间过程,主要用于执行更新dom在内的副作用,这个过程结束。 两个阶段还会调用对应的生命周期函数,这些函数就是标记整个过程进行到哪些阶段的hook,类似的比如git hook。

    协调的具体细节会在源码阶段解读,现有的资料都比较旧了,和实际的实现不符,因此这里不过多赘述。

    2.7 优先级

    react系统中优先级分为五种,默认第三种

    export const unstable_ImmediatePriority = 1;
    export const unstable_UserBlockingPriority = 2;
    export const unstable_NormalPriority = 3;
    export const unstable_IdlePriority = 5;
    export const unstable_LowPriority = 4;
    

    3 fiber相关源码

    这里会包含最新版本整个协调过程,包括首次渲染和更新在内的源码。

    先推荐几个现成的

    • React@16.8.6原理浅析
    • React源码系列

    4 hooks

    hook是react16 除fiber以外的另一个重要更新,利用hook的方式使函数组件有了状态。

    参考

    • 深入 React Hooks 原理
    • React hooks 的基础概念:hooks链表
    • Under the hood of React’s hooks system
    • useEffect 完整指南
    • 函数式组件与类组件有何不同?

    5 事件系统

    react为了浏览器兼容和性能原因,在原生事件基础上封装了自己的事件系统。

    参考

    • React 事件系统工作原理
    • 动画浅析REACT事件系统和源码
    • 谈谈React事件机制和未来(react-events)
    • React v17.0 RC 版本发布:没有新特性

    6 状态管理

    本部分会分别介绍redux和mobx原理及其对比

    参考

    • Mobx总结以及mobx和redux区别
    • Redux or MobX: An attempt to dissolve the Confusion

    7 react-router

    本部分对react-router相关原理做讨论

    参考

    • Understanding The Fundamentals of Routing in Reac
    • reactjs routing
    • React Router源码浅析

    起源地下载网 » react技术栈:从原理到源码

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元