ReactJS状态与道具

这可能是在回应和意见之间的界限,但是我正在来回地讨论如何构build一个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> ); } }); 

这似乎是错误的。

更多的是使我们的text模型属性state的React方法,并保存之前编译回模型:

 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的第一种方法是要走的路?

你的第二种方法更像。 React并不关心模型,因为它关心和它们如何stream经你的应用程序。 理想情况下,您的发布模型将被存储在根的单个组件中。 然后创build每个消耗模型部件的子组件。

您可以将callback传递给需要修改数据的子项,并从子组件调用它们。

直接修改this.props或this.state不是一个好主意,因为React将无法获取更改。 这是因为React对你的后期道具进行浅层比较以确定它是否已经改变。

我做了这个jsfiddle来显示数据如何从外部stream向内部组件:

http://jsfiddle.net/jxg/M3CLB/

handleClick方法显示3种方式(im)正确更新状态:

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

更新2016年:反应改变了,“道具与状态”的解释变得非常简单。 如果一个组件需要改变数据 – 把它放在一个状态,否则在道具上。 因为道具现在是只读的

道具和国家之间的确切区别是什么?

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

改变道具和状态

从反应文档

道具是不可变的:它们从父母传递,由父母“拥有”。 为了实现交互,我们向组件引入了可变状态。 this.state对组件是私有的,可以通过调用this.setState()来改变。 当状态被更新时,组件重新呈现自己。

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

从“ 反思”中的思考阅读

我们来看看每一个是哪一个状态。 简单地问一下每个数据的三个问题:

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

  3. 你能基于组件中的任何其他状态或道具来计算它吗? 如果是这样,那不是状态。

我不知道我是否回答你的问题,但是我发现,特别是在一个大型/不断增长的应用程序中,容器/组件模式工作得非常好。

基本上你有两个React组件:

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

注意:这个例子可能太简单了,不足以说明这种模式的好处,因为对于这样一个简单的例子来说,它是相当冗长的。

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

优点

通过保持显示逻辑和数据/状态pipe理分离,你有一个可重复使用的显示组件:

  • 可以使用诸如react-component-playground之类的东西轻易地用不同的道具组迭代
  • 可以用不同的容器包装不同的行为(或者与其他组件结合来构build应用程序的更大部分

你也有一个处理所有外部通信的容器组件。 如果稍后在*上进行任何重大更改,这将使您更容易灵活地访问数据。

这种模式也使得编写和实现unit testing更为直接。

迭代了一个大的React应用程序几次之后,我发现这个模式可以让事情变得相对简单,特别是当你有更大的组件,包括计算样式或者复杂的DOM交互。

*阅读stream量模式,并看看Marty.js ,很大程度上启发了这个答案(我最近一直在使用很多) Redux (和react-redux ),它实现了这个模式非常好。

我想你正在使用Facebook已经在这个链接解释过的反模式

这是你find的东西:

 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'}作为价值的道具。 如果用户点击锚点,则父组件的状态将更新为{value:{foo:'barbar'}},触发内部组件的重新渲染过程,该过程将接收{foo:'barbar'}作为道具的新价值。

问题是,由于父组件和内部组件共享对同一个对象的引用,所以当对象在onClick函数的第2行发生变异时,内部组件的prop将会改变。 因此,当重新呈现过程开始,并且应该调用ComponentComponent时,this.props.value.foo将等于nextProps.value.foo,因为事实上,this.props.value引用与nextProps.value相同的对象。

因此,由于我们将错过道具上的变化,并重新渲染过程中短路,用户界面将不会从“酒吧”更新到“野蛮人”