ReactJS状态与属性


121

这可能会在负责任和有见识之间走出一条界限,但是我将反复讨论如何随着复杂性的增加和使用某些方向来构建ReactJS组件。

来自AngularJS,我想将模型作为属性传递给组件,并让组件直接修改模型。还是应该将模型拆分为各种state属性,并在向上游发送回时将其重新编译在一起?什么是ReactJS方式?

以博客文章编辑器为例。试图直接修改模型最终看起来像:

var PostEditor = React.createClass({
  updateText: function(e) {
    var text = e.target.value;
    this.props.post.text = text;
    this.forceUpdate();
  },
  render: function() {
    return (
      <input value={this.props.post.text} onChange={this.updateText}/>
      <button onClick={this.props.post.save}/>Save</button>
    );
  }
});

好像错了

是否更像React的方式来制作我们的text模型属性state,并在保存之前将其编译回模型中:

var PostEditor = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },
  componentWillMount: function() {
    this.setState({
      text: this.props.post.text
    });
  },
  updateText: function(e) {
    this.setState({
      text: e.target.value
    });
  },
  savePost: function() {
    this.props.post.text = this.state.text;
    this.props.post.save();
  },
  render: function() {
    return (
      <input value={this.state.text} onChange={this.updateText}/>
      <button onClick={this.savePost}/>Save</button>
    );
  }
});

不需要调用this.forceUpdate(),但是随着模型的增长,(帖子可能会有作者,主题,标签,评论,评分等),该组件开始变得非常复杂。

使用ReactLink的第一种方法是可行的吗?

Answers:


64

您的第二种方法更像是它。React并不关心模型,而是关心以及它们在应用程序中的流动方式。理想情况下,您的帖子模型应存储在根目录下的单个组件中。然后创建子组件,每个子组件都使用模型的各个部分。

您可以将回调传递给需要修改数据的子级,然后从子级组件中调用它们。

直接修改this.props或this.state并不是一个好主意,因为React无法接受更改。这是因为React对您的发布道具进行了浅层比较,以确定它是否已更改。

我做了这个jsfiddle来展示数据如何从外部组件流到内部组件。

handleClick方法显示了3种(不正确)更新状态的方法:

var Outer = React.createClass({

  getInitialState: function() {
    return {data: {value: 'at first, it works'}};
  },

  handleClick: function () {

    // 1. This doesn't work, render is not triggered.
    // Never set state directly because the updated values
    // can still be read, which can lead to unexpected behavior.

    this.state.data.value = 'but React will never know!';

    // 2. This works, because we use setState

    var newData = {value: 'it works 2'};
    this.setState({data: newData});

    // 3. Alternatively you can use React's immutability helpers
    // to update more complex models.
    // Read more: http://facebook.github.io/react/docs/update.html

    var newState = React.addons.update(this.state, {
      data: {value: {$set: 'it works'}}
    });
    this.setState(newState);
 },

  render: function() {
      return <Inner data={this.state.data} handleClick={this.handleClick} />;
  }
});

但是,如果我们有一个具有自己的状态操纵函数的不透明模型,该怎么办?例如,假设text我们有一个setText 方法而不是字段,它可以进行验证和其他一些工作。我可以看到方法(2)setText是纯函数,并且返回模型的全新实例。但是,如果setText仅更新内部状态,我们仍然需要调用forceUpdate,对吗?
hugomg 2014年

1
是的,您可以打电话给forceUpdate,但是那时候您正在“退出” React。最好将setState()回调传递给不透明模型,以避免必须手动触发重新渲染。
jxg 2014年

我仍然不确定我是否完全理解。那么,任何旨在修改数据的组件都需要对传入的道具进行深度复制?然后修改并向上游发送该副本,以免修改原始数据?最终,更改将一直流到根(在其中进行处理),然后重新渲染整个应用程序?那正确吗?
尼古拉斯

97

2016年更新: React已更改,“道具与状态”的解释变得非常简单。如果组件需要更改数据,则将其置于一种状态,否则置于道具中。因为道具现在是只读的

道具和状态之间的确切区别是什么?

你可以在这里找到很好的解释(完整版)

改变道具和状态


1
setProps()实际上可以更改组件内部的道具并触发重新渲染
WaiKit Kung 2015年

2
setProps已弃用,不应使用。替换是重新渲染组件,让React处理差异。
jdmichal '16

如果您正在寻找解说视频:youtube.com/watch?
v=qh3dYM6Keuw

35

从React文档

