React.js v15 チュートリアル【日本語翻訳】

2016年4月7日(木)に待望のメジャーバージョンとなる「React v15」がリリースされました。それに伴いReactの公式サイトのチュートリアルもv15に合わせて若干内容がアップデートされたので、改めてReact v15のチュートリアルの日本語訳をアップしておきます。

2016年10月にReactの公式チュートリアルがリニューアルされました。最新の「React」の公式チュートリアルの翻訳記事は以下となります。

React.js

react.js(リアクト)

React v15での変更点で特記すべき事項は以下の通りです。内部的な変更がメインとなっています。

  • Reactの吐き出すコードにidが付与されなくなった
  • Reactの吐き出すテキストのブロックにspan要素が含まれなくなった
  • spanやnoscriptとして吐き出されていた部分がが、コメントノードに置き換わった
  • v0.14まで非推奨となっていたAPIやアドオンなどが使えなくなった

その他React v15の詳細については、当ブログでリリースノートの日本語訳も記事にしていますので、本家のリリースノートと併せてご確認いただければと思います。

チュートリアル


以下、チュートリアルの日本語訳となります。
(頭では理解できていても言葉で表現するのが難しかったりして誤訳などもあるかと思いますので、おかしな部分があったら上記の本家のチュートリアリルも確認していただけたらと思います。また、間違いなどありましたら正解と一緒にお知らせいただけると幸いです。)

※v0.14から変更のあった部分や追加された部分はピンク色をつけてわかるようにしてあります。また削除された部分は取り消し線を付けてあります。v0.14時のチュートリアルを確認したい場合は以下をご覧ください。
React.js v0.14 チュートリアル【日本語翻訳】

React.js チュートリアル(日本語訳)

We’ll be building a simple but realistic comments box that you can drop into a blog, a basic version of the realtime comments offered by Disqus, LiveFyre or Facebook comments.

シンプルですが実用的なブログ向けのコメントボックスを作っていきます。Disqus、LiveFyre、Facebookのコメントでも使われているリアルタイムコメント機能のベーシック版となります。

We’ll provide:

実装する機能:

  • A view of all of the comments

    コメントの全てのビュー
  • A form to submit a comment

    コメントを送信するためのフォーム
  • Hooks for you to provide a custom backend

    カスタムバックエンドを提供するためのフック

It’ll also have a few neat features:

いくつかの特徴:

  • Optimistic commenting: comments appear in the list before they’re saved on the server so it feels fast.

    軽快なコメンティング: コメントはサーバーに保存される前にリストに表示されるので高速に感じます。
  • Live updates: other users’ comments are popped into the comment view in real time.

    ライブアップデート: 他のユーザーのコメントがリアルタイムでコメントビューにひょいと出てきます。
  • Markdown formatting: users can use Markdown to format their text.

    Markdown形式: ユーザーはテキストをフォーマットするためにMarkdownを使えます。

Want to skip all this and just see the source?: すべてスキップして、今すぐソースをみたいですか?

It’s all on GitHub.

すべてはGitHubにあります

Running a server: サーバーを動かす

In order to start this tutorial, we’re going to require a running server. This will serve purely as an API endpoint which we’ll use for getting and saving data. In order to make this as easy as possible, we’ve created a simple server in a number of scripting languages that does exactly what we need it to do. You can view the source or download a zip file containing everything needed to get started.

このチュートリアルを始めるために、サーバーを動かすことが必要となります。これは単に、データを取得して保存するために使うAPIのエンドポイントとしての役割を果たします。できるだけ簡単にこのサーバーを用意するために、必要なことをしっかり行ってくれるいくつかのスクリプト言語でシンプルなサーバーを作りました。ソースを見るか、スタートするのに必要な全てを含んでいるzipファイルをダウンロードしてください。

For sake of simplicity, the server we will run uses a JSON file as a database. You would not run this in production but it makes it easy to simulate what you might do when consuming an API. Once you start the server, it will support our API endpoint and it will also serve the static pages we need.

単純にするために、動かすサーバーは、データベースとしてJSONファイルを使用します。これを製品版(プロダクション)で動かすことはないでしょうが、APIを実行する際に行うことを容易にシミュレートできるようになっています。サーバーを一度起動したら、サーバーはAPIのエンドポイントをサポートし、さらに我々が必要な静的ページを送信します。

Getting started: スタートしよう

For this tutorial, we’re going to make it as easy as possible. Included in the server package discussed above is an HTML file which we’ll work in. Open up public/index.html in your favorite editor. It should look something like this:

このチュートリアルを通して、できる限り簡単に説明していくつもりです。上で述べたサーバーに含まれたパッケージは、その中で動くHTMLファイルです。お好きなエディタでpublic/index.htmlを開いてください。以下のようになっているでしょう。

