zoukankan      html  css  js  c++  java
  • 转-react源码分析-设计思路与流程架构

    转载于:https://www.yuque.com/ant-h5/sourcecode/pizqme
    作者: ant-h5, 蚂蚁金服前端团队
    转载方式: 手打
    转载者: lemon-Xu


    React设计灵感

    react最初的设计灵感源于游戏, 游戏渲染的机制: 当数据变化时, 界面仅仅更新变化的一部分形成新的一帧渲染.

    设计react的核心是认为UI只是把数据通过映射关系变成另一种形式的数据, 也就是展现方式. 传统上, web框架使用模板或者html指令构造页面.

    react处理构建用户界面通过将他们分解为虚拟组件, 虚拟组件是react的核心, 整个react框架的设计理念, 都是围绕虚拟组件进行的.

    1.组件

    在界面变更时, react不直接操作dom, 而是通过组件对比(dom-diff), 找到界面更新前后组件的差异, 而只更新又变化的dom节点, 现有技术的时间时间复杂度O(n^3), n是页面上的元素, 这意味这如果页面上有1000个节点, 会对比10亿次, react重写了dom-diff算法, 把时间复杂度降低到O(n).

    react把组件分成了三类, 三类合称为: Reat Component, 它另一个名字我们也许更加熟悉: Virtual DOM.

    1.1 ReactTextComponent

    文件组件:

    <div>
        <span>hello</span>
        world
     </div>
    

    这里, world就是文件组件, 为什么单独给world设置成文字组件? 每个文字组件外面, 都需要包装一层span用来设置id(或者key), 用来diff.

    1.2 ReactNativeComponent

    html原生组件:

    <div>
        <span>hello</span>
        world
    </div>
    

    还是刚才那个例子, div和span都是原生组件. react的主要算法(如mount, dom-diff)都是在这里实现. 用来dom-diff的在渲染阶段会直接设置在原生组件标签上. 就像下面真样:

    1.3 ReactCompositeComponent

    react复合组件:

    <Table>
        <Table.Columns .../>
        ...
        </Table>
    

    这次我们来换个例子, 复合组件就是我们常见的react组件, 只不过在react内部叫做复合组件. 而react组件是三种组件的合成.

    复合组件有以下特点: 符合组件可以聚合其他的复合组件与原生组件, 但是最底层的复合组件一定只能集合原生组件. 复合组件通常以大写开头.

    1.4组件实现

    每个组件都有自己的props和children, props就是组件的属性, 比如style, id等. children是当前组件的子组件. 比如:

    <Form>
        <From.Item>
        用户名:
        <input placeholder="请输入用户名" />
        </Form.Item>
     <From>
    

    这段jsx会创建3个组件: From, Form, item, inpu, 前两个是复合组件, 最后一个是原生组件, 连小学生都能看懂他们的关系:

    • From
      • children:[ Form.item]
    • From.item
      • children: [span<ReactTextComponent, input>]
    • span
      • children: string
    • input
      • props:{placeholder: string}
      • children: [ ]

    在众多教材中, 这组树形结构被描述成为一个json:

    {
        "component": Form<ReactCompositeComponent>,
        "children": [{
            "component": Form.Item<ReactCompositeComponent>,
            "children": [{
                "component": span<ReactNativeComponent>,   
                "children": string<String> 
            }, {
                "component": input<ReactNativeComponent>,
                "props": { placeholder: string<String> },   
                "children": [] 
            }]
        }]
    }
    

    每个虚拟组件都有自己的children, 这样一来, 从container元素开始的dom树就有一棵结构相同的虚拟组件树.

    2. dom diff

    在实际项目中, 随着页面数据变化(用户交互)或者后端数据返回, 更新大多数只有三种情况:

    • dom的属性或者内容更新(update).
    • dom元素类型发生变化(insert).
    • dom元素的位置发生变化, 或者新增(insert), 或者删除(remove)

    当然还有第四种: 对于跨层级的移动更新少之又少, 比如像这种

    <div>
        <title>子标签</title>
        <input />
     </div>
    

    更新成

    <title>标签</title>
    <div>
        <input />
     </div>
    

    当然一些业务需要时(比如某同学转班或者能从左表格移动到右表格的穿梭框), 我们可以认为它是dom删除与dom插入两个操作, 而不是一次move(insertBefore), 因为这种业务并不多见.

    大多数情况下,我们只需要diff同一子节点下面的元素变化情况, 比如:

    <div>
        <h1>h1</h1>
        <h2>h2</h2>
        <h3>h3</h3>
    </div>
    

    更新成

    <div>
        <h2>h2</h2>
        <p>p</p>
        <h3>new h3</h3>
        <h1>h1</h1>
    </div>
    

    react是如何做到的呢?
    最外层的div标签一致, 然后对比div的children.

    • 循环第一个新节点h2和老节点h1, 发现节点数据类型不同, 删除掉h1, 插入h2.
    • 同理, p不等于h2, 删除h2, 插入p.
    • 继续, h3等于h3, 更新h3的内容new h3.
    • 最后, 发现之前没有h1, 插入h1.

    这就是一个最简单的dom-diff结果. 真实的dom-diff比这个复杂一点点, 原理大体相似, 我们先做个实验, 来验证下流程.

    • 经过判断和操作两个步骤.

    • 特殊说明

      • 如果一个有key, 一个没有key, 仍然是新增节点. 比如新节点没有key, 老节点有key, 仍然执行插入操作.
      • 如果能找到key, 但是key的顺序不一样, 则使用key去老的children按照顺序遍历, 当老的children顺序遍历完, 所有新节点全部重新插入. 比如abcdef改成了bcfdea, 则bcf在老节点都能找到, 而f已经是老节点最后一个节点, 所以之后的节点都是重新插入.
      • 以数组范围的作用域, 计算结果

    3. 层次

    3.1 展示层

    用户声明创建虚拟组件以及渲染虚拟组件.

    3.2 Virtual组件层

    react的绝大多数代码, 包括虚拟组件的创建, 渲染和更新流程dom-diff.

    3.3 基础功能

    所有公共代码, 提供底层功能.

    整体架构

    • ReactMount: 顾名思义, 负责react组件的渲染
    • ReactDOM: 用来创建native组件
    • ReactComponent: react虚拟组件
    • ReactOwnerReactCurrentOwner: 父子组件指针
    • ReactDOMIDOperations: 真实的dom操作
    • ReactMuticalChildren: dom-diff的实现
    • 功能层(黄色部分): 为react业务提供基础功能, 第六章会详细讲解

    模块调用关系以及流程图

    • 1.配置声明: 用户代码
    • 2.组件实例化: ReactComponsiteComponent.createClass
    • 3.组件渲染: ReactMount.renderComponent
    • 4.处理事件回调: renderComponent -> ReactMount.prepareToLevelEvents
    • 5.挂载事件: ReactNativeComponent._updateDOMProperties -> ReactEvent.putListener
    • 6.界面更新: ReactComposite.setState | ReactComponsite.reveiveProps
    • 7.dom-diff: receiveProps -> ReactMutiChild.updateMultiChild
    • 8.组件渲染或更新: ReactDOMIDOperations -> DOMChildrenOperations

    转载于:https://www.yuque.com/ant-h5/sourcecode/pizqme
    作者: ant-h5, 蚂蚁金服前端团队
    转载方式: 手打
    转载者: lemon-Xu

  • 相关阅读:
    ubuntu 启动 重启 停止 apache
    /usr/bin/env: php: No such file or directory 【xunsearch demo项目体验】【已解决】
    安装mezzanine时报:storing debug log for failure【已解决】
    redhat 安装 setuptools【成功】
    SnowNLP:一个处理中文文本的 Python 类库[转]
    Android 了解1G 2G 3G 知识
    Android-体系架构
    Android-bindService远程服务(Aidl)-初步
    Android-SDCard外部存储文件读写
    Android-文件模式
  • 原文地址:https://www.cnblogs.com/xiaoxu-xmy/p/13648500.html
Copyright © 2011-2022 走看看