道具是不可变的:它们从父级传递过来,并由父级“拥有”。为了实现交互,我们向组件引入了可变状态。this.state对组件是私有的,可以通过调用this.setState()进行更改。更新状态后,组件将重新渲染自身。

TrySpace:当更新道具(或状态)(通过setProps / setState或父对象)时,组件也会重新渲染。


16

读《React中的思考》

让我们遍历每个,找出哪个是状态。只需对每个数据问三个问题:

  1. 它是通过道具从父母那里传入的吗?如果是这样,则可能不是状态。
  2. 它会随着时间变化吗?如果不是,则可能不是状态。

  3. 您可以根据组件中的任何其他状态或道具来计算它吗?如果是这样,则不是状态。


13

我不确定是否要回答您的问题,但是我发现,尤其是在大型/成长型应用程序中,“容器/组件”模式的工作异常出色。

本质上,您有两个React组件:

  • 一个“纯”显示组件,用于处理样式和DOM交互;
  • 容器组件,用于处理访问/保存外部数据,管理状态以及渲染显示组件。

注意:这个例子可能太简单了,无法说明这种模式的好处,因为对于这种简单的情况来说,它很冗长。

/**
 * Container Component
 *
 *  - Manages component state
 *  - Does plumbing of data fetching/saving
 */

var PostEditorContainer = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },

  componentWillMount: function() {
    this.setState({
      text: getPostText()
    });
  },

  updateText: function(text) {
    this.setState({
      text: text
    });
  },

  savePost: function() {
    savePostText(this.state.text);
  },

  render: function() {
    return (
      <PostEditor
        text={this.state.text}
        onChange={this.updateText.bind(this)}
        onSave={this.savePost.bind(this)}
      />
    );
  }
});


/**
 * Pure Display Component
 *
 *  - Calculates styling based on passed properties
 *  - Often just a render method
 *  - Uses methods passed in from container to announce changes
 */

var PostEditor = React.createClass({
  render: function() {
    return (
      <div>
        <input type="text" value={this.props.text} onChange={this.props.onChange} />
        <button type="button" onClick={this.props.onSave} />
      </div>
    );
  }
});

好处

通过将显示逻辑和数据/状态管理分开,您将拥有一个可重复使用的显示组件,该组件:

  • 可以使用诸如react-component-playground之类的不同道具轻松地迭代
  • 可以用不同的容器包装以实现不同的行为(或与其他组件组合以构建应用程序的较大部分

您还具有处理所有外部通讯的容器组件。如果以后进行任何重大更改,这将使您更灵活地灵活地访问数据。

这种模式还使编写和实现单元测试更加直接。

迭代了一个大型React应用几次后,我发现这种模式使事情变得相对轻松,尤其是当您拥有具有经过计算的样式或复杂的DOM交互的大型组件时。

*阅读通量模式,然后看一下Marty.js,它极大地启发了这个答案(最近我一直在使用很多) Redux(和react-redux),它非常好地实现了这种模式。

对于那些在2018年或以后阅读本文的人的注意事项:

自从写了这个答案以来,React已经发展了很多,尤其是在引入Hooks之后。但是,此示例中的基础状态管理逻辑保持不变,更重要的是,保持状态和表示逻辑分离所获得的收益仍以相同的方式适用。


0

我认为您使用的是反模式,Facebook已在此链接上进行了解释

您会发现以下内容:

React.createClass({
  getInitialState: function() {
    return { value: { foo: 'bar' } };
  },

  onClick: function() {
    var value = this.state.value;
    value.foo += 'bar'; // ANTI-PATTERN!
    this.setState({ value: value });
  },

  render: function() {
    return (
      <div>
        <InnerComponent value={this.state.value} />
        <a onClick={this.onClick}>Click me</a>
      </div>
    );
  }
});

第一次渲染内部组件时,它将使用{foo:'bar'}作为值prop。如果用户单击锚点,则父组件的状态将更新为{值:{foo:'barbar'}},触发内部组件的重新渲染过程,该组件将收到{foo:'barbar'}作为道具的新价值。

问题在于,由于父组件和内部组件共享对同一对象的引用,因此当对象在onClick函数的第2行上发生突变时,内部组件所具有的属性将发生变化。因此,当重新渲染过程开始并且应该调用ComponentUpdate时,this.props.value.foo将等于nextProps.value.foo,因为实际上,this.props.value引用了与nextProps.value相同的对象。

因此,由于我们会错过道具的更改,并且会缩短重新渲染过程,因此UI不会从“ bar”更新为“ barbar”


您还可以发布Innercomponents代码吗?
阿卜杜拉·汗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.