<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>React Tutorial</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/remarkable/1.6.2/remarkable.min.js"></script> 
  </head>
  <body>
    <div id="content"></div>
    <script type="text/babel" src="scripts/example.js"></script>
    <script type="text/babel">
      // To get started with this tutorial running your own code, simply remove
      // the script tag loading scripts/example.js and start writing code here.
    </script>
  </body>
</html>
Code

For the remainder of this tutorial, we’ll be writing our JavaScript code in this script tag. We don’t have any advanced live-reloading so you’ll need to refresh your browser to see updates after saving. Follow your progress by opening http://localhost:3000 in your browser (after starting the server). When you load this for the first time without any changes, you’ll see the finished product of what we’re going to build. When you’re ready to start working, just delete the preceding <script> tag and then you can continue.

このチュートリアルでは、最後までこのScriptタグの中にJavaScriptのコードを書いていきます。高度なライブリローディングを用意していないので、保存後アップデートの内容を確認するためにブラウザを更新する必要があります。ブラウザ上でhttp://localhost:3000を開くことによって(サーバーを起動した後)、進行を追ってください。これを全く変更のない状態で初めてロードした場合、これからビルドしていくものが完成した状態で表示されます。動かし始める準備が整ったら、前述の<script>タグを削除して、それから続けてください。

注意:
Ajaxの処理を書く部分をシンプルにするためにjQueryを読み込んでいますが、Reactを動かすために必須とはなっていません

Your first component: はじめてのコンポーネント

React is all about modular, composable components. For our comment box example, we’ll have the following component structure:

Reactはモジュラー、構成可能(コンポーザブル)なコンポーネントが全てです。コメントボックスの例では、以下のような構造のコンポーネントを使っていきます。

- CommentBox  - CommentList   - Comment  - CommentForm

Let’s build the CommentBox component, which is just a simple <div>:

CommentBoxコンポーネント(シンプルな<div>)を作りましょう。

// tutorial1.js
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});
ReactDOM.render(
  <CommentBox />,
  document.getElementById('content')
);
Code

Note that native HTML element names start with a lowercase letter, while custom React class names begin with an uppercase letter.

ネイティブなHTML要素は頭文字が小文字となりますが、ReactのカスタムClassは頭文字が大文字となるということに気をつけてください。

JSX Syntax: JSX構文

The first thing you’ll notice is the XML-ish syntax in your JavaScript. We have a simple precompiler that translates the syntactic sugar to this plain JavaScript:

まず最初にJavaScriptの中にあるXML-ishの構文に気付くでしょう。Reactには糖衣構文(シンタックスシュガー)を素のJavaScriptに翻訳するシンプルなプリコンパイラがあります。

// tutorial1-raw.js
var CommentBox = React.createClass({displayName: 'CommentBox',
  render: function() {
    return (
      React.createElement('div', {className: "commentBox"},
        "Hello, world! I am a CommentBox."
      )
    );
  }
});
ReactDOM.render(
  React.createElement(CommentBox, null),
  document.getElementById('content')
);
Code

Its use is optional but we’ve found JSX syntax easier to use than plain JavaScript. Read more on the JSX Syntax article.

使用することは任意となりますが、Reactは素のJavaScriptより簡単に扱えるJSX構文を採用しています。詳細はJSX構文に関する記事を読むとよいでしょう。

What’s going on: 何が起きているのか

We pass some methods in a JavaScript object to React.createClass() to create a new React component. The most important of these methods is called render which returns a tree of React components that will eventually render to HTML.

新しいReactコンポーネントを作るために、JavaScriptのオブジェクトの中のメソッドをReact.createClass()に渡しています。その中でも最も重要なメソッドは「render」です。renderは、最終的にHTMLにレンダリングされるReactコンポーネントのツリーを返す役割を担っています。

The <div> tags are not actual DOM nodes; they are instantiations of React div components. You can think of these as markers or pieces of data that React knows how to handle. React is safe. We are not generating HTML strings so XSS protection is the default.

使われている<div>タグは、現実のDOMノードではありません。それらはReactのdivコンポーネントがインスタンス化されたものです。つまりReactがどう扱うか知っているデータのマーカーやその一片のようなものとして考えることができます。Reactは安全です。HTML文字列を生成していないため、最初からXSS対策がなされています。

You do not have to return basic HTML. You can return a tree of components that you (or someone else) built. This is what makes React composable: a key tenet of maintainable frontends.

ベーシックなHTMLを返さなくてよく、自分(または他者)が作ったコンポーネントのツリーを返せます。これがReactをコンポーザブルにしています。それはメンテナブルなフロントエンドとして重要な信念でもあります。

