React使ってSPAを作るよ(4)

React使ってSPAを作るよ(4)

React使ってSPAを作るよ目次

React使ってSPAを作るよ(3)の続きです。

ReactでJSONの入れ子データを渡す

前回悩んでいた、入れ子になっているコメントリストのデータをどうやって渡すかという問題。これを解決すると同時に、articleそのものもリスト化して描画してしまいましょう。

まずはJSONデータを3件に増やしておきます。せっかくなので元のモックにある記事リンクの要素も追加しておきますね。commentDataをネストした構造はそのままです。

var articleData = [
  {
    articleTitle: "記事その1タイトル",
    articleUrl: "記事その1URL",
    articleDescription: "記事その1description",
    articleImage: "https://source.unsplash.com/random",
    commentData: [
      {author: "その1ユーザー1",
       authorImage: "https://source.unsplash.com/random",
       comment: "その1コメント1"
      },
      {author: "その1ユーザー2",
       authorImage: "https://source.unsplash.com/random",
       comment: "その1コメント2"
      },
      {author: "その1ユーザー3",
       authorImage: "https://source.unsplash.com/random",
       comment: "その1コメント3"
      }
    ]
  },
  {
    articleTitle: "記事その2タイトル",
    articleUrl: "記事その2URL",
    articleDescription: "記事その2description",
    articleImage: "https://source.unsplash.com/random",
    commentData: [
      {author: "その2ユーザー1",
       authorImage: "https://source.unsplash.com/random",
       comment: "その2コメント1"
      },
      {author: "その2ユーザー2",
       authorImage: "https://source.unsplash.com/random",
       comment: "その2コメント2"
      },
      {author: "その2ユーザー3",
       authorImage: "https://source.unsplash.com/random",
       comment: "その2コメント3"
      }
    ]
  },
  {
    articleTitle: "記事その3タイトル",
    articleUrl: "記事その3URL",
    articleDescription: "記事その3description",
    articleImage: "https://source.unsplash.com/random",
    commentData: [
      {author: "その3ユーザー1",
       authorImage: "https://source.unsplash.com/random",
       comment: "その3コメント1"
      },
      {author: "その3ユーザー2",
       authorImage: "https://source.unsplash.com/random",
       comment: "その3コメント2"
      },
      {author: "その3ユーザー3",
       authorImage: "https://source.unsplash.com/random",
       comment: "その3コメント3"
      }
    ]
  }
];

そして、記事リストをコンポーネント化します。
ここでちょっと問題が。

mapで生成したノード群をレンダリングする際に、「ルートになる親要素」に格納する必要がある、ということで・・・
li要素をmapで生成したら、親要素、つまりulをコンポーネント側で用意しなければなりません。
すると、React.render()でそれを格納する親要素が必要になります。
本当はul自体にli群を格納したいんですけど…
あとで解決策を探したいです。

ってことで、index.html側のソースを変更します。

<div class="wrap" id="container"></div>

#article-listを格納するdiv、#containerを用意しました。

JSXのレンダリング部分は、

/* React + JSX */
React.render(
  <ArticleArea data={articleData} />,
  document.getElementById('container')
);

となりますね。

クッ……ヤラ( ゚∀゚ )レタ!!!

よけいなdivが・・・

で、本題の記事リストコンポーネントはこうなります。

//記事リストコンポーネント
var ArticleArea = React.createClass({
  render: function() {
    var articleNodes = this.props.data.map(function(article) {
      return (
        <ArticleList articleComment={article.commentData} articleTitle={article.articleTitle} articleUrl={article.articleUrl} articleDescription={article.articleDescription} articleImage={article.articleImage} />
      )
    });
    return (
    <ul id="article-list">
      {articleNodes}
    </ul>
    )
  }
});

コメントリストのときと同じ感覚で、articleNodeにリストをドカッと入れることにします。
このとき、articleCommentに、JSONで入れ子になっているcommentDataを入れて渡してあげます。
(子のコンポーネント側でthis.props.commentDataとかでできたらいいなと思ってたんですが、できませんでした)

そして、子のコンポーネントを作ります。

//記事コンポーネント
var ArticleList = React.createClass({
  render: function() {
    var articleImage = {
      backgroundImage : "url(" + this.props.image + ")" 
    };
    return (
    <li>
      <article>
        <a href={this.props.articleUrl} target="_blank"  style={articleImage}>
          <h2>{this.props.articleTitle}</h2>
          <p>{this.props.articleDescription}</p>
        </a>
        <CommentList data={this.props.articleComment} />
      </article>
    </li>
    );
  }
});

