reactjs中props和state最佳实践

来源:互联网 发布:步惊云软件网址 编辑:程序博客网 时间:2024/05/29 00:34

reactjs中props和state最佳实践

翻译文章:ReactJS Props vs State Best Practices

props & state的误用产生的bug,花费了我们大量的修复时间。在一个app中如果包含有成百不可见的危险代码,对我们的应用将是非常危险和头痛的事情。
这篇文章中,你将明白这些不可预知的行为产生的原因,这样在编程中出错将变得非常困难。这也可以用来解决我们其他JS编程中遇到的类似的问题。

在我开始学习reactjs时,第一个困惑我的事情是在使用props传递数据给组件、在组件的state中怎样读取数据源和store中提供的数据。这一方面,我低估了问题的难度。

一个简单的例子

这一节中我们将明白它们(props & state)为什么这么容易混淆。
下面这段代码是reactjs langding page中的第一个例子修改而来,其中的HelloMessage组件,在这里我们使用Counter来代替。

/* Snippet 1 */var Counter = React.createClass({  render: function() {    return <span>Count is {this.props.count}</span>;  }});React.render(<Counter count={5} />, mountNode);

非常容易,对不对?但是,我们现在使用state来做相同的事情。

/* Snippet 2 */var items = ['item1', 'item2', 'item3'];var Counter = React.createClass({  getInitialState: function() {    return {count: items.length};  },  render: function() {    return <span>Count is {this.state.count}</span>;  }});React.render(<Counter />, mountNode);

哪一个是恶魔?一旦我们程序变大,哪一个将是不可见的错误?实际上,两种用法都有可能正确,也有可能错误,这取决于它们使用时的上下文环境。接下来,我将尝试使用最好的方法来解释这是为什么。

我相信这些困惑的出现都是因为理解过于简单造成的。我们不能责备reactjs上面的例子不够好,这些例子确实是一个很好的入门实例。但是没有任何上下文环境,这样在我们使用的时候就会遇到困难。第一件比较明显的事情就是要告诉我们这个Counter组件的目的。我们是要用它来展示一个帖子的数量呢?还是要显示类似于facebook导航条上的消息数?

TweetList Component

这里我们将建立一个帖子列表组件(TweetList)和一个相对应的帖子计数器组件(Counter),来说明我们怎样及什么时候来使用props & state。以此说明当Counter展示一个帖子数量时该怎么使用。当我们传入1时,显示的是one,以此类推。首先我们尝试在没有props & state的帮助下来建立组件。

/* Snippet 3 */var _tweet = {  author: 'John',  content: 'My first ReactJS tweet',  impressions: 5};var Counter = React.createClass({  render: function() {    var wordsDataSet = ['zero', 'one', 'two', 'three', 'four', 'five'];    var word = wordsDataSet[_tweet.impressions];    return (      <span>Impressions: {word}</span>    );  }});var TweetItem = React.createClass({  render: function() {    return (    <div>      <div>@{_tweet.author} says:</div>      <div>{_tweet.content}</div>      <Counter />    </div>    );  }});

这一小段代码非常易于理解,当然你也可能发现了一下可怕的缺陷。最糟糕的是,这个Counter组件只能用于一个帖子,而不能复用。为了改变这一特征,我们来看看我们需要为Counter组件定义哪些属性:
1)不能将count定义死。(它应该是可以被用于tweet impressions, retweets, number of followers等等)
2)我们可以不用关心是怎样获取的数据。(它可以来自于JS对象、HTML属性、一个ajax回调等等)
3)它应该是很容易被任何层级的复杂组件调用。

Flux框架要求我们,组件层次中的最外层的那个组件(controller-view 组件)应该处理一个state,然后通过props传递给它的子组件们。这样无论何时何地要改变view将变得容易,无论何时只要controller-view的state发生变化,它都将触发它的render函数,子组件们将会获取新的props,然后逐级影响并更新其他组件。通过这些特性以及上面我们总结的Counter组件属性,Counter组件不应定义任何state,而是只能依靠props。所以我们修改了我们的代码:

/* Snippet 4 */// ...var Counter = React.createClass({  render: function() {    var wordsDataSet = ['zero', 'one', 'two', 'three', 'four', 'five'];    var word = wordsDataSet[this.props.count];    return (      <span>{word}</span>    );  }});var TweetItem = React.createClass({  getInitialState: function() {    return {tweet: _tweet};  },  render: function() {    return (      <div>        <div>@{_tweet.author} says:</div>        <div>{_tweet.content}</div>        Impressions:        <Counter count={_tweet.impressions} />      </div>    );  }});

