乐趣区

关于前端:react组件完全可控组件非可控的组件与派生state

一、派生 state 常见应用问题

大部分应用派生 state 导致的问题,不外乎两个起因:1,间接复制 props 到 state 上;2,如果 props 和 state 不统一就更新 state

间接复制 props 到 state

最常见的误会就是 getDerivedStateFromPropscomponentWillReceiveProps 只会在 props“扭转”时才会调用。实际上只有父级从新渲染时,这两个生命周期函数就会从新调用,不论 props 有没有“变动”。所以,在这两个办法内间接复制(_unconditionally_)props 到 state 是不平安的。这样做会导致 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) {
    // Do not do this!
    if (nextProps.email !== this.state.email) {this.setState({ email: nextProps.email});
    }
  }
}

class Timer extends Component {
  state = {count: 0};

  componentDidMount() {
    this.interval = setInterval(() =>
        this.setState(prevState => ({count: prevState.count + 1})),
      1000
    );
  }

  componentWillUnmount() {clearInterval(this.interval);
  }

  render() {
    return (
      <Fragment>
        <blockquote> 请输出邮箱:</blockquote>
        <EmailInput email="example@google.com" />
        <p>
          此组件每秒会从新渲染一次
        </p>
      </Fragment>
    );
  }
}
render(<Timer />, document.getElementById("root"));

state 的初始值是 props 传来的,当在 <input> 里输出时,批改 state。然而如果父组件从新渲染,咱们输出的所有货色都会失落,即便在重置 state 前比拟 nextProps.email !== this.state.email 依然会导致更新。
这个小例子中,应用 shouldComponentUpdate,比拟 props 的 email 是不是批改再决定要不要从新渲染。然而在实践中,一个组件会接管多个 prop,任何一个 prop 的扭转都会导致从新渲染和不正确的状态重置。加上行内函数和对象 prop,创立一个齐全牢靠的 shouldComponentUpdate 会变得越来越难。而且 shouldComponentUpdate 的最佳实际是用于性能晋升,而不是改过不适合的派生 state

在 props 变动后批改 state

持续下面的示例,咱们能够只应用 props.email 来更新组件,这样能避免批改 state 导致的 bug

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) {
    // Do not do this!
    if (nextProps.email !== this.props.email) {this.setState({ email: nextProps.email});
    }
  }
}

当初组件只会在 prop 扭转时才会扭转,然而依然有个问题。设想一下,如果这是一个明码输出组件,领有同样 email 的两个账户进行切换时,这个输入框不会重置(用来让用户从新登录)。因为父组件传来的 prop 值没有变动!

侥幸的是,有两个计划能解决这些问题。这两者的关键在于,任何数据,都要保障只有一个数据起源,而且防止间接复制它。咱们来看看这两个计划。

一、齐全可控组件

咱们都晓得 React 表单中有受控组件,那么什么是齐全可控组件呢,看下列示例你就明确了

function ControlledEmailInput(props) {
  return (
    <label>
      Email: <input value={props.email} onChange={props.handleChange} />
    </label>
  );
}

class App extends Component {
  state = {draftEmail: 'some.email@test.com'};

  handleEmailChange = event => {this.setState({ draftEmail: event.target.value});
  };

  resetForm = () => {
    this.setState({draftEmail: 'some.email@test.com'});
  };

  render() {
    return (
      <Fragment>
        <h1> 此示例展现了什么是齐全可控组件 </h1>
        <ControlledEmailInput
          email={this.state.draftEmail}
          handleChange={this.handleEmailChange}
        />
        <button onClick={this.resetForm}> 重置 </button>
      </Fragment>
    );
  }
}
render(<App />, document.getElementById("root"));

从上示例咱们就晓得了,这不正是咱们所见过的组件间通信嘛,子组件中的 email 齐全受父组件数据管制就像提线木偶一样

二、有 key 的齐全不受控组件

让子组件本人存储长期的 state 数据,子组件依然能够从 prop 接管“初始值”,然而更改之后的值就和 prop 没关系了

class EmailInput extends Component {state = { email: this.props.defaultEmail};

  handleChange = event => {this.setState({ email: event.target.value});
  };
  
  resetForm = () => {this.setState({ email: 'some.email@test.com'});
  };

  render() {return <input onChange={this.handleChange} value={this.state.email} />;
  }
}

class App extends Component {inputRef = React.createRef();
  
  state = {draftEmail: 'some.email@test.com',};
  
  resetForm = () => {this.inputRef.current.resetForm()
  };

  render() {
    return (
      <Fragment>
        <h1> 此示例展现了什么是有 Key 的非可控组件 </h1>
        <EmailInput
          defaultEmail={this.state.draftEmail}
          ref={this.inputRef}
        />
        <button onClick={this.resetForm}> 重置 </button>
      </Fragment>
    );
  }
}
render(<App />, document.getElementById("root"));

总结

设计组件时,重要的是确定组件是受控组件还是非受控组件。
不要间接复制 props 的值到 state 中,而是去实现一个 受控 的组件,而后在父组件里合并两个值。
对于 不受控 的组件,当你想在 prop 变动时重置 state 的话,能够抉择一下几种形式:

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