一、什么是派生state

简略来说,如果一个组件的state中的某个数据来自内部,就将该数据称之为派生状态。

大部分应用派生state导致的问题,不外乎两个起因:

  • 间接复制props到state
  • 如果props和state不统一就更新state

二、开发我的项目过程中遇到的派生state的问题

在我开发治理后盾的需要的时候,因为借鉴了一下大象后盾的局部代码,所以索性也就浏览了一下大象后盾的代码。这在其中,我发现了一个派生状态的问题,引发了我的思考,上面是我的项目中的代码:

在这里,我发现了这样子的写法有三个不好的问题

  • 间接将props复制给了state,如果不采纳生命周期钩子的话,这里会产生一个问题:当props更新的时候state并没有更新;

看上面的这一段代码:

  • 父组件
class Huang extends React.Component {    constructor(props) {      super(props);      this.state = {        count: 0      }    }    componentDidMount() {      this.interval = setInterval(() => {        this.setState(preState => ({count: preState.count + 1}))      }, 1000)    }    componentWillUnmount() {      clearInterval(this.interval);    }    render() {      return (        <div>          <p>{'父组件的值:' + this.state.count}</p>          <Dong count={this.state.count}/>        </div>      )    }  }
  • 子组件
class Dong extends React.Component {    constructor(props) {      super(props);      this.state = {        count: this.props.state        //子组件的state是派生的      }    }    // componentWillReceiveProps(nextProps) {    //   if(this.state.count !== nextProps.count) {    //     this.setState({count: nextProps.count});    //   }    // }      render() {      return (        <div>          <p>{'子组件的值:' + this.state.count}</p>        </div>      )    }  }

很显著,输入的后果就是:父组件的state在一直变动,然而子组件的state不会随着父组件的state扭转而扭转,也就印证了下面的论断。

  • 显然,作者也是意识到了这个问题,所以他上面应用了componentWillReceiveProps()钩子函数,想着当props更新时触发这个函数,在该函数中手动的应用了setState()办法批改了state。这样看似没有什么问题,然而这又引起了另外的一个问题:如果我想以另外的形式去批改state呢?比如说在<input>里监听输出,批改state。然而如果父组件从新渲染,咱们输出的所有货色都会失落!对于这个问题能够看一下例子:无论在输入框中输出什么每一秒都会主动重制输入框的内容
  • 退一步,即便你只是在componentWillReceiveProps()会去应用setState()去批改子组件中state的值。是不是这样就没有问题了呢?然而,还是会有一个问题:试想,子组件的state的某个字段是齐全依赖于props的,也就是说只有props不更新,state就不会去更新,这样子组件就不会从新渲染我感觉官网举的这个例子很好:设想一下,如果这是一个明码输出组件,领有同样 email 的两个账户进行切换时,这个输入框不会重置(用来让用户从新登录)。因为父组件传来的 prop 值没有变动!这会让用户十分诧异,因为这看起来像是帮忙一个用户分享了另外一个用户的明码。这个是官网对于这个问题的一个例子:当切换选项时因为子组件的props没变,所以子组件不会被从新渲染

三、如何解决派生state的问题

在这里,我略微总结了几种办法,仅供大家参考。

1.罗唆就不必state,间接将组件变为齐全可控组件

阻止上述问题产生的一个办法是:从组件里删除state,而后将组件变为一个齐全受控的组件,这样就不会产生派生状态的影响。像上面这样:

function EmailInput(props) {  return <input onChange={props.onChange} value={props.email} />;}

2.有key的非可控组件

有很多时候会有这样的一种状况,组件的state外面不齐全是派生组件,组件也不齐全是受控组件,这时候如果咱们想用派生状态的话怎么办?

为每一个组件设置一个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}        //这个id必须是惟一的,当id发生变化的时候,会创立一个新的子组件/>

在这里要留神,不能应用this.props.key来读取key的值

3.用prop的ID重置非受控组件

如果某些状况下 key 不起作用(可能是组件初始化的开销太大),一个麻烦然而可行的计划是在 getDerivedStateFromProps 察看 userID 的变动:

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,用 componentWillReceiveProps 也一样。

4.应用实例办法重置非受控组件

更少见的状况是,即便没有适合的 key,咱们也想从新创立组件。一种解决方案是给一个随机值或者递增的值当作 key,另外一种是用实例办法强制重置外部状态

class EmailInput extends Component {  state = {    email: this.props.defaultEmail  };  resetEmailForNewUser(newEmail) {    this.setState({ email: newEmail });  }  // ...}

而后父组件能够应用ref调用这个办法

refs 在某些状况下很有用,比方这个。但通常咱们倡议审慎应用。即便是做一个演示,这个命令式的办法也是非现实的,因为这会导致两次而不是一次渲染。

四、总结

下面的派生状态引发的问题揭示咱们:设计组件时,重要的是确定组件是受控组件还是非受控组件。

不要间接复制(mirror) props 的值到 state 中,而是去实现一个受控的组件,而后在父组件里合并两个值。比方,不要在子组件里被动的承受 props.value 并跟踪一个长期的 state.value,而要在父组件里治理 state.draftValue 和 state.committedValue,间接管制子组件里的值。这样数据才更加明确可预测。

对于不受控的组件,当你想在 prop 变动(通常是 ID )时重置 state 的话,能够抉择一下几种形式:

  • 倡议: 重置外部所有的初始 state,应用 key 属性
  • 选项一:仅更改某些字段,察看非凡属性的变动(比方 props.userID)。
  • 选项二:应用 ref 调用实例办法。

这篇文章只是出于我集体遇到派生state引发的问题的一些思考,如果大家对这个问题比拟感兴趣,能够参考官网这篇文章:你可能不须要应用派生state