ReactDOM.render() instantiates the root component, starts the framework, and injects the markup into a raw DOM element, provided as the second argument.

ReactDOM.render()は、ルートコンポーネントをインスタンス化し、フレームワーク(構成)を開始し、マークアップを生のDOM要素(2番目の引数として渡される)へ注入します。

The ReactDOM module exposes DOM-specific methods, while React has the core tools shared by React on different platforms (e.g., React Native).

ReactDOMモジュールは、DOMの特殊なメソッドを公開し、同時にReactは異なるプラットフォーム上のReact(例: React Native)によってシェアされるコアツールを持っています。

It is important that ReactDOM.render remain at the bottom of the script for this tutorial. ReactDOM.render should only be called after the composite components have been defined.

ReactDOM.renderはこのチュートリアルを通してスクリプトの下に配置しておくことが重要です。ReactDOM.renderは各種コンポーネントが定義された後にのみ実行されるべきです。

Composing components: コンポーネントを作る

Let’s build skeletons for CommentList and CommentForm which will, again, be simple <div>s. Add these two components to your file, keeping the existing CommentBox declaration and ReactDOM.render call:

CommentListCommentFormのためのスケルトンを作りましょう。それらは再度シンプルな<div>タグで作られます。存在しているCommentBoxの宣言とReactDOM.renderの呼び出しを保持しながら、この2つのコンポーネントをファイルに追加しましょう。

// tutorial2.js
var CommentList = React.createClass({
  render: function() {
    return (
      <div className="commentList">
        Hello, world! I am a CommentList.
      </div>
    );
  }
});

var CommentForm = React.createClass({
  render: function() {
    return (
      <div className="commentForm">
        Hello, world! I am a CommentForm.
      </div>
    );
  }
});
Code

Next, update the CommentBox component to use these new components:

次に、新しいコンポーネントを扱うためにCommentBoxコンポーネントをアップデートしましょう。

// tutorial3.js
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList />
        <CommentForm />
      </div>
    );
  }
});
Code

Notice how we’re mixing HTML tags and components we’ve built. HTML components are regular React components, just like the ones you define, with one difference. The JSX compiler will automatically rewrite HTML tags to React.createElement(tagName) expressions and leave everything else alone. This is to prevent the pollution of the global namespace.

どのようにHTMLのタグと作ったコンポーネントを混合して書いているか見てください。HTMLコンポーネントは、1つの違いはあるものの、あなたが定義するコンポーネントと同じように正規のReactコンポーネントです。JSXのコンパイラは自動的にHTMLタグをReact.createElement(tagName) という形式で書き換えて、それ以外の部分はそのままにしておきます。これはグローバルの名前空間の汚染を防ぐためです。

Using props: propsを使う

Let’s create the Comment component, which will depend on data passed in from its parent. Data passed in from a parent component is available as a ‘property’ on the child component. These ‘properties’ are accessed through this.props. Using props, we will be able to read the data passed to the Comment from the CommentList, and render some markup:

Commentコンポーネントを作りましょう。子コンポーネントは親からのデータに依存することになります。親コンポーネントから渡されたデータは、子コンポーネント上で「プロパティ」として利用することができます。これらの「プロパティ」はthis.propsを通してアクセスされます。propsを使うことで、CommentListからCommentに渡されたデータを読むことができ、マークアップをレンダリングできるようになります。

// tutorial4.js
var Comment = React.createClass({
  render: function() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        {this.props.children}
      </div>
    );
  }
});
Code

By surrounding a JavaScript expression in braces inside JSX (as either an attribute or child), you can drop text or React components into the tree. We access named attributes passed to the component as keys on this.props and any nested elements as this.props.children.

(「属性」か「子要素」かのどちらかとして)JSXの中でJavaScriptのコードを波括弧で囲むことによって、テキストやReactコンポーネントをツリーの中に落とし込むことができます。this.propsのkeyを通してコンポーネントに渡された名前の付けられた属性と、this.props.childrenを通してネストされた要素にアクセスします。

Component Properties: コンポーネントのプロパティ

Now that we have defined the Comment component, we will want to pass it the author name and comment text. This allows us to reuse the same code for each unique comment. Now let’s add some comments within our CommentList:

Commentコンポーネントを定義しました。ユーザー名とコメントのテキストをCommentコンポーネントに渡したくなるでしょう。Commentコンポーネントを定義しておくことで、それぞれのコメントごとに同じコードを使い回すことができるようになります。さあ、いくつかのコメントをCommentListの中に追加しましょう。

