大部分使用派生 state 致使的问题,不外乎两个缘由:1,直接复制 props 到 state 上;2,若是 props 和 state 不一致就更新 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.email
来更新组件,这样能防止修改 state 致使的 bugthis
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 值没有变化!google
幸运的是,有两个方案能解决这些问题。这二者的关键在于,任何数据,都要保证只有一个数据来源,并且避免直接复制它。咱们来看看这两个方案。设计
咱们都知道React表单中有受控组件,那么什么是彻底可控组件呢,看下列示例你就明白了code
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彻底受父组件数据控制就像提线木偶同样component
让子组件本身存储临时的state数据,子组件仍然能够从 prop 接收“初始值”,可是更改以后的值就和 prop 不要紧了orm
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 的话,能够选择一下几种方式:对象
key
属性props.userID
)。