现在我们的Counter变得更好了。当TweetItem是最外层组件(controller-view)时,那么它就应该有一个state。但是我们一直还有一个问题没有解决,TweetItem不太可能单独使用,我们也要将它放在tweet列表中,所以它也不应该拥有state。接下来,让我们来修改TweetItem,并且创建TweetList组件。

/* Snippet 5 */// ...var TweetItem = React.createClass({  render: function() {    return (      <div>        <div>@{this.props.tweet.author} says:</div>        <div>{this.props.tweet.content}</div>        Impressions:        <Counter count={this.props.tweet.impressions} />      </div>    );  }});var TweetList = React.createClass({  getInitialState: function() {    return {tweets: []};  },  componentDidMount: function() {    var self = this;    $.get('/latest-tweets.json', function(_tweets) {      self.setState({tweets: _tweets});    });  },  render: function() {    var listItems = this.state.tweets.map(function(tweet, index) {      return (        <li key={index}>          <TweetItem tweet={tweet} />        </li>      );    });    return (      <ul>        {listItems}      </ul>    );  }});

现在代码看起来更好了。最外层组件TweetList拥有了该应用的state。在componentDidMount函数中我们通过jQuery ajax来获取最后一个tweet,并且更新了其state。

提示:尽管讲ajax代码写在TweetList组件中没有任何问题,但是最好的做法还是将其逻辑分离。比如写在stores中。这样看起来更简洁明了。

UnreadMessagesCount Component

还记得我开始时候说过,不同版本的Counter组件的正确还是错误主要取决于其使用它的上下文环境吗?之前我们创建了一个只依赖于props的Counter组件,接下来,我们将创建一个依赖于state的Counter组件。
想象一下我们使用messages来代替tweets来工作,而且我们应该有一个MessageList组件。我们想让它最大限度的像TweetList组件一样渲染,只不过没有impressions数量。我们来一起定义这个组件的特征:
1)我们只关心unread messages的数量。在整个APP中我们只存储了一个数字。
2)只要unread messages一创建,它将自动更新。
3)无论在哪里它应该都可以被独立调用。类似于Facebook页眉中可以被使用。即我们的MessageList和Counter组件需要保持彼此分离。
我们的HTML文件里的代码应该看起来像这样:

<body>  <header>      <span id="counter-mount-node"></span>  </header>  <main>      <span id="message-list-mount-node"></span>  </main></body>

你是否已经看出来了,Counter组件自己就是一个controller-view。而且我们也不需要对它进行重用,它的代码简单明了。Bat-Signal规定我们只能使用state来实现。

var UnreadMessagesCounter = React.createClass({  getInitialState: function() {    return {count: 0};  },  componentDidMount: function() {    messageStore.addChangeListener(this.onMessagesChanged);  },  onMessagesChanged: function() {      var count = messageStore.getUnreadMessagesCount();      this.setState({count: count});  },  render: function() {    return (      <span>You have {this.state.count} unread messages</span>    );  }});var mountNode = document.getElementById("counter-mount-node");React.render(<UnreadMessagesCounter />, mountNode);$(document).ready(function() {    messageStore.loadMessagesAndEmitChange();    setInterval(messageStore.loadMessagesAndEmitChange, 5000);});

希望这些代码比较容易理解。这个组件非常的直接。我们没有使用逻辑代码,因为还没有messageStore。我们来看看这个相应的store是怎样的:
1)它应该只有一个通道来检索信息列表;
2)让组件订阅任何更新时事件,因此它们可以得到最后一个list。这里可以通过注册一个addChangeListener的回调方法来加以实现;
3)loadMessagesAndEmitChange方法在页面加载后,每个5秒被调用一次。

如果MessageList可以通过messageStore来监听其变化。我们就不用像TweetList那样通过MessageList组件代码来获取更新。这里我们省略了store代码。

结论

到目前为止,我们学会了关于reactjs的使用方法。我会让它更有趣的展示一个Twitter克隆在后面的阶段,或者是更令人兴奋的东西,我会做出决定在一天或两天。即将到来的文章将介绍其他主题,将最后的基础教程。如果你正在寻找问题运行片段在评论中让我知道,我很乐意帮助你。


0 0