// tutorial5.js
var CommentList = React.createClass({
  render: function() {
    return (
      <div className="commentList">
        <Comment author="Pete Hunt">This is one comment</Comment>
        <Comment author="Jordan Walke">This is *another* comment</Comment>
      </div>
    );
  }
});
Code

Note that we have passed some data from the parent CommentList component to the child Comment components. For example, we passed Pete Hunt (via an attribute) and This is one comment (via an XML-like child node) to the first Comment. As noted above, the Comment component will access these ‘properties’ through this.props.author, and this.props.children.

親のCommentListコンポーネントから子のCommentコンポーネントにいくつかのデータを渡したということに注意してください。例えば、Pete Hunt(属性を通して)とThis is one comment(XMLのような子ノードを通して)を一番目のCommentに渡しました。上記の通り、Commentコンポーネントはthis.props.authorthis.props.childrenを通してこれらの「プロパティ」にアクセスします。

Adding Markdown: Markdownを追加する

Markdown is a simple way to format your text inline. For example, surrounding text with asterisks will make it emphasized.

Markdown記法はインラインのテキストをフォーマットする簡単な方法です。例えば、強調させたい場合は、アスタリスクと一緒にテキストを囲みます。

In this tutorial we use a third-party library remarkable which takes Markdown text and converts it to raw HTML. We already included this library with the original markup for the page, so we can just start using it. Let’s convert the comment text to Markdown and output it:

このチュートリアルでは、Markdownで書かれたテキストを受け取り、生のHTMLにコンバートしてくれるサードパーティーのライブラリ「remarkable.js」を使います。このライブラリはすでにオリジナルのマークアップと共にページに挿入されているので、そのままこのライブラリを使うことができます。コメントのテキストをMarkdownにコンバートし、アウトプットしてみましょう。

// tutorial6.js
var Comment = React.createClass({
  render: function() {
    var md = new Remarkable();
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        {md.render(this.props.children.toString())}
      </div>
    );
  }
});
Code

All we’re doing here is calling the remarkable library. We need to convert this.props.children from React’s wrapped text to a raw string that remarkable will understand so we explicitly call toString().

ここで行っていることはremarkableライブラリの呼び出しです。this.props.childrenを、Reactのラップされたテキストから、生の文字列(remarkableが解釈することになる)にコンバートする必要があるので、明示的にtoString()を呼ぶようにします。

But there’s a problem! Our rendered comments look like this in the browser: “<p>This is <em>another</em> comment</p>”. We want those tags to actually render as HTML.

しかし、ここに問題があります。レンダリングされたコメントはブラウザに以下のような感じで表示されてしまいます。
<p>This is <em>another</em> comment</p>“.
実際はこれらのタグをHTMLとしてレンダリングしないといけません。

That’s React protecting you from an XSS attack. There’s a way to get around it but the framework warns you not to use it:

このようにしてReactはXSS攻撃からあなたを守っています。これをうまく回避する方法もありますが、フレームワークは使用してはいけないと警告を出します。

// tutorial7.js
var Comment = React.createClass({
  rawMarkup: function() {
    var md = new Remarkable();
    var rawMarkup = md.render(this.props.children.toString(), {sanitize: true});
    return { __html: rawMarkup };
  },

  render: function() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={this.rawMarkup()} />
      </div>
    );
  }
});
Code

This is a special API that intentionally makes it difficult to insert raw HTML, but for remarkable we’ll take advantage of this backdoor.

これは生のHTMLの挿入を意図的に難しくさせる特殊なAPIですが、remarkableのためにこのバックドアのアドバンテージを使います。

Remember: by using this feature you’re relying on remarkable to be secure. In this case, we pass sanitize: true which tells marked to escape any HTML markup in the source instead of passing it through unchanged.remarkable automatically strips HTML markup and insecure links from the output.

備忘: この機能を使うということは、安全性をmarkedに依存しているということremarkableが自動的にHTMLのマークアップとアウトプットからの安全でないリンクを取り除いているということを忘れないでください。

Hook up the data model: データモデルをつなぐ

So far we’ve been inserting the comments directly in the source code. Instead, let’s render a blob of JSON data into the comment list. Eventually this will come from the server, but for now, write it in your source:

ここまでは、ソースコードの中に直接コメントを挿入することを行ってきました。次は、JSONデータをコメント一覧の中にレンダリングすることを行ってみましょう。最終的にサーバーから取得することになりますが、まずは自分のソースの中に書いていきます。

// tutorial8.js
var data = [
  {id: 1, author: "Pete Hunt", text: "This is one comment"},
  {id: 2, author: "Jordan Walke", text: "This is *another* comment"}
];
Code

We need to get this data into CommentList in a modular way. Modify CommentBox and the ReactDOM.render() call to pass this data into the CommentList via props:

