反应中的“半控制”组件

时间:2018-06-29 08:23:34

标签: javascript reactjs

我有一个子组件,可以通过props获取初始值。在该组件中,该起始值将复制到本地状态,并且对它的所有修改都将在组件本身中进行处理。

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

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

因此,子组件并非不受控制,但也不受完全控制。你可以说是半控制的。

这是一个基本示例:

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>
    );
  }
}

Link to working example

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

可能的解决方案:

  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包含值字段时更新子组件的值。

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

3 个答案:

答案 0 :(得分:1)

我认为您的问题已在this official react blog post中得到了很好的描述。通常在父项通过传递道具“重置”子状态时发生。

在使用getDerivedStateFromProps()时,请确保不要在没有任何进一步检查的情况下用道具覆盖状态。无条件复制道具到状态被认为是反模式:

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 });
  }
}

有关避免这种情况的方法,请参阅博客文章中的Preferred Solutions

解决方案

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

  • 另一种解决方案是使其完全不受控制,并为其赋予唯一的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将卸载旧组件,并用新组件替换它。

答案 1 :(得分:1)

如果您想从父级更新状态, 添加timestamp状态,并在每次状态更新时对其进行更新。

increment = () => {
  this.setState({ value: this.state.value + 1,timestamp : (new Date()).getTime() });
};

内部渲染:

<ChildComponent value={this.state.value} timestamp = {this.state.timestamp} onDone={val => alert(val)} />

现在,在子组件中,

state = {
  value: this.props.value,
  timestamp : this.prop.timestamp // store the passd value from prop
};

每次从父级进行重新渲染时,只有timestamp绝对更新 是从increment方法(而不是其他方法)更新时。因此,您可以通过正确的操作而不是forceUpdate来检查父级触发了更新。

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

答案 2 :(得分:0)

我猜想如果不实现一些反模式,就没有一个很好的解决方案。我要做的是使ChildComponent不受控制,并实现使ChildComponent受到完全控制的包装器。因此,根据我的需要,我可以直接使用ChildComponent或使用包装器。

感谢您的所有帮助!