likes
comments
collection
share

React:数据驱动视图、组件设计模式

作者站长头像
站长
· 阅读数 75

数据驱动视图是什么?(单向数据流)

传统的jquery开发是通过操作dom来实现页面的渲染和交互,而React采用的是数据驱动视图的方式:view是基于数据来渲染的,数据一旦变化,view就会自动更新。因此我们在开发时,只需要关注数据即可,不用直接操作dom。

注意:React不是响应式设计,因为它需要通过this.setStateuseState等方式手动触发数据变化。

一、数据驱动视图原理

class Calculator extends React.Component {
    constructor(props) {
        super(props);
        this.state = {val: 0};
    }
    render() {
        return(
            <div className="container">
                <button onClick={ () => {
                    this.setState({val: 1})
                }}>Submit!</button>
            </div>
        )
    }
}

React通过setState实现数据驱动视图,通过setState引发一次组件的更新过程从而实现页面的重新渲染(除非shouldComponentUpdate返回false)。假设我们点击button触发了onClick事件,然后它对应的监听函数调用了this.setState()来改变数据,之后的操作过程为:

1. setState:dirtyComponent => 批量更新

  • pending:当前所有等待更新的state队列。
  • isBatchingUpdatesReact中用于标识当前是否处理批量更新状态,默认false
  • dirtyComponent:当前所有待更新state的组件队列。
  • pending队列中的state进行合并,得到最终要更新的state,并将pending队列置为空。(该方法的执行时机不确定,应该在事务结束后?)

处理过程:

  1. setState()首先将接收的第一个参数state存储在pending队列中;(state)
  2. 判断当前React是否处于批量更新状态,是的话就将需要更新state的组件添加到dirtyComponents中;(组件)
  3. 不是的话,它会遍历dirtyComponents的所有组件,调用updateComponent方法更新每个dirty组件(开启批量更新事务),每个组件有:

2. 执行更新阶段的生命周期 => dirty组件的更新

调用setState会默认调用组件更新阶段的5个生命周期,依次是:

  • 执行生命周期getDerivedStateFromProps
  • 执行生命周期shouldComponentUpdate,根据返回值判断是否要继续更新。
  • 执行生命周期render:执行真正的更新。
  • 执行生命周期getSnapshotBeforeUpdate
  • 执行生命周期componentDidUpdate

3. render:重新构建虚拟dom树,执行 diff 并更新到真实dom => view更新

我们知道在React的生命周期里,无论是挂载还是更新阶段,在render之前的生命周期函数都不会更新this.stateprops,直到render执行完成后,数据才会更新。(只有shouldComponentUpdate返回false时例外,此时会中断更新过程,但依然会更新this.state

3.1 虚拟dom树:dirty组件的组件树

  1. jsx 通过 babel 转换成 React.createElement;
  2. createElement()函数返回了一个了ReactElement函数;
  3. ReactElement函数返回的对象就是虚拟dom(Fiber 链表结构);
  4. Fiber节点通过return、child、sibling属性连接成Fiber Tree

当state或props改变时,会再次调用render生成一个虚拟dom树。

3.2 diff:批量更新,使虚拟dom和真实dom保持同步

比较新旧两个虚拟dom树,生成一个补丁,最后批量把补丁更新到真实dom上。

它会把收集到的多个补丁集暂存到队列中,最终实现集中的dom批量更新。

协调Reconciliation过程是将虚拟dom和真实dom保持同步,它包含diff算法,但它加了一些启发规则。

二、事务:react 17已经删除

React:数据驱动视图、组件设计模式

  • 事务Transaction就是将目标函数wrapper封装起来,通过事务提供的perform方法去执行它。
  • 在目标函数执行前,会先执行所有wrapperinitialize方法、做一些初始化工作;目标函数执行完后,再执行所有close方法、做一下清理的工作。
  • 一组initializeclose方法称为一个wrapper,事务支持多个wrapper叠加。

三、组件设计模式

1. 前端工程化、模块化、组件化

能够降低成本、提供项目质量和开发效率的事情都属于工程化,比如程序的性能、稳定性、可维护性等。前端工程化可以细分为模块化、组件化、规范化和自动化 4个方面。

  1. 模块化:侧重于业务功能层面的拆分,比如购物、直播功能。一般来说,一个模块就是一个实现特定功能的文件,可以通过多个组件来构建。JS 模块化方案有AMD、CMD、module...CSS 模块化方案有 less、sass..

  2. 组件化:侧重于UI设计层面的拆分,比如提交按钮、确认按钮。组件独立可复用,组件之间自由组合。(每个UI组件都应该包含html、css、js文件)

  3. 规范化:包括编码规范(eslint、文件命名规范..)、开发流程规范(code review..)等。

  4. 自动化:包括自动化测试、构建、部署等,将简单重复的工作交给机器来做。(开发者提交本地代码后,merge master后就会跑自动化测试,包含单元测试、集成测试、e2e测试;测试通过后,代码会合入master,然后开始build,将源码转换为可运行的实际代码,比如安装依赖、配置各种资源等;然后当前代码就是一个可以直接部署的release版本)

2. 如何封装一个组件

React、Vue等框架都在引领着前端的组件化开发方向,组件封装含义是不会直接暴露内部结构,而是提供props去控制组件。

1. 为什么要封装组件?

  • 有利于代码的复用,减少代码冗余。
  • 有利于代码的维护。
  • 有利于单元测试。

2. 封装组件需要考虑的点

  • 单一职责原则:一个组件只负责一件事情,不要耦合一些没必要的逻辑。

  • 可复用性:需要考虑适用的不同场景,考虑组件功能的通用性,可以被使用在多个UI场景。(公用组件更多要考虑通用性,项目组件则是需要处理当前业务中的特殊场景,业务部门一般都是项目组件)(UI组件只负责渲染,侧重复用性;业务组件侧重数据和业务的逻辑处理)

  • 可组合:易于和其它组件一起使用,或者嵌套在另一个组件内部

  • 可维护性:每个小的组件仅仅包含自身的逻辑,更容易被理解和维护。像业务组件如果过度追求可复用性,兼容各种页面,这就会导致它本身变得难以维护。

  • 组件的粒度:拆分并不是越细越好,根据具体的业务场景分析,尽量复合高内聚、低耦合的思路,使自己的组件易于维护。

参考:

  1. How V-DOM and diffing works in React
  2. setState机制
  3. 协调中的diff思想
转载自:https://juejin.cn/post/7084900386489761799
评论
请登录