本文是你可能不需要使用派生 state博文的总结整理。
使用派生 state 存在的问题
1, 直接使用 props 更新 state
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) {
// 这会覆盖所有组件内的 state 更新!
// 不要这样做。
this.setState({ email: nextProps.email });
}
}
一方面,由于 componentWillReceiveProps 不仅会在 props 变化时调用,还会在父组件重新渲染时更新。 所以直接根据 props 更新 state 是不安全的,会导致 state 更新失效。
另一方面,这样不比较 props 是否变化就更新 state, 会导致任何 props 变化都更新 email。
2, 比较 props 变化后,更新 state
class EmailInput extends Component {
state = {
email: this.props.email
};
componentWillReceiveProps(nextProps) {
// 只要 props.email 改变,就改变 state
if (nextProps.email !== this.props.email) {
this.setState({
email: nextProps.email
});
}
}
// ...
}
这样单纯比较 props 属性变化,会存在 props 属性没有变化,但是 props 有变化,却没有触发 state 更新的问题。 一个重要原因是组件使用 props 改变 state, 又在组件内部改变 state,数据源不同。 所以核心是单一数据源的问题,避免直接复制它。
保持单一数据源
1,使用受控组件, 传入数据和回调函数
function EmailInput(props) {
return <input onChange={props.onChange} value={props.email} />;
}
属性完全由外部传入。
这是第一种错误更新 state 的解决方案。
2,有 key 的非受控组件, 传入初始值,添加 key
组件写法
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} />;
}
}
使用组件写法
<EmailInput
defaultEmail={this.props.user.email}
key={this.props.user.id}
/>
非受控组件,state 初始值有外部传入,随后改变不受外部控制。使用 key 保证组件的唯一性,每次 key 改变时,重新创建组件。
这是第二种错误更新 state 的解决方案
3,两种不常用的更新非受控组件的方法
1,对于更新 key, 不起作用时
class EmailInput extends Component {
state = {
email: this.props.defaultEmail,
prevPropsUserID: this.props.userID
};
static getDerivedStateFromProps(props, state) {
// 只要当前 user 变化,
// 重置所有跟 user 相关的状态。
// 这个例子中,只有 email 和 user 相关。
if (props.userID !== state.prevPropsUserID) {
return {
prevPropsUserID: props.userID,
email: props.defaultEmail
};
}
return null;
}
// ...
}
使用 getDerivedStateFromProps 比较 userID,更新关于user 的所有 state
2,对于没有合适的 key 时
随机或递增生成一个key, 或者父组件通过 ref 调用子组件的重置方法
class EmailInput extends Component {
state = {
email: this.props.defaultEmail
};
resetEmailForNewUser(newEmail) {
this.setState({ email: newEmail });
}
// ...
}
总结
当在设计通用组件时,使用两种方式,保持单一数据源,建立 props 到 state 的派生关系。
1,组件设计成受控组件,完全使用 props 代替 state。
2,组件设计成非受控组件,使用 props 作为 state 的初始值,使用 key 更新组件。
非受控组件还有两种不常用的建立 props 到 state 的派生关系。
1,使用 getDerivedStateFromProps, 比较 key 更新 state
2,父组件使用 ref, 调用组件方法更新 state