ちょっとわかりやすくするためにa要素部分のコンポーネントは用意しないでここに直接書いちゃいました。
articleUrlなどはそのまま受け取ってリンクを生成し、コメントリストの部分は前回作ったコンポーネントCommentListを引っ張ってきます。

Reactの、親から子へ、という流れとソースの順序が逆になってるせいで説明がわかりづらいかもしれません・・・すみません(;´Д`)

app.jsxのソースは最終的にこうなります。

var React = require('react');

var articleData = [
  {
    articleTitle: "記事その1タイトル",
    articleUrl: "記事その1URL",
    articleDescription: "記事その1description",
    articleImage: "https://source.unsplash.com/random",
    commentData: [
      {author: "その1ユーザー1",
       authorImage: "https://source.unsplash.com/random",
       comment: "その1コメント1"
      },
      {author: "その1ユーザー2",
       authorImage: "https://source.unsplash.com/random",
       comment: "その1コメント2"
      },
      {author: "その1ユーザー3",
       authorImage: "https://source.unsplash.com/random",
       comment: "その1コメント3"
      }
    ]
  },
  {
    articleTitle: "記事その2タイトル",
    articleUrl: "記事その2URL",
    articleDescription: "記事その2description",
    articleImage: "https://source.unsplash.com/random",
    commentData: [
      {author: "その2ユーザー1",
       authorImage: "https://source.unsplash.com/random",
       comment: "その2コメント1"
      },
      {author: "その2ユーザー2",
       authorImage: "https://source.unsplash.com/random",
       comment: "その2コメント2"
      },
      {author: "その2ユーザー3",
       authorImage: "https://source.unsplash.com/random",
       comment: "その2コメント3"
      }
    ]
  },
  {
    articleTitle: "記事その3タイトル",
    articleUrl: "記事その3URL",
    articleDescription: "記事その3description",
    articleImage: "https://source.unsplash.com/random",
    commentData: [
      {author: "その3ユーザー1",
       authorImage: "https://source.unsplash.com/random",
       comment: "その3コメント1"
      },
      {author: "その3ユーザー2",
       authorImage: "https://source.unsplash.com/random",
       comment: "その3コメント2"
      },
      {author: "その3ユーザー3",
       authorImage: "https://source.unsplash.com/random",
       comment: "その3コメント3"
      }
    ]
  }
];

//コメント
var Comment = React.createClass({
  render: function() {
  var authorImage = {
    backgroundImage : "url(" + this.props.authorImage + ")" 
  };
    return (
      <li>
        <span data-account={this.props.author} style={authorImage}></span><span>{this.props.comment}</span>
      </li>
    )
  }
});

//コメントリスト
var CommentList = React.createClass({
  render: function() {
    var commentNodes = this.props.data.map(function(comment) {
      return (
        <Comment author={comment.author} authorImage={comment.authorImage} comment={comment.comment} />
      )
    });
    return (
      <ul className="comment-list">
        {commentNodes}
      </ul>
    )
  }
});

//記事コンポーネント
var ArticleList = React.createClass({
  render: function() {
    var articleImage = {
      backgroundImage : "url(" + this.props.articleImage + ")" 
    };
    return (
    <li>
      <article>
        <a href={this.props.articleUrl} target="_blank" style={articleImage}>
          <h2>{this.props.articleTitle}</h2>
          <p>{this.props.articleDescription}</p>
        </a>
        <CommentList data={this.props.articleComment} />
      </article>
    </li>
    );
  }
});

//記事リストコンポーネント
var ArticleArea = React.createClass({
  render: function() {
    var articleNodes = this.props.data.map(function(article) {
      return (
        <ArticleList articleComment={article.commentData} articleTitle={article.articleTitle} articleUrl={article.articleUrl} articleDescription={article.articleDescription} articleImage={article.articleImage} />
      )
    });
    return (
    <ul id="article-list">
      {articleNodes}
    </ul>
    )
  }
});

/* React + JSX */
React.render(
  <ArticleArea data={articleData} />,
  document.getElementById('container')
);

ブラウザで表示するとこんな感じです。

react

ちゃんと、記事リスト2には2のデータが入っていますね!
よしよし( ´∀`)bグッ!

次回はほかのパーツも組み込んでいきます。
そうなるとJSONデータもかなり膨大になってくるので、別ファイルにしたいですね。外部のAPIを叩いて取得したりする使い方もありそうなので、コンポーネントを整理していきたいと思います。

React使ってSPAを作るよ目次