このデータをモジュール式の方法で(一つの塊として)CommentListの中に取り込む必要があります。CommentBoxReactDOM.render()の呼び出しを修正して、propsを通してこのデータをCommentListの中に渡せるようにしましょう。

// tutorial9.js
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm />
      </div>
    );
  }
});

ReactDOM.render(
  <CommentBox data={data} />,
  document.getElementById('content')
);
Code

Now that the data is available in the CommentList, let’s render the comments dynamically:

これでデータはCommentListの中で使えるようになるので、コメントを動的にレンダリングしましょう。

// tutorial10.js
var CommentList = React.createClass({
  render: function() {
    var commentNodes = this.props.data.map(function (comment) {
      return (
        <Comment author={comment.author} key={comment.id}>
          {comment.text}
        </Comment>
      );
    });
    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
});
Code

That’s it!

そうです!

Fetching from the server: サーバから取ってくる

Let’s replace the hard-coded data with some dynamic data from the server. We will remove the data prop and replace it with a URL to fetch:

上記ではハードコーディングされたデータを使いましたが、サーバーからの動的なデータに置き換えていきましょう。dataプロップを取り除き、フェッチするためのURLに置き換えます。

// tutorial11.js
ReactDOM.render(
  <CommentBox url="/api/comments" />,
  document.getElementById('content')
);
Code

This component is different from the prior components because it will have to re-render itself. The component won’t have any data until the request from the server comes back, at which point the component may need to render some new comments.

このコンポーネントは以前のコンポーネントとは異なります。なぜならコンポーネント自身を再びレンダリング(re-render)しなければいけなくなるからです。コンポーネントはサーバーからのリクエストが戻ってくるまでどんなデータも持たず、リクエストが戻ってきた時点でコンポーネントは新しいコメントをレンダリングするようになっています。

Note: the code will not be working at this step.

注意: コードはこの段階では動きません。

Reactive state: リアクティブな状態

So far, based on its props, each component has rendered itself once. props are immutable: they are passed from the parent and are “owned” by the parent. To implement interactions, we introduce mutable state to the component. this.state is private to the component and can be changed by calling this.setState(). When the state updates, the component re-renders itself.

ここまでは、propsに基づいて、各コンポーネントは自分自身を一回レンダリングしてきました。propsはイミュータブルです:それらは親から渡されるものであり、親によって「所有されているもの」です。親と子の相互作用を実装するために、ミュータブルなstateをコンポーネントに取り込みます。this.stateはコンポーネントに対してプライベートであり、this.setState()を実行することによって変更可能です。stateがアップデートする時、コンポーネントは自分自身を再びレンダリングします。

render() methods are written declaratively as functions of this.props and this.state. The framework guarantees the UI is always consistent with the inputs.

render()メソッドはthis.porpsthis.stateの関数として宣言的に書かれています。フレームワークは、UIが常にインプットと一致していることを保証しています。

When the server fetches data, we will be changing the comment data we have. Let’s add an array of comment data to the CommentBox component as its state:

サーバーがデータを取得する時、保持しているコメントデータを変更していることになります。コメントデータの配列をstateとしてCommentBoxコンポーネントに追加してみましょう。

// tutorial12.js
var CommentBox = React.createClass({
  getInitialState: function() {
    return {data: []};
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
});
Code

getInitialState() executes exactly once during the lifecycle of the component and sets up the initial state of the component.

getInitialState()はコンポーネントのライフサイクルの間に1回だけ正確に実行され、コンポーネントの初期状態をセットアップします。

Updating state: stateのアップデート

When the component is first created, we want to GET some JSON from the server and update the state to reflect the latest data. We’re going to use jQuery to make an asynchronous request to the server we started earlier to fetch the data we need. The data is already included in the server you started (based on the comments.json file), so once it’s fetched, this.state.data will look something like this:

コンポーネントがまず作られたら、サーバーからのJSONをGETして、最新のデータに反映するためにstateをアップデートするようにします。必要なデータを取ってくるために先ほど起動したサーバーへの非同期のリクエストを投げるためにjQueryを使っていきます。データはすでに起動したサーバー(comments.jsonファイルをベースとした)に取り込まれているので、一度fetchしたら、this.state.dataは以下のようになっています。

// tutorial13.json
[
  {"author": "Pete Hunt", "text": "This is one comment"},
  {"author": "Jordan Walke", "text": "This is *another* comment"}
]
Code
// tutorial13.js
var CommentBox = React.createClass({
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
});
Code

Here, componentDidMount is a method called automatically by React after a component is rendered for the first time. The key to dynamic updates is the call to this.setState(). We replace the old array of comments with the new one from the server and the UI automatically updates itself. Because of this reactivity, it is only a minor change to add live updates. We will use simple polling here but you could easily use WebSockets or other technologies.

さて、componentDidMountはコンポーネントが最初にレンダリングされた後にReactによって自動的に呼ばれるメソッドです。動的な更新のポイントはthis.setState()の呼び出しです。コメントの古い配列をサーバーからの新しい配列に置き換え、UIは自動的に自分自身をアップデートします。このリアクティビティによって、ライブアップデートを追加するための変更は最小で済みます。ここではシンプルなポーリングを使っていますが、簡単にWebSocketsや他の技術を使うこともできます。

// tutorial14.js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
});

