目录
2.3static getDerivedStateFromProps()
3.1static getDerivedStateFromProps()
5.1static getDerivedStateFromError()
生命周期
所谓的生命周期就是指某个事物从开始到结束的各个阶段,当然在 React.js 中指的是组件从创建到销毁的过程,React.js 在这个过程中的不同阶段调用的函数,通过这些函数,我们可以更加精确的对组件进行控制,前面我们一直在使用的 render 函数其实就是组件生命周期渲染阶段执行的函数
1.周期分类
React.js 为组件的生命周期划分了四个
不同的阶段,不同的阶段又会对应着一些不同的函数。
- 挂载阶段:组件创建到渲染到页面的过程。constructor()、render()、static getDerivedStateFromProps()、componentDidMount()
- 更新阶段:组件重新渲染的过程,组件 state 的更新(调用 setState())和父组件渲染都会触发。static getDerivedStateFromProps()、shouldComponentUpdate()、render()、getSnapshotBeforeUpdate()、componentDidUpdate()
- 卸载阶段:当组件从 DOM 中移除时会调用如下方法componentWillUnmount()
- 错误处理:当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法static getDerivedStateFromError()、componentDidCatch()
参考:http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
2.挂载阶段
挂载阶段是指组件创建到渲染到页面的过程,这个过程提供了四个
不同的函数
- constructor()
- render()
- static getDerivedStateFromProps()
- componentDidMount()
2.1constructor
constructor(props)
类的构造函数,也是组件初始化函数,只会在初始化创建时执行一次。一般情况下,我们会在这个阶段做一些初始化的工作
- 初始化 state
- 处理事件绑定函数的 this
组件标签的使用<Lifecycle />等同于new Lifecycle()。
2.2render()
render 方法是 Class 组件必须实现的方法。用于渲染和返回组件产生的结构。
2.3static getDerivedStateFromProps(props,state)
static getDerivedStateFromProps(props, state)
该方法会在 render 方法之前调用,无论是挂载阶段还是更新阶段(挂载阶段和更新阶段都会被调用),它的存在只有一个目的:让组件在 props 变化时更新 state。
- 该方法是静态方法,所以不能再该方法中使用this(因为静态方法属于类的方法不是属于实例的方法)。
- 当组件中的state依赖外部传入的props时(且内部私有状态也会可能会更改),可以使用此生命周期来处理这种需求。如使用this.state={v:this.props.a},外部传入props数据会影响数据a的变化,而内部私有状态state的改变也会影响数据a的变化,此时可以此生命周期方法。
- 这个生命周期函数一定要有返回值,返回值将被设置给state
案例:邮件发送-收件人选择
需求分析(应用场景):
如下,
- 收件人列表中:可以填写收件人邮箱地址。可把收件人列表看做一个独立组件,此收件人列表组件,有一个状态叫收件人列表,此状态可以由自己决定,同时收件人列表组件中有input输入框,当input输入框中输入内容时,就会更新组件私有状态state,这些组件内部的私有行为;
- 右侧好友列表组件:好友列表中可以随意选择某个人,选择的地址会传入到收件人列表组件(外部输入数据为props)。
- 即可以由外部传入数据到收件人列表组件内部,同时收件人列表组件内部也可以自己控制内部私有数据。
步骤:
收件人列表组件:方法一:使用onKeyDown()/onInput()/onPress()其一绑定该组件的方法keyDown(),该方法用于添加一个新收件人,通过判断(13===keyCode)(建议不要写成keyCode===13,因为一旦写错可能写成keyCode=13成了赋值,判断永远成立)时,将输入地址添加到地址栏(更新私有状态this.setState()),使用[...this.state.users,{name:'',mail:value}]通过扩展运算符将原来的地址和新添加的地址都重新进行更新,最后清空input框target.value = '';
收件人列表组件:方法二:使用非受控组件方式实现同样效果。在this.state中设置email='',再通过onChange监听并更新input框的值;
好友列表组件:点击某个好友时,添加一条好友地址到收件人列表组件。接收外部组件收件人列表组件传入的数据。e=>this.addUser(friends)使用箭头函数方式,首先调用的是箭头函数,当箭头函数执行时,再执行addUser()方法并接收外部传入数据friends。再将点击获取的数据更新到收件人列表组件;
添加的地址时累加到原有地址后而不是覆盖,此时就用到生命周期函数static getDerivedStateFromProps(),这个生命周期函数一定要有返回值,返回值将被设置给state(更新state)。getDerivedStateFromProps()只要是组件重新渲染都会执行,而此时不是每次都需要获得该方法里的props,而是只有得到user数据后才需要即判断if (props.user)时才更新即return {users:[...users,props.users]}。
案例代码实现:
mail.css:
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
li {
line-height: 30px;
}
.fl {
float: left;
}
.fr {
float: right;
}
.clear:after {
content: '';
display: block;
clear: both;
}
.box {
padding: 10px;
margin: 10px;
border: 1px solid #000;
width: 200px;
}
.multi-input {
margin: 10px;
padding: 5px;
border: 1px solid #666;
}
.multi-input>div {
height: 30px;
}
Mail.js:
import React from 'react';
import './mail.css';
import MultiInput from './MultiInput.js';
/**
* 好友列表组件(父组件):
* 点击好友列表某个好友时,以prop传入数据给收件人列表组件,并和收件人列表组件中原有邮箱地址进行累加显示
*/
class Mail extends React.Component{
constructor(props){
super(props);
//在constructor()方法中设置初始化数据
this.state = {
friends: [
{id: 1, name: '张三', email: 'zhangsan@email.com'},
{id: 2, name: '李四', email: 'lisi@email.com'},
{id: 3, name: '王五', email: 'wangwu@email.com'}
],
user:null
};
this.selectFriend = this.selectFriend.bind(this);
}
//此处直接将数据设置到user中,而prop数据更改会直接重新渲染自身及子组件,同时渲染时会调用static getDerivedStateFromProps()
//所以可以在子组件中通过此方法的props参数将更改后的user数据传递过去(一定要通过组件将user传递过去)
selectFriend(friend){
//将点击的数据设置到user中
this.setState({
user:friend
});
}
render(){
//获取好友列表数据
let {friends,user} = this.state;
return (
<div className="clear">
<h1>发送邮件</h1>
<hr/>
<ul className="box fr">
{/* 好友列表 */}
{
/*el => this.selectFriend(friends) 由于此处需要传参friends,而直接使用this.selectFriend(friends)会直接执行
所以使用箭头函数方式,点击时会调用箭头函数,箭头函数执行时才会执行this.selectFriend(friends)方法并传参
*/
Object.keys(friends).map(item=> <li onClick={el => this.selectFriend(friends[item])} key={friends[item].id}>{friends[item].name}</li>)
}
</ul>
<div className="fl">
{/* 收件人列表组件(子组件):收件人输入框 一定要通过组件将user传递过去,子组件中方法getDerivedStateFromProps需要用到*/}
<MultiInput user={user}/>
</div>
</div>
);
}
}
export default Mail;
MultiInput.js:
import React from 'react';
import './mail.css';
/**
* 收件人列表组件(子组件)——DOM操作方式清空input框:
* 在输入框输入邮箱地址并按enter键时,将地址显示在上方
*/
class MultiInput extends React.Component {
constructor(props) {
super(props);
this.state = {
users: []
}
this.keyDown = this.keyDown.bind(this);
}
//添加好友方法(收入邮箱地址后添加到输入框上方)
keyDown({ keyCode, target, target: { value } }) {
//判断当按下enter键时执行添加操作,keyCode从事件对象Event中解构出来
if (13 === keyCode) {
this.setState({
users: [...this.state.users, {
name: '',
email: value
}]
});
//操作DOM方式清空输入框
target.value = '';
}
}
//注意静态方法只能调用静态方法,所以此方法也必须是静态方法
static addNewUser(user,users){
//判断email不相等时进行添加
if ( !users.find(u => u.email === user.email) ) {
users.push(user);
}
// users.push(user);
return users;
}
//将父级props数据和私有state数据更新都会影响的数据用此方法进行处理
//注意静态方法中没有this,所以不能直接使用this.state改变users
static getDerivedStateFromProps(props,state){
if(props.user){
//将父组件传递的好友和输入的邮箱地址进行累加(注意静态方法中没有this),静态方法调用时必须使用类名进行调用
MultiInput.addNewUser(props.user,state.users);
}
//此方法返回值即更新state,返回null不进行更新
return null;
}
render() {
let { users } = this.state;
return (
<div className="multi-input">
{
Object.keys(users).map(item => (
<div key={item}>{users[item].name} {users[item].email};</div>
))
}
<div>
<input type="text" onKeyDown={this.keyDown} />
</div>
</div>
);
}
}
export default MultiInput;
效果:
2.4componentDidMount()
componentDidMount()
在组件挂载后(render 的内容插入 DOM 树中)调用。通常在这个阶段,我们可以:
- 操作 DOM 节点
- 发送请求
3.更新阶段
更新阶段是指组件重新渲染的过程,组件 state 的更新(调用 setState())和父组件渲染都会触发
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
3.1static getDerivedStateFromProps()
同挂载阶段,更新阶段也会触发该生命周期函数
3.2shouldComponentUpdate()
shouldComponentUpdate(nextProps, nextState)
发生在更新阶段,getDerivedStateFromProps 之后,render 之前,该函数会返回一个布尔值,决定了后续是否执行 render,首次渲染不会调用该函数。(应该于更新了某个东西,但不需要重新渲染时)。
如下:Child组件初始化时接收父组件传过来的value值,但是不希望后续更新也受父级组件此数据的影响(判断this.state.value!==nextState.value时即如果是私有数据state变化时返回true会重新渲染,如果是props传入数据会返回false就不会重新渲染),自己内部会有自己的value逻辑控制。
import React from 'react';
import Child from './Child';
export default class ShouldComponentUpdateComponent extends React.Component {
constructor(...args) {
super(...args);
this.state = {
n: 1,
}
}
render() {
return(
<div>
<h2 onClick={e=> {
this.setState({n: this.state.n + 1})
}}>n: {this.state.n}</h2>
<Child value={this.state.n} />
</div>
)
}
}
import React from 'react';
export default class Child extends React.Component {
constructor(...props) {
super(...props);
this.state = {
value: this.props.value
};
}
//Child组件初始化时接收父组件传过来的value值,但是不希望后续更新也收父级组件此数据的影响,自己内部会有自己的value逻辑控制
shouldComponentUpdate(nextProps, nextState) {
return this.state.value !== nextState.value;
}
render() {
console.log('render');
return(
<div>
value: {this.state.value}
<button onClick={e=>{
this.setState({
value: this.state.value + 1
})
}}>+</button>
</div>
);
}
}
此方法仅作为性能优化的方式而存在,不要企图依靠此方法来“阻止”渲染,因为可能会产生一些问题。其次,在 React.js 中本来对渲染已经做了必要的优化了,所以通过该函数本质上不能带来特别大的明显提升,且容易增加组件的复杂性,变得难以维护,除非确定使用它能为当前组件带来显著的性能提升
官方后期也会更改该方法的特性,即使返回 false 仍可能会重新渲染组件,不推荐滥用该函数。
3.3render()
同上
3.4getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate(prevProps, prevState)
该方法在 render() 之后,但是在输出到 DOM 之前执行,用来获取渲染之前的快照。当我们想在当前一次更新前获取上次的 DOM 状态,可以在这里进行处理,该函数的返回值将作为参数传递给下个生命周期函数 componentDidUpdate。该函数并不常用。
3.5componentDidUpdate()
componentDidUpdate(prevProps, prevState, snapshot)
该函数会在 DOM 更新后立即调用,首次渲染不会调用该方法。我们可以在这个函数中对渲染后的 DOM 进行操作
4.卸载阶段
当组件从 DOM 中移除时会调用如下方法
-
componentWillUnmount()
4.1componentWillUnmount()
componentWillUnmount()
该方法会在组件卸载及销毁前调用,我们可以在这里做一些清理工作,如:组件内的定时器、未完成的请求(这些不会随组件销毁而清理)等。
5.错误处理
当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法
- static getDerivedStateFromError()
- componentDidCatch()
5.1static getDerivedStateFromError()
static getDerivedStateFromError(error)
该方法用来获取子组件抛出的错误,返回值是一个对象,该对象被存储在 state 中,在后续的 render 方法中就可以根据这个对象的值来进行处理,超出错误边界的错误无法捕获。如:显示不同的 UI。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <div>出错了</div>;
}
return this.props.children;
}
}
案例:父级组件包含子级组件,子级组件出错时,只在对应子级组件处显示错误页面,父级原有组件不受影响。
错误边界组件:ErrorBoundry.js
import React from 'react';
class ErrorBoundry extends React.Component{
constructor(props){
super(props);
//static getDerivedStateFromError()方法返回值为一个对象存放在state中,所以可以在此设置错误标识
this.state = {
errorMsg : false
}
}
static getDerivedStateFromError(error){
//当错误边界组件包含的组件出错时会调用此方法,可将错误标识为true
return { errorMsg:true };
}
render(){
//通过判断,如果错误弹出错误页面,否则正常显示
if (this.state.errorMsg) {
return <div>某个组件出错了</div>;
}
return this.props.children;
}
}
export default ErrorBoundry;
MyParent.js:
import React from 'react';
import MyChild from './MyChild';
import ErrorBoundry from './ErrorBoundry';
class MyParent extends React.Component{
render(){
return (
<div>
<h1>这是父级MyParent</h1>
<ErrorBoundry>
<MyChild />
</ErrorBoundry>
</div>
);
}
}
export default MyParent;
MyChild.js:
import React from 'react';
class MyChild extends React.Component{
render(){
let obj = {
x:12
}
return(
<div>
<h2>这是子级MyChild</h2>
{/* 对象不能直接输出所以会出错 */}
{/* {obj} */}
</div>
);
}
}
export default MyChild;
效果:
未出错:
出错:
5.2componentDidCatch()
componentDidCatch(error, info)
该方法与 getDerivedStateFromError() 类似,但是也有不同的地方:
-
该方法会有一个记录详细错误堆栈信息的 info 参数
-
该方法可以执行一些额外的操作:打印错误、上报错误信息……