父子组件生命周期:
“生命周期”细想之下有点浪漫主义色彩,不知道是不是从lifecycle英译过来的。作为一个前端从业者,如果让我来取,可能会取成“渲染周期”之类的,毕竟是和浏览器打交道的职业,浏览器的layout使dom树具有骨架,paint则让整个页面光亮起来。
React 的一切都是组件,通过 React.createElement 方法来创建嵌套层级,说白了在内存中构建对象树,据此渲染到浏览器中成为dom树,这个时候一个节点是什么时候真正渲染到页面中就变得重要起来,因为只有这个时候你才能真正和浏览器环境内的对象和方法交互,同样离开的时候也需要清理监听器等防止干扰后续逻辑,因此钩子函数,也可以说是生命周期函数就有了存在的意义。
先上代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="app"></div>
<div id="hollow"></div>
<script type="text/babel">
const { Component, Fragment } = React;
class F extends Component {
state = { x: 'state before' };
static getDerivedStateFromProps(getProps, getState) {
console.log('F get props', { getProps, getState });
}
componentDidMount() {
console.log('F did mount');
setTimeout(() => {
console.log('%c %s', 'color:blue', 'start to update state');
this.setState({ x: 'state after' });
}, 2000);
}
shouldComponentUpdate(nextProps, nextState) {
console.log('F should update', { nextProps, nextState });
return true;
}
componentDidUpdate(prevProps, prevState) {
console.log('F did update', { prevProps, prevState });
}
componentWillUnmount() {
console.log('F will unmount', Date.now());
}
render() {
return (
<div>
{this.props.x}
{this.state.x}
</div>
);
}
}
class App extends Component {
state = { x: 'props before ' };
componentDidMount() {
console.log('App did mount');
setTimeout(() => {
console.log('%c %s', 'color:red', 'start to update props');
this.setState({ x: 'props after ' });
}, 2000);
}
componentWillUnmount() {
console.log('App will unmount', Date.now());
}
render() {
return <F {...this.state} />;
}
}
setTimeout(() => {
ReactDOM.render('unmount Component App at ' + Date.now(), app);
}, 6000);
ReactDOM.render(<App />, app);
</script>
</body>
</html>
Props 和 State 相关
父组件 App 将自身的State传入了子组件 F 内,忽略挂载和卸载,列举生命周期函数:
1,getDerivedStateFromProps --
通过浏览器打印结果可以看到,不管是组件初始化,还是更新或继承新的 state 或者 props ,最先触发的都是静态钩子函数 getDerivedStateFromProps ;
2,shouldComponentUpdate --
在 setState 异步赋值了 state 的下一个状态后,整个子组件 F 开始收集和对比新旧状态,将新的状态输入到生命周期函数 shouldComponentUpdate 中,写明 return 的值是 truthy 还是 falsy 可以选择中断更新或者继续更新;
3,componentDidUpdate --
在 shouldComponentUpdate 顺利进入下一步后,将执行 render 方法,更新虚拟dom树,浏览器完成渲染,在此之后旧的状态将作为参数传给 componentDidUpdate ,可以对比新状态以及是否保留旧状态;
挂载 和 卸载
componentDidMount 和 componentWillUnmount --
观察父子组件的挂载生命周期函数,可以发现挂载时,子组件的挂载钩子先被触发;卸载时,子组件的卸载钩子后被触发;
对于挂载钩子,一般来说,应该将子组件从上至下依次挂载到一个 fragment 上,再整体挂载到dom树中,因为频繁操作dom树不仅影响性能甚至可能影响用户体验。
但是实际情况却并如此,我们经常在挂载函数上注册监听器,说明此时是可以与页面交互的,也就是说其实所有挂载钩子都是在父组件实际挂载到dom树上才触发的,不过是在父组件挂载后依次触发子组件的 componentDidmount ,最后再触发自身的挂载钩子,说白了,componentDidMount 其实是异步钩子。
相反,卸载的时候父节点先被移除,再从上至下依次触发子组件的卸载钩子;
但是我们也经常在卸载钩子上卸载监听器,这说明 componentWillUnmount 其实在父组件从dom树上卸载前触发的,先触发自身的卸载钩子,但此时并未从dom树上剥离,然后依次尝试触发所有子组件的卸载钩子,最后,父组件从dom树上完成实际卸载。
