乐趣区

关于javascript:关于React组件中派生state的思考

一、什么是派生 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

退出移动版