ReactDOM.render(
  <CommentBox url="/api/comments" pollInterval={2000} />,
  document.getElementById('content')
);
Code

All we have done here is move the AJAX call to a separate method and call it when the component is first loaded and every 2 seconds after that. Try running this in your browser and changing the comments.json file (in the same directory as your server); within 2 seconds, the changes will show!

ここで行ってきたことは、AJAXの呼び出しを独立したメソッドに移動させることと、コンポーネントが最初にロードされ、その後2秒ごとにそれを実行させることです。ブラウザでこれを動かし、(あなたのサーバーでも同じディレクトリの中にある)comment.jsonファイルを変更してみましょう。2秒以内に変化が見られるでしょう!

Adding new comments: 新しいコメントを追加する

Now it’s time to build the form. Our CommentForm component should ask the user for their name and comment text and send a request to the server to save the comment.

さて、フォームを作る時がきました。CommentFormコンポーネントはユーザーに名前とコメントのテキストを入力してもらい、そのコメントを保存するためにサーバーへリクエストを送るようにしましょう。

// tutorial15.js
var CommentForm = React.createClass({
  render: function() {
    return (
      <form className="commentForm">
        <input type="text" placeholder="Your name" />
        <input type="text" placeholder="Say something..." />
        <input type="submit" value="Post" />
      </form>
    );
  }
});
Code

Controlled components

With the traditional DOM, input elements are rendered and the browser manages the state (its rendered value). As a result, the state of the actual DOM will differ from that of the component. This is not ideal as the state of the view will differ from that of the component. In React, components should always represent the state of the view and not only at the point of initialization.

従来のDOMでは、input要素はレンダリングされると、ブラウザがその状態(そのレンダリングされた値)を管理します。結果として、実際のDOMの状態はコンポーネントの状態とは異なってきます。viewの状態がコンポーネントの状態と異なることは理想的ではありません。Reactでは、初期化時のみならずコンポーネントは常にviewの状態を表しておくべきです。

Hence, we will be using this.state to save the user’s input as it is entered. We define an initial state with two properties author and text and set them to be empty strings. In our <input> elements, we set the value prop to reflect the state of the component and attach onChange handlers to them. These <input> elements with a value set are called controlled components. Read more about controlled components on the Forms article.

従って、ユーザーのinputが入力された時にそれを保存するためにthis.stateを使うことになります。authortextの2つのプロパティの初期stateを定義し、それらに空の文字列をセットします。各<input>要素には、コンポーネントのstateを反映させるためのvalueプロパティをセットし、onChangeハンドラ―をそれらに設置します。これらのvalueをセットされた<input>要素は、「controlled components」と呼ばれます。controlled componentsについての詳細はForms articleをお読みください。

// tutorial16.js
var CommentForm = React.createClass({
  getInitialState: function() {
    return {author: '', text: ''};
  },
  handleAuthorChange: function(e) {
    this.setState({author: e.target.value});
  },
  handleTextChange: function(e) {
    this.setState({text: e.target.value});
  },
  render: function() {
    return (
      <form className="commentForm">
        <input
          type="text"
          placeholder="Your name"
          value={this.state.author}
          onChange={this.handleAuthorChange}
        />
        <input
          type="text"
          placeholder="Say something..."
          value={this.state.text}
          onChange={this.handleTextChange}
        />
        <input type="submit" value="Post" />
      </form>
    );
  }
});
Code

Events

React attaches event handlers to components using a camelCase naming convention. We attach onChange handlers to the two <input> elements. Now, as the user enters text into the <input> fields, the attached onChange callbacks are fired and the state of the component is modified. Subsequently, the rendered value of the input element will be updated to reflect the current component state.

Reactはイベントハンドラ(命名規則としてキャメルケースを用います)をコンポーネントに結び付けます。2つのinput要素にonChangeハンドラを設置します。さて、inputフィールドにユーザーがテキストを入力した時に、設置されたonChangeハンドラは発火し、コンポーネントのstateは変更されます。続いて、レンダリングされたinput要素のvalueは現在のコンポーネントのstateを反映するためにアップデートされることになります。

