getDerivedStateFromProps
的意義僅在使組件依其 props 的變化來改變其內部 state。Derived state 不應頻繁使用,須遵循以下原則:
對於組件有以下二類:
Derived state 的最常見錯誤是混淆此兩者。若一 derived state 值也能被 setState
所更新,該數據就不止有一個 source of truth。
一個常見誤解是 getDerivedStateFromProps
和 componentWillReceiveProps
祇在 props 變更時調用。事實是每當父組件重渲染時,這些 lifecycles 都會觸發,而不論 props 是否有變。因此,在這些 lifecycles 中無條件地覆蓋 state 並不安全,會造成 state 更新丟失。
一個將 prop 映射至 state 的 EmailInput
組件:
class EmailInput extends Component {
state = { email: this.props.email };
render() {
return <input onChange={this.handleChange} value={this.state.email} />;
}
handleChange = event => {
this.setState({ email: event.target.value });
};
componentWillReceiveProps(nextProps) {
// 這會擦除任何本地狀態更新!
// 別這麼做。
this.setState({ email: nextProps.email });
}
}
每當其父組件重渲染時,往 <input>
裏鍵入的內容就會丟失(見例)。
加入 shouldComponentUpdate
來讓組件祇在 email
prop 變更時才重渲染,可以解決這個問題。但實際情況中,組件經常接收多個 props;另外的 prop 變更也會引致重渲染。shouldComponentUpdate
應祇用於優化性能,而非用以糾正 derived state。
componentWillReceiveProps(nextProps) {
// 每當 `props.email` 變化時,即更新狀態。
if (nextProps.email !== this.props.email) {
this.setState({
email: nextProps.email
});
}
}
現在組件祇會在 props 真正變更時,才抹除鍵入的內容。但在一些邊緣需求的用例下,會引致 bugs(見例)。
從組件完全移除 state——若 email 地址祇以 prop 形式存在,則不必擔心與 state 衝突。甚至可以把 EmailInput
轉換成一個輕量的函數組件:
function EmailInput(props) {
return <input onChange={props.onChange} value={props.email} />;
}
讓組件完全擁有「draft」email 地址,但仍可接收一個 prop 作為初始值,唯忽略其後續變更。
class EmailInput extends Component {
state = { email: this.props.defaultEmail };
handleChange = event => {
this.setState({ email: event.target.value });
};
render() {
return <input onChange={this.handleChange} value={this.state.email} />;
}
}
(就上例的密碼管理器而言)為其加入一個 key
屬性。當 key
變化時,React 會新建一個組件實例,而非更新當前的組件實例。
<EmailInput
defaultEmail={this.props.user.email}
key={this.props.user.id}
/>
每當 ID 有變,EmailInput
會被重建且其 state 被重置(見例)。你祇須為整個 form 配置一個 key
,每當 key 變化時,form 內的所有組件都會被重建並重置 state。
在多數情況下,這是處理需要重置的 state 的最好方式。
如果出於某些原因 key
沒有用(可能組件的初始化非常昂貴),一個可能但笨拙的方法是在 getDerivedStateFromProps
中監測 userID
的變動:
class EmailInput extends Component {
state = {
email: this.props.defaultEmail,
prevPropsUserID: this.props.userID
};
static getDerivedStateFromProps(props, state) {
// Any time the current user changes,
// Reset any parts of state that are tied to that user.
// In this simple example, that's just the email.
if (props.userID !== state.prevPropsUserID) {
return {
email: props.defaultEmail,
prevPropsUserID: props.userID
};
}
return null;
}
// ...
}
完 :)