因為最近在做一個邏輯較為復雜的需求,在封裝組件時經常遇到父組件props更新來觸發子組件的state這種情景。在使用componentWillReceiveProps時,發現React官網已經把componentWillReceiveProps重名為UNSAFE_componentWillReceiveProps,但是我發現了getDerivedStateFromProps可以替代,卻又被一篇博客告知這個也盡量別使用。因為組件一旦使用派生狀態,很有可能因為沒有明確的數據來源導致出現一些bug和不一致性。既然提倡避免使用,肯定也會有相應的解決方案。
本文會介紹以上兩種生命周期的使用方法、誤區和替代升級方案。
componentWillReceiveProps
1.介紹
componentWillReceiveProps是React生命周期函數之一,在初始props不會被調用,它會在組件接受到新的props時調用。一般用於父組件更新狀態時子組件的重新渲染。在react16.3之前,componentWillReceiveProps是在不進行額外render的前提下,響應props中的改變並更新state的唯一方式。
2.使用方法
componentWillReceiveProps(nextProps) { //通過this.props來獲取舊的外部狀態,初始 props 不會被調用 //通過對比新舊狀態,來判斷是否執行如this.setState及其他方法 }
主要在以下兩種情景使用:
- 從上傳的props無條件的更新state
- 當props和state不匹配時候更新state
3.常見誤區
無條件的更新state
class EmailInput extends Component { state = { email: this.props.email }; componentWillReceiveProps(nextProps) { // 這樣會覆蓋內部 email的更新! this.setState({ email: nextProps.email }); } handleChange = event => { this.setState({ email: event.target.value }); }; render() { return <input onChange={this.handleChange} value={this.state.email} />; } }
從上述例子可以發現子組件的更新會被父組件的更新覆蓋。並且大家在使用過程沒有必要這樣無條件更新,完全可以寫成一個完全受控組件。
<input onChange={this.props.onChange} value={this.props.email} />
也可以對比新舊props狀態:
componentWillReceiveProps(nextProps) { if (nextProps.email !== this.props.email) { this.setState({ email: nextProps.email }); } }
現在該組件只會在props改變時候覆蓋之前的修改了,但是仍然存在一個小問題。例如一個密碼管理網站使用了如上的輸入組件。當切換兩個不同的賬號的時候,如果這兩個賬號的郵箱相同,那么我們的重置就會失效。因為對於這兩個賬戶傳入的email屬性是一樣的,即數據源相同。效果如下:

// 父組件: import React, { Component, Fragment } from 'react'; import { Radio } from 'antd'; import UncontrolledInput from './UncontrolledInput'; const accounts = [ { id: 1, name: 'One', email: 'same.email@example.com', }, { id: 2, name: 'Two', email: 'same.email@example.com', }, { id: 3, name: 'Three', email: 'other.fake.email@example.com', } ]; export default class AccountList extends Component { state = { selectedIndex: 0 }; render() { const { selectedIndex } = this.state; return ( <Fragment> <UncontrolledInput email={accounts[selectedIndex].email} /> <Radio.Group onChange={(e) => this.setState({ selectedIndex: e.target.value })} value={selectedIndex}> {accounts.map((account, index) => ( <Radio value={index}> {account.name} </Radio> ))} </Radio.Group> </Fragment> ); } } //子組件 import React, { Component } from 'react'; import { Input } from 'antd'; export default class UncontrolledInput extends Component { state = { email: this.props.email }; componentWillReceiveProps(nextProps) { if (nextProps.email !== this.props.email) { this.setState({ email: nextProps.email }); } } handleChange = event