Submitting the form

Let’s make the form interactive. When the user submits the form, we should clear it, submit a request to the server, and refresh the list of comments. To start, let’s listen for the form’s submit event and clear it.

フォームを対話式にしてみましょう。ユーザーが送信した時に、それをクリアし、サーバーへリクエストを送信し、コメントのリストをリフレッシュするようにします。始めに、フォームの送信イベントを受け取り、それをクリアしてみましょう。

// tutorial17.js
var CommentForm = React.createClass({
  getInitialState: function() {
    return {author: '', text: ''};
  },
  handleAuthorChange: function(e) {
    this.setState({author: e.target.value});
  },
  handleTextChange: function(e) {
    this.setState({text: e.target.value});
  },
  handleSubmit: function(e) {
    e.preventDefault();
    var author = this.state.author.trim();
    var text = this.state.text.trim();
    if (!text || !author) {
      return;
    }
    // TODO: send request to the server
    this.setState({author: '', text: ''});
  },
  render: function() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>
        <input
          type="text"
          placeholder="Your name"
          value={this.state.author}
          onChange={this.handleAuthorChange}
        />
        <input
          type="text"
          placeholder="Say something..."
          value={this.state.text}
          onChange={this.handleTextChange}
        />
        <input type="submit" value="Post" />
      </form>
    );
  }
});
Code

Events: イベント

We attach an onSubmit handler to the form that clears the form fields when the form is submitted with valid input.

onSubmitハンドラをフォームに設置し、そのフォームはvalidなインプットデータが送信される時にフォームのフィールドをクリアします。

Call preventDefault() on the event to prevent the browser’s default action of submitting the form.

フォーム送信時のブラウザのデフォルトのアクションを防ぐために、イベント上でpreventDefault()を呼びましょう。

Callbacks as props: propsとしてのコールバック

When a user submits a comment, we will need to refresh the list of comments to include the new one. It makes sense to do all of this logic in CommentBox since CommentBox owns the state that represents the list of comments.

ユーザーがコメントを送信する時、新しいコメントを含めるためにコメントのリストをリフレッシュする必要があります。CommentBoxはコメントのリストを表示するstateを所持しているため、CommentBoxでこのロジックの全てを行うことは理にかなっています。

We need to pass data from the child component back up to its parent. We do this in our parent’s render method by passing a new callback (handleCommentSubmit) into the child, binding it to the child’s onCommentSubmit event. Whenever the event is triggered, the callback will be invoked:

子コンポーネントのバックアップからその親にデータを渡す必要があります。新しいコールバック(handleCommentSubmit) を子に渡し、子のonCommentSubmitイベントにそれをバインドすることによって、親のrenderメソッドの中でこれを行います。イベントがトリガーされるたびに、コールバックは実行されることになります。

// tutorial18.js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  handleCommentSubmit: function(comment) {
    // TODO: submit to the server and refresh the list
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
});
Code

Now that CommentBox has made the callback available to CommentForm via the onCommentSubmit prop, the CommentForm can call the callback

Let’s call the callback from the CommentForm when the user submits the form:

CommentBoxは、onCommentSubmit propを通してcallbackをCommentFormに利用できるようにしました。これにより、ユーザーがフォームをサブミットした時に、CommentFormからコールバックを実行してみましょう。CommentFormはcallbackを呼び出すことができるようになります。

// tutorial19.js
var CommentForm = React.createClass({
  getInitialState: function() {
    return {author: '', text: ''};
  },
  handleAuthorChange: function(e) {
    this.setState({author: e.target.value});
  },
  handleTextChange: function(e) {
    this.setState({text: e.target.value});
  },
  handleSubmit: function(e) {
    e.preventDefault();
    var author = this.state.author.trim();
    var text = this.state.text.trim();
    if (!text || !author) {
      return;
    }
    this.props.onCommentSubmit({author: author, text: text});
    this.setState({author: '', text: ''});
  },
  render: function() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>
        <input
          type="text"
          placeholder="Your name"
          value={this.state.author}
          onChange={this.handleAuthorChange}
        />
        <input
          type="text"
          placeholder="Say something..."
          value={this.state.text}
          onChange={this.handleTextChange}
        />
        <input type="submit" value="Post" />
      </form>
    );
  }
});
Code

Now that the callbacks are in place, all we have to do is submit to the server and refresh the list:

コールバックが適切に実行されたら、サーバーにサブミットしてリストを更新する必要があります。

// tutorial20.js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  handleCommentSubmit: function(comment) {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      type: 'POST',
      data: comment,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
});
Code

