React:数据驱动视图、组件设计模式
数据驱动视图是什么?(单向数据流)
传统的jquery
开发是通过操作dom
来实现页面的渲染和交互,而React
采用的是数据驱动视图的方式:view
是基于数据来渲染的,数据一旦变化,view
就会自动更新。因此我们在开发时,只需要关注数据即可,不用直接操作dom。
注意:React
不是响应式设计,因为它需要通过this.setState
、useState
等方式手动触发数据变化。
一、数据驱动视图原理
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
队列。isBatchingUpdates
:React
中用于标识当前是否处理批量更新状态,默认false
。dirtyComponent
:当前所有待更新state
的组件队列。- 将
pending
队列中的state
进行合并,得到最终要更新的state,并将pending
队列置为空。(该方法的执行时机不确定,应该在事务结束后?)
处理过程:
setState()
首先将接收的第一个参数state
存储在pending队列
中;(state)- 判断当前
React
是否处于批量更新状态,是的话就将需要更新state的组件添加到dirtyComponents
中;(组件) - 不是的话,它会遍历
dirtyComponents
的所有组件,调用updateComponent
方法更新每个dirty
组件(开启批量更新事务),每个组件有:
2. 执行更新阶段的生命周期 => dirty组件的更新
调用setState
会默认调用组件更新阶段的5个生命周期,依次是:
- 执行生命周期
getDerivedStateFromProps
- 执行生命周期
shouldComponentUpdate
,根据返回值判断是否要继续更新。 - 执行生命周期
render
:执行真正的更新。 - 执行生命周期
getSnapshotBeforeUpdate
- 执行生命周期
componentDidUpdate
3. render:重新构建虚拟dom树,执行 diff 并更新到真实dom => view更新
我们知道在React
的生命周期里,无论是挂载还是更新阶段,在render
之前的生命周期函数都不会更新this.state
和props
,直到render
执行完成后,数据才会更新。(只有shouldComponentUpdate
返回false
时例外,此时会中断更新过程,但依然会更新this.state
)
3.1 虚拟dom树:dirty组件的组件树
jsx
通过babel
转换成React.createElement
;createElement()
函数返回了一个了ReactElement
函数;ReactElement
函数返回的对象就是虚拟dom(Fiber 链表结构);Fiber
节点通过return、child、sibling
属性连接成Fiber Tree
。
当state或props改变时,会再次调用render生成一个虚拟dom树。
3.2 diff:批量更新,使虚拟dom和真实dom保持同步
比较新旧两个虚拟dom树,生成一个补丁,最后批量把补丁更新到真实dom上。
它会把收集到的多个补丁集暂存到队列中,最终实现集中的dom批量更新。
协调Reconciliation
过程是将虚拟dom和真实dom保持同步,它包含diff
算法,但它加了一些启发规则。
二、事务:react 17已经删除
- 事务
Transaction
就是将目标函数用wrapper
封装起来,通过事务提供的perform
方法去执行它。 - 在目标函数执行前,会先执行所有
wrapper
的initialize
方法、做一些初始化工作;目标函数执行完后,再执行所有close
方法、做一下清理的工作。 - 一组
initialize
和close
方法称为一个wrapper
,事务支持多个wrapper
叠加。
三、组件设计模式
1. 前端工程化、模块化、组件化
能够降低成本、提供项目质量和开发效率的事情都属于工程化,比如程序的性能、稳定性、可维护性等。前端工程化可以细分为模块化、组件化、规范化和自动化 4个方面。
-
模块化:侧重于业务功能层面的拆分,比如购物、直播功能。一般来说,一个模块就是一个实现特定功能的文件,可以通过多个组件来构建。
JS
模块化方案有AMD、CMD、module...
,CSS
模块化方案有less、sass..
。 -
组件化:侧重于UI设计层面的拆分,比如提交按钮、确认按钮。组件独立可复用,组件之间自由组合。(每个
UI
组件都应该包含html、css、js
文件) -
规范化:包括编码规范(
eslint
、文件命名规范..)、开发流程规范(code review
..)等。 -
自动化:包括自动化测试、构建、部署等,将简单重复的工作交给机器来做。(开发者提交本地代码后,
merge master
后就会跑自动化测试,包含单元测试、集成测试、e2e
测试;测试通过后,代码会合入master
,然后开始build
,将源码转换为可运行的实际代码,比如安装依赖、配置各种资源等;然后当前代码就是一个可以直接部署的release
版本)
2. 如何封装一个组件
React、Vue
等框架都在引领着前端的组件化开发方向,组件封装含义是不会直接暴露内部结构,而是提供props
去控制组件。
1. 为什么要封装组件?
- 有利于代码的复用,减少代码冗余。
- 有利于代码的维护。
- 有利于单元测试。
2. 封装组件需要考虑的点
-
单一职责原则:一个组件只负责一件事情,不要耦合一些没必要的逻辑。
-
可复用性:需要考虑适用的不同场景,考虑组件功能的通用性,可以被使用在多个UI场景。(公用组件更多要考虑通用性,项目组件则是需要处理当前业务中的特殊场景,业务部门一般都是项目组件)(UI组件只负责渲染,侧重复用性;业务组件侧重数据和业务的逻辑处理)
-
可组合:易于和其它组件一起使用,或者嵌套在另一个组件内部
-
可维护性:每个小的组件仅仅包含自身的逻辑,更容易被理解和维护。像业务组件如果过度追求可复用性,兼容各种页面,这就会导致它本身变得难以维护。
-
组件的粒度:拆分并不是越细越好,根据具体的业务场景分析,尽量复合高内聚、低耦合的思路,使自己的组件易于维护。
参考:
转载自:https://juejin.cn/post/7084900386489761799