一、派生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 调用实例办法。