在写生命周期的例子时,我发现光是用函数组件没办法演示生命周期的效果,所以本节专门用类组件作为例子去实现,当作一个类组件的知识补充吧。
下文即将出现的Demo汇总如下
| 标题 | Demo |
|---|---|
| 组件通信方式 | 父传子Demo |
| 子传父Demo | |
| 生命周期 | Demo:UNSAFE_componentWillMount、UNSAFE_componentWillUpdate、UNSAFE_componentWillReceiveProps |
| Demo:getDerivedStateFromProps、getSnapshotBeforeUpdate | |
| 完整生命周期Demo |
组件通信方式(父传子,子传父)
自从有了hook以后,我们几乎更偏向使用函数组件(至少我是),但是在某些时候函数组件还不能完全代替类组件,也就是说现在的React开发者还是有必要去了解类组件的。
父传子
在类组件中,要注意方法里this的指向;
虽然改变state也是用setState方法,但是比hook的要复杂。
以下是关键代码:
import React from "react";
import Child from "./Child";
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
parentData: 1
};
// 绑定changeParentData里的this指向
this.changeParentData = this.changeParentData.bind(this);
}
changeParentData() {
this.setState({ parentData: this.state.parentData + 1 });
this.forceUpdate();
}
render() {
return (
<div>
<button onClick={this.changeParentData}>控制子元素显示值</button>
<Child showData={this.state.parentData} />
</div>
);
}
}
export default Parent;
完整代码请查看Demo
子传父
同样的注意this绑定的问题
如果你的方法写成这样:
sendChildData(data) {
this.setState({
childData: data
});
}
却没在构造器里加上bind绑定this
this.sendChildData = this.sendChildData.bind(this);
或者没有在render传入函数里加入bind绑定this
<Child sendChildData={this.sendChildData.bind(this)} />
那么就会报以下错误
你也可以把sendChildData变成箭头函数,从而不用写bind方法
sendChildData = (data) => {
.....
}
关键代码如下
import React from 'react';
import ReactDom from 'react-dom';
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
childData: null
}
this.sendChildData = this.sendChildData.bind(this);
}
sendChildData(data) {
this.setState({
childData: data
});
}
render() {
return <div>
<Child sendChildData={this.sendChildData} />
显示结果:<span>{this.state.childData}</span>
</div>
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
newData: "aaa"
}
sendData = () => {
this.props.sendChildData(this.state.newData);
}
render() {
return <div>
<button onClick={this.sendData}>修改父组件数据</button>
</div>
}
}
}
完整代码请查看Demo
生命周期
在讲生命周期之前,我们要注意的是有三个生命周期方法:
componentWillMount();组件渲染前执行componentWillUpdate();组件更新前执行componentWillReceiveProps();组件接收props数据前执行
当你使用它们的时候,编译器会报错提示你:
该方法在React 16.9.0已被废弃,用UNSAFE_componentWillMount来代替它。
当你用下列三个生命周期方法时:
UNSAFE_componentWillMount();UNSAFE_componentWillUpdate();UNSAFE_componentWillReceiveProps();
它又会提示你:
该方法直到React 17 都不会被废弃,同时它还介绍了两个新的生命周期给你getDerivedStateFromProps和getSnapshotBeforeUpdate。
新增生命周期:
getDerivedStateFromProps;完全取代已废弃方法componentWillReceivePropsgetSnapshotBeforeUpdate;完全取代已废弃方法componentWillUpdate
关于getDerivedStateFromProps和getSnapshotBeforeUpdate的使用,你可以点击查看Demo。
实际上,当你使用严格模式的时候,UNSAFE_xxxx那三个生命周期方法就不能执行且会报警告。
更多详细情况请查看reactjs.org/link/unsafe…
以下是常用的生命周期图谱
componentDidMount:Dom加载完毕。
componentDidUpdate: 组件已更新。
componentWillUnmount:组件将要卸载。
| 异 | componentDidMount | componentDidUpdate | componentWillUnmount |
|---|---|---|---|
| 执行周期不同 | 每次刷新页面的第一次挂载时 | 每次更新时 | 第一次也是最后一次卸载时 |
其中以下周期建议执行操作:
componentDidMount:依赖DOM的操作、网络请求、添加订阅
componentWillUnmount:清除timer、取消订阅
以下是完整的生命周期图谱:
shouldComponentUpdata:组件接收新的state或者props时判断是否更新,返回布尔值。
shouldComponentUpdata接收两个参数,一个是props,一个是state。
一般来说,上层组件改变state,下层组件接收props,当你把shouldComponentUpdata同时放在上下层组件里就更容易理解:
再次强调一下getDerivedStateFromProps和getSnapshotBeforeUpdate,它们也接收两个参数,props和state,与shouldComponentUpdata有明显的不同。
| 异 | getDerivedStateFromProps | shouldComponentUpdata |
|---|---|---|
| 执行周期不同 | 第一次挂载时和每次更新时,且先于shouldComponentUpdata方法执行 | 每次更新时,且先于render阶段执行 |
| 对传入参数的要求不同 | state必须要在该类中有初始值 | state若没有声明则为null |
| 返回值不同 | 返回一个对象更新state,或者返回null以表示新props不需要任何状态更新(返回值结果不影响实际渲染) | 返回布尔值,true代表允许重新渲染视图,否则停止渲染(返回值结果影响实际渲染) |
getSnapshotBeforeUpdate因为并不经常需要但有专门的使用场景所以得单独提出来说,它的返回值将作为第三个参数传递给componentDidUpdata,在手动保存重渲染期间的滚动位置时很有用。
完整生命周期示例代码请查看Demo
注意:因为getSnapshotBeforeUpdate、componentDidMount、componentDidUpdate和componentWillUnmount在render之后执行,所以个人习惯把它们放在render之后的位置。
特别注意:getSnapshotBeforeUpdate虽然在render之后,也可以读取DOM,但是在图谱的Pre-commit阶段,与可以运行副作用的Commit阶段是不同的。
以上插图均来自生命周期图谱
Fragment <></>
由于组件只能返回一个根元素,所以一般我们会在最外层套一个<div></div>,但是有时候如果我们写的组件并不希望最外层套一个不需要的<div></div>时,那么就可以使用React.Fragment来包在外层。
例如下面代码:
import React from 'react'
export default class Test extends React.Component {
render() {
return (
<React.Fragment>
<header>header</header>
<main>main</main>
</React.Fragment>
);
}
}
渲染效果如下
React.Fragment还支持短语法<></>,例如下面内容就和上面的等价:
import React from 'react';
export default class Test extends React.Component {
render() {
return (
<>
<header>header</header>
<main>main</main>
</>
)
}
}
渲染效果相同