首页 > 解决方案 > 反应中的“半控制”组件

问题描述

我有通过道具获取起始值的子组件。在组件中,这个起始值被复制到本地状态,并且对它的所有修改都在组件本身中处理。

父组件通过事件接收修改值的结果。

现在可以在父组件中修改该值,并且此更改应反映在子组件中。

所以,子组件不是不受控制的,也不是完全受控制的。你可以说是半控制的。

这是一个基本示例:

class ParentComponent extends React.Component {
  state = {
    value: 5
  };

  increment = () => {
    this.setState({ value: this.state.value + 1 });
  };

  someOtherAction = () => {
    this.forceUpdate();
  };

  render() {
    return (
      <div>
        <button onClick={this.increment}>Parent modify</button>
        <button onClick={this.someOtherAction}>Force rerender</button>
        <br />
        <br />
        <ChildComponent value={this.state.value} onDone={val => alert(val)} />
      </div>
    );
  }
}

class ChildComponent extends React.Component {
  state = {
    value: this.props.value
  };

  static getDerivedStateFromProps(props, state) {
    return { value: props.value };
  }

  increment = () => {
    this.setState({ value: this.state.value + 1 });
  };

  render() {
    return (
      <div>
        <b>Child component</b> <br />
        value: {this.state.value} <br />
        <button onClick={this.increment}>Child modify</button>
        <button onClick={() => this.props.onDone(this.state.value)}>
          End modification
        </button>
      </div>
    );
  }
}

链接到工作示例

在示例中,一切正常,直到父组件由于与字段无关的操作而state.value更新(通过强制更新模拟),因为子值与父值不同步,因此在更新后孩子的价值被父母的价值覆盖。

可能的解决方案:

  1. 为子组件添加一个“parentValue”状态,该状态始终设置为父组件的值。然后我可以检查getDerivedStateFromProps新的 props 值是否与当前的 parentValue 不同,只有在这种情况下才更新值:

    static getDerivedStateFromProps(props, state) {
       return (props.value !== state.parentValue) ? { value: props.value, parentValue: props.value } : null;
    }
    

    问题:如果在子组件中修改了值,并且我想在父组件中将其重置为旧的初始值,props.value 将等于 state.parentValue,因此不会发生更新。

  2. 添加一个initialValue prop,用于初始化子组件上的值,如果props包含value字段,则只更新子组件的值。

您还有其他想法,如何处理?谢谢你的帮助!

标签: javascriptreactjs

解决方案


我认为您的问题在这篇官方反应博客文章中得到了很好的描述。它通常发生在父母通过传递道具“重置”子状态时。

getDerivedStateFromProps()确保在没有任何进一步检查的情况下使用时永远不要用道具覆盖状态。无条件地将 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) {
    // This will erase any local state updates!
    // Do not do this.
    this.setState({ email: nextProps.email });
  }
}

有关避免这种情况的方法,请参阅博客文章中的首选解决方案。

解决方案

  • 最简单的解决方案是通过从组件中完全移除状态或将其提升到父组件来使组件完全受控。

  • 另一种解决方案是使其完全不受控制并赋予其唯一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}
    />
    

    如果您现在希望您的组件“重置”,只需将新的初始值与不同的键一起传递。React 将卸载旧组件并用新组件替换它。


推荐阅读