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 调用实例方法。
原文链接:segmentfault.com

上一篇:LiveNode.js 解决前端跨域问题
下一篇:javascript算法与数据结构-栈

相关推荐

  • 🍊仿Element自定义Vue组件库

    (/public/upload/643b972fb2ebd2e6272ff8b16712b205) 前言 🍊 市面上目前已有各种各样的UI组件库,他们的强大毋庸置疑。

    23 天前
  • 🆘 一次理解清楚,为什么使用 React useEffect 中使用 setInterval 获取的值不是最新的

    Intro 这篇文章将通过一个使用 React Hook 常遇到的问题(stale state)入手,尝试理解 Hook 的内部运行逻辑。 废话不多说,直接看示例Sandbox。

    1 个月前
  • (vue框架)为element组件赋初始值以后无法更改值得问题

    情况描述:组件未加载时已有初始值,mounted里面加载数据,赋值,渲染以后,组件无法更改内容 data里面已经有这个表单对象的初始值但还是无法修改,之前有过一次,没有给表单绑定对象,所以赋值以后无法...

    1 年前
  • 魔方实时通信一对一音视频组件

    魔方实时通信/协作引擎(Web SDK)是一个全能力的实时云端协作引擎 魔方实时通信,请点击这个(https://www.shixincube.com/) 继上一个im聊天组件增加了发动语音,语音...

    2 年前
  • 高频数据交换下Flutter与ReactNative的对比

    (标题图片来自网络,侵删) 后端使用go写的socketio服务模拟期货行情数据,每10ms推送10条行情数据 ReactNative已经尽力优化了。 Flutter由于没fluttersock...

    2 年前
  • 高阶组件之属性代理

    新组件类继承子React.component类,对传入的组件进行一系列操作,从而产生一个新的组件,达到增强组件的作用 操作props 访问ref 抽取state 封装组件 ...

    1 年前
  • 高阶组件HOC - 小试牛刀

    1. 前言 老毕曾经有过一句名言,叫作“国庆七天乐,Coding最快乐~”。所以在这漫漫七天长假,手痒了怎么办?于是乎,就有了接下来的内容。。。 2. 一个中心 今天要分享的内容有关高阶组件...

    2 年前
  • 高阶组件 + New Context API = ?

    1. 前言 继上次小试牛刀(https://github.com/SmallStoneSK/Blog/issues/6)尝到高价组件的甜头之后,现已深陷其中无法自拔。。。

    2 年前
  • 高阶函数&amp;&amp;高阶组件

    高阶函数 特点: 接受函数类型的参数。 返回值是函数。 高阶函数具有可扩展性。 常见的高阶函数: 定时器 setTimeout(); setInterval() Promise(); 数组相关:...

    5 个月前
  • 高级 Angular 组件模式 (3b)

    03b Enhance Components with Directives 原文: Enhance Components with Directives(https://blog.angula...

    3 年前

官方社区

扫码加入 JavaScript 社区