Optimization: optimistic updates: 最適化: 快適なアップデート

Our application is now feature complete but it feels slow to have to wait for the request to complete before your comment appears in the list. We can optimistically add this comment to the list to make the app feel faster.

アプリケーションは今フィーチャーコンプリート(ユーザーが求めている何かの提供を完了させること)状態となっていますが、コメントがリストに表示されるまで、完了のためのリクエストを待たなくてはならないことは遅いと感じるはずです。アプリをより速く感じさせるためにコメントをリストに気軽に追加する方法があります。

// tutorial21.js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  handleCommentSubmit: function(comment) {
    var comments = this.state.data;
    // Optimistically set an id on the new comment. It will be replaced by an
    // id generated by the server. In a production application you would likely
    // not use Date.now() for this and would have a more robust system in place.
    // idを新しいコメントに気軽にセットしましょう。サーバーによって生成されたidによって
    // 置き換えられます。製品版においてDate.now()をおそらく使わなくなり、
    // 適切により健全なシステムを持つことになるでしょう。
    comment.id = Date.now();
    var newComments = comments.concat([comment]);
    this.setState({data: newComments});
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      type: 'POST',
      data: comment,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        this.setState({data: comments});
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
});
Code

Congrats!: おめでとう!

You have just built a comment box in a few simple steps. Learn more about why to use React, or dive into the API reference and start hacking! Good luck!

いくつかのシンプルなステップを通して、コメントボックスを作りました。なぜReactを使うのかもっと学んでいきましょう。または、APIリファレンスに飛び込んで、ハッキングをスタートさせましょう。幸運を祈ります!


チュートリアルは以上となります。

サンプルを実行して確認する

このチュートリアルのサンプルはGitHubからダウンロードすることができます。手っ取り早くReactがどんなものか見たい場合は、以下より「react-tutorial-master」をダウンロードして動作などを確認されると良いと思います。

サンプルをダウンロードしたら、適当なフォルダに入れてその中に移動します。

$ cd react-tutorial-master

ご自身の環境に合わせてサーバーを起動します。Node.js環境で確認する場合は以下のコマンドを実行します。その他にPythonRubyPHPGoに対応しています。

$ npm install
$ node server.js
[npm install]コマンドでpackage.jsonに記載の依存関係にあるパッケージがインストールされ、[node server.js]コマンドでサーバーを起動します。

ブラウザで「http://localhost:3000」にアクセスします。サンプルはコメントリストになっていて、付属の「comments.json」ファイルに対してデータの読み込みや書き込みが行えるようになっています。

今後の参考に

チュートリアルだけでもそれなりに把握することができますが、オライリーからもReactに関する書籍が出ているので紹介しておきます。内容自体は現時点ではv0.13当時のものとなりますが、React v0.14の変更内容React v15の変更内容を頭に入れておけば、まだまだ十分頼りになる書籍だと思います。

入門 React ―コンポーネントベースのWebフロントエンド開発
  • 『入門 React ―コンポーネントベースのWebフロントエンド開発』
  • 著者: Frankie Bagnardi, Jonathan Beebe, Richard Feldman, Tom Hallett, Simon HØjberg, Karl Mikkelsen, 宮崎 空(翻訳)
  • 出版社: オライリージャパン
  • 発売日: 2015年4月3日
  • ISBN: 978-4873117195

またご存知の方も多いと思いますが、以下もReactを理解する上ですごく助けになりますので紹介しておきます。

コメント一覧

  1. 参考にさせていただいてます。

    markedでのマークダウンが使えません。

    本家を参照すると、markedではなくremarkableがインポートされています。

    markdownの下りでも
    var md = new Remarkable();
    とmd.render();
    が使用されています。

    kyane  返信

    • kyaneさん

      こちらもご指摘ありがとうございます。内容が若干変更されていましたね。

      さっそく変更点を反映しました。その他何かありましたら、お知らせください。

      今後ともよろしくお願いいたします。

      Takanori Maeda  返信

  2. 参考にさせていただいています。ありがとうございます。

    // tutorial8.js
    var data = [
    {id: 1, author: “Pete Hunt”, text: “This is one comment”},
    {id: 2 author: “Jordan Walke”, text: “This is *another* comment”}
    ];

    どうも、id: 2のあとのコンマが抜けてしまっているようです。

    hirotaka  返信

    • hirotakaさん

      コメントありがとうございます。ご指摘の箇所直しました。

      今後ともよろしくお願いいたします。

      Takanori Maeda  返信

  3. ピンバック: Gunma.web #27 開催報告 | 群馬のweb系勉強会&コミュニティ | Gunma.web

     

  • 必須

コメント