reactjs中props和state最佳实践

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 Count is {this.props.count};
  }
});

React.render(5} />, mountNode);

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

/* Snippet 2 */
var items = ['item1', 'item2', 'item3'];

var Counter = React.createClass({
  getInitialState: function() {
    return {count: items.length};
  },

  render: function() {
    return Count is {this.state.count};
  }
});

React.render(, 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 (
      Impressions: {word}
    );
  }
});

var TweetItem = React.createClass({
  render: function() {
    return (
    
@{_tweet.author} says:
{_tweet.content}
); } });

这一小段代码非常易于理解,当然你也可能发现了一下可怕的缺陷。最糟糕的是,这个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 (
      {word}
    );
  }
});

var TweetItem = React.createClass({
  getInitialState: function() {
    return {tweet: _tweet};
  },

  render: function() {
    return (
      
@{_tweet.author} says:
{_tweet.content}
Impressions:
); } });

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

/* Snippet 5 */
// ...

var TweetItem = React.createClass({
  render: function() {
    return (
      
@{this.props.tweet.author} says:
{this.props.tweet.content}
Impressions: this.props.tweet.impressions} />
); } }); 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 (
  • ); }); return (
      {listItems}
    ); } });

    现在代码看起来更好了。最外层组件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 (
          You have {this.state.count} unread messages
        );
      }
    });
    
    var mountNode = document.getElementById("counter-mount-node");
    React.render(, 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克隆在后面的阶段,或者是更令人兴奋的东西,我会做出决定在一天或两天。即将到来的文章将介绍其他主题,将最后的基础教程。如果你正在寻找问题运行片段在评论中让我知道,我很乐意帮助你。


    你可能感兴趣的:(JavaScript)