React 学习笔记:你可能不需要使用派生 state

592 阅读2分钟

本文是你可能不需要使用派生 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

参考文章

1,你可能不需要使用派生 state