一、派生 state 常见应用问题
大部分应用派生 state 导致的问题,不外乎两个起因:1,间接复制 props 到 state 上;2,如果 props 和 state 不统一就更新 state
间接复制 props 到 state
最常见的误会就是 getDerivedStateFromProps
和 componentWillReceiveProps
只会在 props“扭转”时才会调用。实际上只有父级从新渲染时,这两个生命周期函数就会从新调用,不论 props 有没有“变动”。所以,在这两个办法内间接复制(_unconditionally_)props 到 state 是不平安的。这样做会导致 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) {
// Do not do this!
if (nextProps.email !== this.state.email) {this.setState({ email: nextProps.email});
}
}
}
class Timer extends Component {
state = {count: 0};
componentDidMount() {
this.interval = setInterval(() =>
this.setState(prevState => ({count: prevState.count + 1})),
1000
);
}
componentWillUnmount() {clearInterval(this.interval);
}
render() {
return (
<Fragment>
<blockquote> 请输出邮箱:</blockquote>
<EmailInput email="example@google.com" />
<p>
此组件每秒会从新渲染一次
</p>
</Fragment>
);
}
}
render(<Timer />, document.getElementById("root"));
state 的初始值是 props 传来的,当在 <input>
里输出时,批改 state。然而如果父组件从新渲染,咱们输出的所有货色都会失落,即便在重置 state 前比拟 nextProps.email !== this.state.email
依然会导致更新。
这个小例子中,应用 shouldComponentUpdate
,比拟 props 的 email 是不是批改再决定要不要从新渲染。然而在实践中,一个组件会接管多个 prop,任何一个 prop 的扭转都会导致从新渲染和不正确的状态重置。加上行内函数和对象 prop,创立一个齐全牢靠的 shouldComponentUpdate
会变得越来越难。而且 shouldComponentUpdate
的最佳实际是用于性能晋升,而不是改过不适合的派生 state
在 props 变动后批改 state
持续下面的示例,咱们能够只应用 props.email
来更新组件,这样能避免批改 state 导致的 bug
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) {
// Do not do this!
if (nextProps.email !== this.props.email) {this.setState({ email: nextProps.email});
}
}
}
当初组件只会在 prop 扭转时才会扭转,然而依然有个问题。设想一下,如果这是一个明码输出组件,领有同样 email 的两个账户进行切换时,这个输入框不会重置(用来让用户从新登录)。因为父组件传来的 prop 值没有变动!
侥幸的是,有两个计划能解决这些问题。这两者的关键在于,任何数据,都要保障只有一个数据起源,而且防止间接复制它。咱们来看看这两个计划。
一、齐全可控组件
咱们都晓得 React 表单中有受控组件,那么什么是齐全可控组件呢,看下列示例你就明确了
function ControlledEmailInput(props) {
return (
<label>
Email: <input value={props.email} onChange={props.handleChange} />
</label>
);
}
class App extends Component {
state = {draftEmail: 'some.email@test.com'};
handleEmailChange = event => {this.setState({ draftEmail: event.target.value});
};
resetForm = () => {
this.setState({draftEmail: 'some.email@test.com'});
};
render() {
return (
<Fragment>
<h1> 此示例展现了什么是齐全可控组件 </h1>
<ControlledEmailInput
email={this.state.draftEmail}
handleChange={this.handleEmailChange}
/>
<button onClick={this.resetForm}> 重置 </button>
</Fragment>
);
}
}
render(<App />, document.getElementById("root"));
从上示例咱们就晓得了,这不正是咱们所见过的组件间通信嘛,子组件中的 email 齐全受父组件数据管制就像提线木偶一样
二、有 key 的齐全不受控组件
让子组件本人存储长期的 state 数据,子组件依然能够从 prop 接管“初始值”,然而更改之后的值就和 prop 没关系了
class EmailInput extends Component {state = { email: this.props.defaultEmail};
handleChange = event => {this.setState({ email: event.target.value});
};
resetForm = () => {this.setState({ email: 'some.email@test.com'});
};
render() {return <input onChange={this.handleChange} value={this.state.email} />;
}
}
class App extends Component {inputRef = React.createRef();
state = {draftEmail: 'some.email@test.com',};
resetForm = () => {this.inputRef.current.resetForm()
};
render() {
return (
<Fragment>
<h1> 此示例展现了什么是有 Key 的非可控组件 </h1>
<EmailInput
defaultEmail={this.state.draftEmail}
ref={this.inputRef}
/>
<button onClick={this.resetForm}> 重置 </button>
</Fragment>
);
}
}
render(<App />, document.getElementById("root"));
总结
设计组件时,重要的是确定组件是受控组件还是非受控组件。
不要间接复制 props 的值到 state 中,而是去实现一个 受控 的组件,而后在父组件里合并两个值。
对于 不受控 的组件,当你想在 prop 变动时重置 state 的话,能够抉择一下几种形式:
- 倡议: 重置外部所有的初始 state,应用
key
属性 - 选项一:仅更改某些字段,察看非凡属性的变动(比方
props.userID
)。 - 选项二:应用 ref 调用实例办法。