React原理篇
函数式编程
- 函数是"第一等公民"
- 不可变值的重要性
vdom和diff算法
这里可以参考前面vue原理篇,因为react和vue底层都是基于diff算法实现,不过两个框架的具体实现和函数命名会有一些差异。
- h函数(render函数)
- vnode数据结构
- patch函数
jsx本质
因为jsx
可以直接被babel编译,我们来到babel官网编译jsx模板看看结果:
这是一段jsx代码:
const imgElem = <div id="div1">
<p>AirHua</p>
<img src={imgUrl}/>
</div>
编译后的结果:
const imgElem = /*#__PURE__*/React.createElement("div", {
id: "div1"
}, /*#__PURE__*/React.createElement("p", null, "AirHua"), /*#__PURE__*/React.createElement("img", {
src: imgUrl
}));
我们可以联系前面讲到的vue模板编译来理解jsx模板编译了:
- 由
React.createElement
返回vnode React.createElement
第一个参数可能是组件或者hrml tag
,就取决于首字母大写来解析了- 第二个参数是属性,没有的话为
null
- 第三个参数可以是几个单子元素,也可以子元素数组
合成事件
前面我们也提到过React里面的事件Event的不同,还是以这个例子来详细看看:
import React, { Component } from 'react';
export default class EventDemo extends Component {
constructor(props) {
super(props)
this.state = {
name: 'airhua',
list: [
{
id: '1-1',
title: '标题1'
},
{
id: '1-2',
title: '标题2'
},
{
id: '1-3',
title: '标题3'
}
]
}
}
clickHander = (event) => {
event.preventDefault();
event.stopPropagation();
console.log('target', event.target);
console.log('current target', event.currentTarget);
console.log('event', event);
// console.log('event.__proto__.constructor', event.__proto__.constructor);
console.log('nativeEvent', event.nativeEvent);
console.log('nativeEvent target', event.nativeEvent.target) // 指向当前元素,即当前元素触发
console.log('nativeEvent current target', event.nativeEvent.currentTarget) // 指向 root !!!
// console.log('nativeEvent.__proto__.constructor', event.nativeEvent.__proto__.constructor);
}
render() {
return <div>
<h1>{ this.state.name }</h1>
<ul>
{
this.state.list.map((item, index) => (
<li key={index} onClick={this.clickHander}>{ item.title }</li>
))
}
</ul>
</div>;
}
}
看看打印结果:
现象
- react的event是SyntheticBaseEvent ,模拟出来 DOM 事件所有能力
- event.nativeEvent 是原生事件对象,其 _proto_.constructor 是 PointerEvent
- 所有事件都被挂载到了root节点上(react17前是document,后面为了不污染document)
原理
react内部实现了SyntheticBaseEvent
,由root实例event去分发事件。
为何要合成事件机制
- 更好的兼容性和跨平台
- 绑定到root,减少内存消耗,避免频繁绑定
- 方便事件的统一管理(如事务机制)
setState和batchUpdate
前面React基础篇我们说过setState的三个特性
- 不可变值
- 可能是异步,可能是同步
- 同步时:传入对象会被合并,传入函数不被合并;异步时:不被合并
实际上这和batchUpadte机制分不开关系。
setState执行流程
- setState接收一个新状态不会立即执行,而是先放到pending(等待队列)中
- 判断isBatchingUpdates(是否批量更新模式)
true
: 将接收到的新状态保存到dirtyCompents中false
: 遍历所有dirtyComponents,并且调用updateCompent更新state,执行完毕将isBatchUpdates置为true
接下来我们可以通过一个场景来解释一下:
componentDidMount() {
this.setState({
count: this.state.count + 1
})
console.log('1', this.state.count) // 0
this.setState({
count: this.state.count + 1
})
console.log('2', this.state.count) // 0
setTimeout(() => {
this.setState({
count: this.state.count + 1
})
console.log('3', this.state.count) // 2
})
setTimeout(() => {
this.setState({
count: this.state.count + 1
})
console.log('4', this.state.count) // 3
})
}
打印结果为0 0 2 3
,解析一下执行过程就是:
- 前面两次修改为同步代码,此时
isBatchingUpdates:true
,所以前两次执行会被缓存到dirtyComponents
中,合并修改,所以打印为0。 - 等同步代码执行完毕后,
isBatchingUpdates:false
,所以执行setTimeout时,要先遍历dirComponents
,按顺序执行,就可以获取到值了 - 打印结果为
0 0 2 3
通过这些我们基本可以理解为什么setState会有这些特性了,最后我们再来看看batchUpdate机制实际是基于transaction机制来的
transaction
在React源码中transaction部分有一段字符是解释transaction的作用的:
* wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
我们可能写一段类似伪代码来解释
handler = () => {
// 开始initialize阶段 设置处于batchUpdate
// isBatchUpdates = true
// 逻辑操作
// anyMethod
// 结束close阶段
// isBatchUpdates = false
}
实质上就是React处理时给在自己管控之下的函数都加上了isBatchingUpdates
包括:生命周期(和它调用的函数)、React中注册的事件(和它调用的函数)
组件渲染过程
组件初始渲染
- 获取生成props state
- render()生成vnode(这里指的是React.createElement)
- patch(elem, vnode)
[源码里不一定是这个patch函数,patch只是之前snabbdom里的实现,但是功能基本一样]
更新过程
-
setState(newState)
-
render()生成newVnode
-
patch(vnode, newVnode)[这个阶段拆分成了两个阶段]
- reconciliation阶段-执行diff算法,纯js计算
- commit阶段 - 将diff结果渲染DOM
- 不拆分可能会导致性能问题,当组件复杂,计算和渲染压力都很大,还有DOM操作可能会卡顿
React fiber
- 将reconciliation(协调算法)阶段进行任务拆分
- DOM需要渲染时暂停计算,空闲时恢复
- window.requestIdleCallback