Open yaofly2012 opened 3 years ago
组件根据当前的props
和state
生成vDOM树的过程,即组件转成vDOM的过程。
注意整个过程都是同步的,不过在Concurrent Mode 下会存在异步场景(下面说到)。
整个渲染过程大致分为三个阶段(主要是1,3):
每个阶段做的事情,可以做的事情,不可以做的事情。
在Concurrent Mode Render阶段的生命周期函数可能会被终止,暂停,重新执行,所以这些生命周期函数必须是Pure,保证是个纯函数,不能有副作用。
DOM已经被修改了,refs
也已经更新了,但是因为调用栈还没空,浏览器无法渲染DOM。
Commit阶段的生命周期函数可以访问最新的DOM(但用户还没看到)和refs了,并且触发执行都是同步的。
React文档里提到State Updates May Be Asynchronous。
异步渲染只是指批处理state
更新,只触发一次渲染。
注意:更新state
,计算最新的state
,触发相关生命周期函数和render
函数执行这整个过程都是同步的,这也是React下一步要优化的点。
state
更新队列在一个调用栈里(比如事件处理函数里)多次setState
调用会统一批处理。等函数执行完后,在批处理state
更新,并计算最新的state
。
const [counter, setCounter] = useState(0);
const onClick = async () => {
console.log('onClick begin')
setCounter(2);
setCounter(3);
console.log('onClick end')
}
console.log('render ', counter)
return (<div onClick={onClick}>Count: {counter}</div>)
点击看下输出:
onClick begin onClick end render 3
class
组件批处理执行时机class
组件中当回调函数执行完毕后会立马执行批处理,并计算最新的state
。
export default class Default extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
componentDidMount() {
console.log('componentDidMount begin')
this.setState((state) => {
console.log('componentDidMount.setState.callback: ', state.count)
return {
count: state.count + 1
}
})
console.log('componentDidMount: ', this.state.count)
this.setState((state) => {
console.log('componentDidMount.setState.callback: ', state.count)
return {
count: state.count + 2
}
})
console.log('componentDidMount: ', this.state.count)
Promise.resolve().then(() => {
console.log('componentDidMount done in promise');
})
}
componentDidUpdate() {
console.log('componentDidUpdate')
}
render() {
const { count } = this.state;
console.log('render: ', count);
return (
<div>
<p>Count: {count}</p>
</div>
)
}
}
组件渲染后控制台输出:
但是对于函数组件情况复杂些,存在立即计算和懒计算策略,并且默认采用立即计算策略。想知道原因就戳解密React state hook
内部利用unstable_batchedUpdates
实现的,并且这个函数是对外导出的。
同步渲染则是每次调用setState
都会同步的计算最新的state
,如果state
发生变化则触发render以及相关生命周期函数。
注意:整个过程都是同步的。
const [counter, setCounter] = useState(0);
const onClick = async () => {
setCounter(0);
setCounter(1);
const data = await fetchSomeData();
setCounter(2);
setCounter(3);
}
上面代码会触发3次渲染。
如果是同步渲染,则在处理函数里最好只触发一次状态更新。等确定最终值时再调用setState
。
就如同react-dom函数unstable_batchedUpdates
的实现的注释说的那样,此时:
Batching should be implemented at the renderer level。
使用unstable_batchedUpdates
函数
import { unstable_batchedUpdates } from 'react-dom'
const onClick = async () => { setCounter(0); setCounter(1);
const data = await fetchSomeData(); unstable_batchedUpdates(() => { setCounter(2); setCounter(3); }) }
## 2.3 总结
### 1. 批处理的场景
- 生命周期函数
- React事件处理函数
- `render`函数
### 2. 不会进行批处理的场景
非上面的场景,如:
1. 异步回调函数
- `setTimeout`/`setInterval`回调函数
- `Promise.then`回调函数
- `XMLHttpRequest`回调函数
2. 非React事件处理函数
因为这些函数执行不是React能控制的(脱离的React的上下文),无法进行批处理优化。
# 三、跳过DOM渲染
## 3.1 现状
Commit阶段的生命周期函数里(`componentDidMount`, `componentDidUpdate`, `useLayoutEffect`)调用状态更新会跳过当前的DOM渲染。
React依旧会进行批处理,计算最新的`state`,触发渲染执行相关的生命周期函数(也包含`useLayoutEffect `的回调函数)。 但是会跳过当前组件渲染:
1. 整个过程都是同步进行的,会阻塞浏览器渲染,也就是说最终浏览器只会渲染最终的`state`值,会跳过中间的`state`值。
2. 也会跳过`useEffect`回调函数;
3. 跳过子组件调用。
`class`组件和[函数组件效果](https://github.com/yaofly2012/note/issues/204)一样。
## 3.2 效果
>Immediately re-render with the updated data
`render`函数里更新`state`也会同样的效果。
React防止阻塞浏览器渲染太久,会设置个重新渲染触发次数,超过了会报错:
>Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
## 3.3 用途
1. Hooks可用于实现[`getDerivedStateFromProps`](https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops);
2. `state`依赖DOM的属性,在Commit阶段利用`refs`获取DOM的属性,让后再更新`state`。
# 参考
1. [React lifecycle methods diagram.](https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/)
2. [解密React state hook](https://github.com/yaofly2012/note/issues/204)
3. [Simplifying state management in React apps with batched updates](https://blog.logrocket.com/simplifying-state-management-in-react-apps-with-batched-updates/)
4. [Batch Your React Updates](https://dev.to/raibima/batch-your-react-updates-120b)
常见问题:
render
函数不能返回undefined
;props
没有变化,也会触发re-render;setState
更新state
,而Vue却可以直接通过赋值方式更新。组件化
鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM
渲染和更新
重新渲染整个组件 在 React 应用中,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树。看似这样容易造成不必要的渲染,从而造成性能问题,但是大部分情况下性能不是问题。如果真的遇到性能问题可以采用优化手段避免不必要的渲染?
PureComponent
/React.memo
shouldComponentUpdate
主要表达如何构建新vDOM树。即使重新渲染整个组件子树,但性能一般也不是问题,因为这些都是JS执行的,并没有操作DOM。
vDOM如何跟真实DOM关联的? 利用结构关系,vDOM和生成的DOM结构是一致的。
列表渲染为啥需要
key
?queues a state update
props.children
is always a new reference同步&异步
同步就会立马拿到最新值吗?
reconcilers
unstable_batchedUpdates
跨组件层级数据传递
设计模式
参考