LoginSignup
72
70

More than 3 years have passed since last update.

React公式サイト日本語訳「主な概念」編  〜公式入門教科書〜

Last updated at Posted at 2018-11-20

2019.5追記
:flag_jp: https://ja.reactjs.org/ :flag_jp:
↑React公式サイト全体が和訳されました:sunny: この記事より↑をご覧いただくのが便利です!


前置き

本記事はReact公式ドキュメントの「基礎概念」編の和訳です。長いです。

React公式ドキュメントは、Reactが初めての入門者に対して、以下2つのドキュメントから読者の好みで選んで読み始めるよう促しています。

場所 「こんな人におすすめ」 URL
Tutorial 手を動かして学びたい人へ https://reactjs.org/tutorial/tutorial.html
Docs > Main Concepts (主な概念) 概念を1つ1つ学びたい人へ https://reactjs.org/docs/hello-world.html から

Tutorialはコードを真似しながら3目並べゲームを作る内容で、既に多くの和訳記事があります。

この記事で訳したMain Concepts編はかなりの長文であまり訳されていないようですが、
Reactの基礎をやさしく、一歩一歩説明していく素晴らしい教科書になっています。

例えば以下の概念がJavaScriptの基礎だけを前提知識に説明されています。

  • JSX
  • コンポーネント
  • props
  • state
  • Reactでのイベントハンドリング

以下はその和訳(第1章ー第8章)です。英文へのリンクを除き、筆者の理解の限りそのままの内容です。

1.Hello World

最小のReactアプリケーションは、このような見た目をしています。

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);'

CodePenで試す

“Hello, world!”という見出しがページに表示されます。

上のリンクをクリックし、オンラインのエディターを開いてください。自由に編集して、出力がどのように変わるのかを見てみましょう。このガイドの多くのページには、このような編集可能な例があります。

このガイドの読み方

このガイドでは、Reactアプリケーションを組み立てる積み木となる、「element(※)」と「コンポーネント」について考えていきます。この2つをマスターすれば、再利用可能な部品から複雜なアプリケーションを作ることができるようになります。

※訳注:React elementと呼ばれる特別なオブジェクトのこと

ポイント
このガイドは概念を1つ1つ学んでいきたい人向けに書かれています。手を動かして学ぶほうが良ければ、実践的なチュートリアルをご覧ください。このガイドとチュートリアルはお互いへの補足になるかもしれません。

前提知識

ReactはJavascriptのライブラリですから、Javascript言語の基礎的な知識は前提となります。あまり自信がなければ、MDNのJavascriptチュートリアルに挑戦して知識レベルを測ることをおすすめします。そうすれば、このガイドで迷子になることもありません。30分〜1時間ほどかかるかもしれませんが、結果的にJavascriptとReactを同時に学ばなくてすみます。

注意
このガイドではあちこちでJavascriptの新しい文法を使っています。ここ数年間Javascriptを使っていなくても、この3点が判れば大体乗り切れます。

さあ、始めましょう!

2.JSXの紹介

この変数宣言について考えてみてください

const element = <h1>Hello, world!</h1>;

この不思議なタグの記法は、文字列でもなければ、HTMLでもありません。

これは「JSX」と呼ばれるもので、Javascriptの拡張文法です。UIの見た目を表現するときには、Reactと一緒にJSXを使うことをおすすめします。テンプレート言語を思わせる見た目ですが、JSXはJavascriptの力を完全に受け継いでいます。

JSXは「React element」を生成します。そのReact elementをDOMにレンダリングする件は次章で扱うとして、以下ではまずJSXの基本知識を解説します。

なぜJSX?

レンダリングのロジックは、UIについての他のロジック...―イベント・ハンドリング、時間経過による状態の変化、表示のためのデータ整形―...と本質的に対になるものです。Reactはこの考え方を取り入れています。

Reactは、マークアップとロジックを別々のファイルに書いて人為的に技術の領域を分離することはしません
代わりにReactは、その両方を持つ「コンポーネント」という単位で関心を分離します。この先の章でコンポーネントを扱いますが、JSファイルにマークアップを書くことにまだ抵抗があれば、こちらのプレゼンがあなたを説得できるかもしれません。

ReactにJSXが必ず必要というわけではありませんが、Javascriptの中でUI作る場合、多くの人はJSXが視覚的に使いやすいと感じます。さらに、JSXを使うとReactのエラーメッセージがより分かりやすくなります。

JSXに式を入れる

下の例では、name変数を宣言し、波括弧{}で囲んでJSXの中で使っています。

const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;

ReactDOM.render(
  element,
  document.getElementById('root')
);

Javascriptとして有効な式なら何であれ、波括弧で囲んでJSXの中で使えます。例えば2 + 2,user.firstName,formatName(user)はすべて有効なJavascriptの式です。

下の例では、<h1>要素にJavascript関数の呼び出し結果formatName(user)を埋め込んでいます。

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
    Hello, {formatName(user)}!
  </h1>
);

ReactDOM.render(
  element,
  document.getElementById('root')
);

CodePenで試す

見やすいようJSXは改行しています。また、文法上必須ではありませんが、「自動セミコロン」の混乱を避けるために常にJSXをカッコ()で囲むのがオススメです。

JSXも式の一種

コンパイルされた後、JSXは通常のJavasciptの関数呼び出しになり、Javascriptオブジェクトとして評価されます。

そのためJSXは、if文やforループの中に書いたり、変数に入れたり、引数として渡したり、関数から返すことができます。

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

JSXで属性を指定する

属性に文字列を設定するには、クォートを使えます。

const element = <div tabIndex="0"></div>;

属性にJavascriptの式を設定するには、波括弧を使えます。

const element = <img src={user.avatarUrl}></img>;

式を設定する時にクォートはいりません。文字列ならクォート、式なら波括弧と、どちらかを使います。

注意
JSXはHTMLよりJavaScriptに近いので、ReactDOM(※)はHTML属性名そのままではなく、キャメルケースの命名規則を用います。

例えば、classclassNameに、tabindextabIndexになります。
※訳注: JSXから(React elementを経由し)DOMを操作してくれるReactの機能

JSXで子要素を指定する

空要素の場合、XMLのように/>ですぐに閉じることができます。

const element = <img src={user.avatarUrl} />;

JSXのタグは子要素を内包できます。

const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);

JSXはインジェクション攻撃を防ぐ

JSXには安全にユーザーの入力を埋め込むことができます。

const title = response.potentiallyMaliciousInput;
// これも安全:
const element = <h1>{title}</h1>;

デフォルトで、ReactDOMはJSXに埋め込まれた全ての変数をレンダリング前にエスケープします。そのため、アプリに明示的に書かれた以外のコードをインジェクトされる心配はありません。レンダリングされる前に全ては文字列に変換されます。この性質はXSS(クロスサイトスクリプティング)の防止に役立ちます。

JSXはオブジェクトを表現する

BabelはJSXをReact.createElement()の呼び出しに変換します。

以下2つの例は等価です。

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

React.createElement() はバグを防ぐためにいくつかチェック機構を持っていますが、本質的には以下のようなオブジェクトを生成しています。

// 注意: これは単純化された構造です
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

これらのオブジェクトは「React element」と呼ばれています。画面に表示したいモノについての説明文だと思えば良いでしょう。Reactはこれらのオブジェクトを読み込み、DOMを生成・更新するために利用します。

次のセクションでは、React elementsのDOMへのレンダリングについて説明します。

ポイント
お使いのエディタの言語定義は“Babel”にすると、ES6とJSXのコードが正しくシンタックス・ハイライトされるのでオススメです。このサイトはBabelと互換性のあるOceanic Nextというカラースキームを使っています。

3.React elementのレンダリング

ElementはReactアプリケーションを組み上げる一番小さい積み木です

1つ1つのelementは、それぞれが画面に表示されたいモノを説明しています。

const element = <h1>Hello, world</h1>;

ブラウザのDOM要素と違って、React elementはただのJavaScriptオブジェクトで、軽量に生成できます。あとは、DOMがReact elementの状態通りになるよう、React DOMがDOMを更新してくれます。

注意
elementは、よりよく知られた概念である「コンポーネント」と混同されがちです。次の章でコンポーネントを紹介しますが、elementはコンポーネントを作る部品です。飛ばさずにこの章も読むことをオススメします。

elementをDOMにレンダリングする

例えばHTMLのどこかに<div>があるとしましょう。

<div id="root"></div>

この要素配下の全要素をReact DOMで管理するという意味で、この要素を「ルート DOMノード(root DOM node)」と呼びます。

Reactだけで作られたアプリケーションには、通常1つのルートDOMノードがあります。既存のアプリケーションにReactを導入する場合は、何個でも互いに独立したルートDOMノードを作って構いません。

React elementをルートDOMノードの中にレンダリングするには、双方をReactDOM.render()に渡してください。

const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));

CodePenで試す

ページに“Hello, world”が表示されます。

レンダリング済みの要素を更新する

React elementはイミュータブル(immutable=不変)です。後から子要素や属性を変えることはできません。elementは映画の1フレームのようなもので、ある瞬間のUIの状態を表します。

ここまでの知識でUIを更新するには、新しいelementを作ってもう一度ReactDOM.render()に渡すしかありません。

以下のチクタク時計のコードを見てください。

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(element, document.getElementById('root'));
}

setInterval(tick, 1000);

CodePenで試す

ReactDOM.render()setInterval()から1秒ごとに呼び出しています。

注意
実用上は、多くのReactアプリケーションでReactDOM.render()を呼び出すのは一度だけです。以降の章では、「状態を持つコンポーネント(stateful components)」について学んでいきます。
それぞれの話題はお互いの上に成り立っているので、ぜひ飛ばさずに読んでください。

Reactは必要なものだけを更新する

React DOMは、

  • 現在のelementとその子要素
  • 以前のelementとその子要素

を比較し、必要なDOM更新だけを行います。

先ほどのチクタク時計をブラウザのツールで見れば、そのことが確認できます。

granular-dom-updates-c158617ed7cc0eac8f58330e49e48224.gif

UI全体を表すelementを毎秒生成しているにもかかわらず、React DOMが更新しているのは内容が変わったテキストノードだけです。

私達の経験上、「何を変更するか」を考えるよりも「ある瞬間にUIがどう見えるべきか」を考えるほうが、遥かにバグが少なくなります。

4.コンポーネントとprops

コンポーネントを使えば、UIを独立した、再利用可能な部品に分け、それぞれを独立したものとして考えることができます。このページではコンポーネントという概念を紹介します。詳しいAPIリファレンスはこちら

概念的には、コンポーネントはJavaScriptの関数のようなものです。propsと呼ばれる引数を受け付け、画面に表示させるべきものを説明したReact elementを返します。

functionコンポーネントとClassコンポーネント

コンポーネントを定義する一番単純な方法は、JavaScriptの関数を書くことです。

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

この関数は、データの入った1つのprops(プロパティの略)オブジェクトを受け取り、React elementを返しているので、有効なReactコンポーネントです。文字通りJavaScriptの関数なので、こうしたコンポーネントを「functionコンポーネント」と呼びます。

また、ES6のClass記法を使ってコンポーネントを定義することも可能です。

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

以上2つのコンポーネントは、Reactからすれば等価です。

Classコンポーネントはいくつか追加の機能を持っていますが、それは次の章で扱います。それまでは簡潔なfuntionコンポーネントを使っていきましょう。

コンポーネントをレンダリングする

ここまで、私たちはHTMLのタグを表すReact elementだけを扱ってきました。

const element = <div />;

しかし、elementでユーザーが定義したコンポーネントを表すことも可能です。

const element = <Welcome name="Sara" />;

Reactがユーザーが定義したコンポーネントを見つけると、JSXに書かれた全属性を1つのオブジェクトとしてコンポーネントに渡します。これを「props」と呼びます。

例えば、以下のコードは “Hello, Sara”とページに表示します。

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

CodePenで試す

この例で何が起きているのか、おさらいしましょう。

  1. このコードは<Welcome name="Sara" />elementを渡してReactDOM.render()を呼び出す
  2. ReactはWelcomeコンポーネントに{name: 'Sara'}をpropsとして渡して呼び出す
  3. Welcomeコンポーネントは<h1>Hello, Sara</h1>element を返す
  4. React DOMは返ってきた<h1>Hello, Sara</h1>と合致するようDOMを更新する

注意:コンポーネント名は常に大文字から始める
Reactは小文字から始まるコンポーネントをDOMタグとして扱います。例えば<div>はHTMLのdivタグを示しますが、<Welcome />はコンポーネントを示し、スコープ内にWelcomeがなければいけません。

コンポーネントを作る

コンポーネントは出力の中で他のコンポーネントを呼び出すことができます。そのため、様々な詳細度のコンポーネントを抽象化することができます。ボタンも、フォームも、ダイアログも、画面全体も、Reactアプリーケーションではコンポーネントとして表現されます。

例えば、Welcomeを何度もレンダリングするAppコンポーネントを作ってみましょう。

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

CodePenで試す

新しいReactアプリケーションを作る場合、典型的には最上層に1つの<App>コンポーネントを持たせます。既存のアプリケーションに導入する場合、ボタンのような小さなコンポーネントから始め、上層へ上層へとReactの範囲を広げても良いでしょう。

コンポーネントを分割/抽出する

コンポーネントの分割を恐れないでください。

例えば、この<Comment>コンポーネントを考えてみましょう。

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

CodePenで試す

propsとしてauthor(オブジェクト)、text(文字列)、date(dateオブジェクト)を受け取り、SNSのコメント欄を表現しています。

ネストが深く、各部分を再利用することも難しいので、扱いづらい状態です。ここからいくつかコンポーネントを抽出してみましょう。

まず、Avatarを抽出します。

function Avatar(props) {
  return (
    <img className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}

Avatarは自分がCommentの中にレンダリングされていることを知る必要はありません。そのため、propsにはより一般的な名前をつけました(author⇨user)。

propsは、コンポーネントを呼び出す時の文脈からではなく、コンポーネント自体の視点から命名することをおすすめします。

これで少しだけCommentをシンプルにできます。

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <Avatar user={props.author} />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

次に、ユーザー名の隣にAvatarをレンダリングするUserInfoコンポーネントを抽出します。

function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">
        {props.user.name}
      </div>
    </div>
  );
}

これでCommentをもう一歩シンプルにできます。

function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

CodePenで試す

最初はコンポーネントの抽出を面倒な作業に感じるかもしれません。しかし、大規模なアプリケーションにおいては再利用できるコンポーネントの道具箱を持つことにそれだけの価値があります。経験則として、UIの一部が数回以上使われる(ButtonPanelAvatar...)か、それ単体で十分に複雜(AppFeedStoryComment...)な場合、再利用可能なコンポーネントに変える候補になるでしょう。

propsは読み取り専用

functionコンポーネントにせよClassコンポーネントにせよ、自身のpropsを変更してはいけません。このSum関数を考えてみましょう。

function sum(a, b) {
  return a + b;
}

このような関数は、入力を変更せず、同じ入力に対してはいつも同じ結果を返すため、「pure」なコンポーネントと呼ばれます。

対照的に、この関数は自分への入力を変更してしまうため「pure」ではありません。

function withdraw(account, amount) {
  account.total -= amount;
}

Reactはかなり柔軟ですが、1つだけ厳格なルールを持っています。

全てのReactコンポーネントは、propsに対して「pure」でなければならない

もちろん、アプリケーションのUIは動的であり、時間とともに変化します。次の章では、「state」(状態)という新しい概念を紹介します。stateの働きで、Reactコンポーネントは上記のルールを破ることなく、ユーザーの行動・ネットワークのレスポンスなど色々な呼び出しに対して、場合によって異なる表示を返すことができます。

5.stateとライフサイクル

この章はReactコンポーネントの「state」と「ライフサイクル」という概念を説明します。詳しいAPIリファレンスはこちら

過去の章で登場したチクタク時計の例を考えてください。UIを更新する方法は1つだけでした。すなわち、ReactDOM.render()を呼んで以前と違う出力をさせることです。

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

CodePenで試す

この章では、このClockコンポーネントを本当の意味で再利用可能でカプセル化された状態にする方法を学びます。進化した後のClockは、自分でタイマーを設定し、一秒ごとに自身を更新するようになります。

はじめに、時計の見た目をカプセル化してみましょう。

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

CodePenで試す

しかし、これではある決定的な要求が満たされていません。それは、タイマーを持ち、毎秒UIを更新するという動きをClockの内部に実装したい、というものです。

理想的には、以下のコードを1度書くだけでClockが自分で動き始めるようにしたいはずです。

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

これを実装するには、Clockコンポーネントに「state」を足す必要があります。

stateはpropsに似ていますが、プライベート変数で、完全にコンポーネントによってコントロールされています。

以前の章で「Classコンポーネントには追加機能がある」お話をしましたが、この「state」がそれです。

functionをClassに書き換える

ClockのようなfunctionコンポーネントをClassコンポーネントに変えるには、以下の5ステップが必要です。

1. 元の関数と同名のES6のclassを作り、React.Componentを継承させる
2. render()という名前の空のメソッドを足す
3. 元の関数の中身をrender()メソッドの中に移動する
4. render()内のpropsthis.propsに書き換える
5. 空になった関数宣言を削除する

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

CodePenで試す

これでClockはclassとして定義されました。

renderメソッドは更新が起こるたびに呼び出されますが、Clockが同じDOMノードにレンダリングされている限り、使われるClockインスタンスは1つです。stateやライフサイクル・メソッドなどの追加機能が使えるのはそのためです。

Classにstateを足す

以下の3ステップでdateをpropsからstateに移動します。

  1. render()メソッドのthis.props.datethis.state.dateに変える
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
  1. this.stateを初期化するコンストラクタを追加する
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

親クラスのコンストラクタにpropsを渡している点に注意してください。classコンポーネントでは常にこの処理も書くべきです。

3. <Clock /> elementから date属性を削除する

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

CodePenで試す

次に、Clockが自身でタイマーをセットし自身を更新できるようにします。

Classにライフサイクル・メソッドを追加する

多数のコンポーネントを持つアプリケーションにとっては、破棄したコンポーネントが専有していたリソースを開放することが非常に重要です。

ClockがDOMに始めてレンダリングされた時にはタイマーがセットされるようにしたいとします。このタイミングは、Reactでは「マウント」と呼びます。

さらに、Clockに生成されたDOMが削除されたとき、そのタイマーを消去したいとします。このタイミングは、Reactでは「アンマウント」と呼びます。

コンポーネントがマウント、またはアンマウントされた時に何かのコードを実行したい場合、専用の名前をもったメソッドを定義します。

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {

  }

  componentWillUnmount() {

  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

これらのメソッドは「ライフサイクルメソッド」と呼ばれます。

componentDidMount()メソッドは、コンポーネントの出力がDOMにレンダリングされた時に実行されます。タイマーをセットするのにピッタリなタイミングです。

componentDidMount() {
  this.timerID = setInterval(
    () => this.tick(),
    1000
  );
}

thisに直接タイマーのIDを保存していることに注目してください。

this.propsはReactによって設定されますし、this.stateは特別な意味を持っています。が、データの流れに関与しない何か、例えばタイマーIDを保存するために他の変数を設定するのは自由です。

componentWillUnmount()メソッドでタイマーを破棄します。

componentWillUnmount() {
  clearInterval(this.timerID);
}

最後に、Clockコンポーネントが毎秒実行するtick()(※)メソッドを実装します。

※訳注 : 秒針がカチカチいう時の「カチッ」を表す英語

コンポーネントのstateを更新するため、tick()this.setState()を利用します。

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

CodePenで試す

これで、この時計は毎秒カチカチと進むようになりました。

何が起きていて、メソッドがどのような順番で実行されているのか、軽くおさらいしましょう。

  1. <Clock />ReactDOM.render()に渡されたとき、ReactはClockコンポーネントのコンストラクタを呼びます。Clockは現在の時間を表示する必要があるので、自分の状態this.stateを現在時間を含むオブジェクトで初期化します。このstateは後で更新されます。
  2. ReactはClockコンポーネントのrender()メソッドを呼びます。これによってReactは画面に表示すべきものを知ります。そのあと、ReactはClockのrender出力に合致するようDOMを更新します。
  3. Clockの出力がDOMに挿入されたとき、ReactはClockのcomponentDidMount()メソッドを呼びます。その中でClockコンポーネントはブラウザに対し、自身のtick()メソッドを毎秒呼び出すタイマーをセットするよう頼みます。
  4. ブラウザは一秒ごとにtick()を呼びます。Clockコンポーネントはtick()の中でsetState()に現在時刻を渡して呼び、this.dateを更新します。このsetState()呼出しのおかげて、Reactはstateが変わったことに気づき、render()を再度呼んで変更点を調べます。今回はrender()内でthis.state.dateが変化しているので、render出力も異なるものとなり、Reactはそれを反映させるべくDOMを更新します。
  5. もしClockコンポーネントがDOMから除去されれば、ReactはcomponentWillUnmount()メソッドを呼び出し、タイマーは止まります。

stateを正しく使う

以下はsetState()について知っておいて欲しいことです。

stateを直接変更してはいけない

例えば、これではコンポーネントは再描画されません。

// 間違い
this.state.comment = 'Hello';

代わりにsetState()を使ってください。

// 正解
this.setState({comment: 'Hello'});

this.stateに直接代入することが許されるのは、コンストラクタだけです。

stateの更新は非同期の場合がある

パフォーマンス向上のため、Reactは複数のsetState()呼び出しへの対応を一度のバッヂ処理で済ませることがあります。

this.propsthis.stateは非同期的に更新される場合もあるので、これらに依存して次の状態を計算するべきではありません。

例えば、以下のコードではカウンターの更新に失敗する可能性があります。

// 間違い
this.setState({
  counter: this.state.counter + this.props.increment,
});

修正するには、setState()の別の使い方を利用します。引数に関数を渡すと、その関数は以前のstateを第一引数に、更新時のpropsを第二引数にとって実行されます。

// 正解
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

上ではアロー関数を使いましたが、通常の関数でも動作します。

// 正解
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});

stateの更新はマージされる

setState()を呼んだとき、Reactは現在のstateと渡されたオブジェクトをマージします。

stateがいくつかの独立した変数を持っている場合を例にとりましょう。

constructor(props) {
  super(props);
  this.state = {
    posts: [],
    comments: []
  };
}

別々のsetState()呼び出しを使って、それぞれの変数を独立して更新することができます。

  componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

このマージはshallow(浅いマージ)なので、this.setState({comments})によって

  • this.state.postsは全く変化しない
  • this.state.commentsの中はまるごと入れ替わる

ことになります。

データは下に流れる

あるコンポーネントがステートフル(stateを持っている)かステートレスなのかは、その親コンポーネントにとっても子コンポーネントにとっても知りえないことですし、また自分の親や子がfunctionコンポーネントなのかClassコンポーネントなのかも気にするべきではありません。

これが、stateがしばしば「ローカル」だとか「カプセル化されている」と言われる理由です。stateはそれを保持しセットするコンポーネント自身以外からはアクセスできません。

コンポーネントは、自身のstateを子コンポーネントにpropsとして渡す場合があります。

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>

これはユーザーが定義したコンポーネントでも可能です。

<FormattedDate date={this.state.date} />

FormattedDateコンポーネントはdateをpropsとして受け取りますが、それが例えばClockのstateだったのか、Clockのpropsだったのか、直接JSXに書かれていたのかはわかりません。

function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

CodePenで試す

この流れはしばしば「トップダウン」とか「単方向の(unidirectional)」データフローと呼ばれます。全てのstateは特定のコンポーネントに属しており、そのstateから生成されたデータやUIは全て、その子孫のコンポーネントにしか影響を与えません。

コンポーネントのツリー構造を「propsが流れる滝」に例えるなら、各コンポーネントのstateは任意の点で合流する水源であり、合流地点から下流へと流れていきます。

全てのコンポーネントが本当に独立していることを示すために、3つの<Clock>をレンダリングするAppを作ってみましょう。

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

CodePenで試す

それぞれのClockは各自のタイマーを持ち、独立して自身を更新しています。

Reactアプリケーションでは、コンポーネントがステートフルかステートレスかはコンポーネントの実装の問題であり、変わっていくものです。ステートレスなコンポーネントの内部でステートフルなコンポーネントを利用することは可能ですし、逆もまた然りです。

6.イベントハンドリング

React elementでのイベントハンドリングは、DOMのそれとそっくりです。ただ、いくつか文法上の違いがあります。

  • Reactのイベントは「全て小文字」ではなくキャメルケースで命名されています
  • JSXでのイベントハンドラーは、文字列ではなく関数の形で渡します

例えば、HTMLでの以下の例が、

<button onclick="activateLasers()">
  Activate Lasers
</button>

Reactではこうなります。

<button onClick={activateLasers}>
  Activate Lasers
</button>

もう1つの違いは、falseを渡しても「デフォルトのハンドラを無効化」(prevent default)できないことです。そのためには、ハンドラーの中で明示的にpreventDefaultを呼びます。
例えば、Reactを使わない素のHTMLでリンクによる遷移を止めるときは、以下のように書けるでしょう。

<a href="#" onclick="console.log('The link was clicked.'); return false">
  Click me
</a>

Reactでは代わりに以下のように書きます。

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

eはシンセティック・イベント (Synthetic events※) です。ReactはW3Cの仕様に準拠してシンセティック・イベントを定義するので、ブラウザ互換性の心配はありません。詳しくはシンセティック・イベントについてのリファレンスをご覧ください。

※訳注 たぶんブラウザネイティブのイベントに対するReactのWrapper。W3Cの仕様通りの実装でないブラウザでも、仕様どおりの名前をセットすれば相当するイベントを内部的に呼んでくれる。

Reactでは基本的に、生成済みのDOMにaddEventListenerでリスナを登録するべきではありません。代わりに、要素が最初にレンダリングされる時にリスナを用意します。

コンポーネントをES6のClassで定義するときによくあるパターンは、イベントハンドラーをメソッドとして定義することです。例えばこのToggleコンポーネントはクリックで「ON」と「OFF」の表示を行き来します。

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // このbind()はコールバックでthisを動作させるために必要
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

CodePenで試す

JSXのコールバックにおけるthisの意味に気をつけてください。JavaScriptのデフォルトでは、クラスメソッドのthisは束縛されていません。this.handleClickをbindせずにonClickに渡すと、実際に関数が呼ばれた時にthisundefinedになります。

これはReactに限った挙動ではなく、JavaScriptの関数の挙動です。onClick={this.handleClick}のように()を後ろにつけずに関数に言及するときは、一般的にbindしたほうが良いでしょう。

bindを呼ぶ作業にイラっとするあなたには、2つの回避法があります。
実験的なpublic class fields syntaxで書くなら、class fieldsを使って正しくコールバックをbindできます。

class LoggingButton extends React.Component {
  // この構文でthisを確実にhandleClickに束縛できます
  // 警告: これは実験的な文法です
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

この構文はCreate React Appではデフォルトで有効です。

また、コールバックでアロー関数を使うことも有効です。

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // この構文でthisを確実にhandleClickに束縛できます
    return (
      <button onClick={(e) => this.handleClick(e)}>
        Click me
      </button>
    );
  }
}

この構文の問題は、LoggingButtonがレンダリングされるたびに別々のコールバックが作り直されるという点です。多くのケースでは問題ありませんが、このコールバックがpropsとして下位のコンポーネントに渡されている場合、余計な再レンダリングを招くかもしれません。このパフォーマンスの問題を避けるために、一般的にはclass fields syntaxを使うことをお勧めします。

イベントハンドラに引数を渡す

ループの中ではイベントハンドラーに追加の引数を渡すことがよくあります。例えばidが生のIDを指すなら、以下のどちらでも動作します。

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

上の2行はそれぞれアロー関数とFunction.prototype.bindを使っていて、等価です。

どちらのケースでも、Reactイベントを表す引数eはIDの次の第二引数として渡されています。アロー関数では明示的に渡す必要がありますが、bindでは追加の引数は自動的に渡されます。

7.条件付きレンダリング

Reactでは、望む振る舞いをカプセル化した状態で保持するコンポーネントを作ることができます。そして、その振る舞いの中からアプリケーションの状態によって必要なものだけを実際にレンダリングできます。

Reactでの条件付きレンダリング(Conditional rendering)は、JavaScriptの条件式と同じように動作します。JavaScriptのifや条件演算子を使ってその瞬間の状態に応じたelementを作り、ReactにUIを更新させてください。

下の2つのコンポーネントを考えてみましょう。

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

加えてGreetingコンポーネントを作成しましょう。ユーザーがログインしているかによって、上2つのコンポーネントのうちどちらかを表示します。

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

ReactDOM.render(
  // isLoggedIn={true}に変えて試してみてください
  <Greeting isLoggedIn={false} />,
  document.getElementById('root')
);

CodePenで試す

この例ではisLoggedInpropsの値によって異なる挨拶をレンダリングします。

element変数

変数にelementを格納することも可能です。これにより、他の部分の出力を変えないままコンポーネントの一部を条件的にレンダリングすることが可能です。

ログインボタンとログアウトボタンを表す2つのコンポーネントを考えてみましょう。

function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      Login
    </button>
  );
}

function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      Logout
    </button>
  );
}

下の例では、LoginControlというステートフルなコンポーネントを作ります。

LoginControlは自身の現在のstateによって<LoginButton /><LogoutButton />を表示し、また先程の例の<Greeting />を表示します。

class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
  }

  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }

  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }

  render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;

    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}

ReactDOM.render(
  <LoginControl />,
  document.getElementById('root')
);

CodePenで試す

このように変数を宣言しif文を書いて条件付きレンダリングを行っても良いですが、より短い構文を使いたいこともあるでしょう。以下でJSXにインラインで条件を設定する方法を説明します。

 &&論理演算子を用いたインラインのif文

JSXにはどんな式でも波括弧で囲んで埋め込むことができます。&&論理演算子も同様です。条件付きでelementを挿入する場合に便利です。

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);

CodePenで試す

これが動作するのは、true && (式)は常に(式)として評価され、false && (式)は常にfalseとして評価されるからです。

そのため、条件式がtrueなら、&&直後のelementは出力に現れます。falseなら、Reactは無視します。

インラインのif-else + 三項演算子

条件付きのレンダリングをinlineで行うもう一つの方法は、JavaSciptの三項演算子条件 ? true : falseを使うことです。

以下の例では、条件付きで小さなテキストブロックを表示しています。

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
    </div>
  );
}

より大きな式で使うことも可能ですが、何が起きているのかは分かりにくくなります。

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? (
        <LogoutButton onClick={this.handleLogoutClick} />
      ) : (
        <LoginButton onClick={this.handleLoginClick} />
      )}
    </div>
  );
}

JavaScriptの場合と同様、自分のチームにとっての読みやすさを考え、適切なスタイルを決めるのはあなたです。ただ、条件式が複雜になりすぎたら、コンポーネントを分割するタイミングかもしれないことを覚えておきましょう。

コンポーネントのレンダリングを止める

稀に、他のコンポーネントによってrenderされたコンポーネントに、自分自身を隠して欲しい場合があります。これを実現するには、レンダリング出力の代わりにnullを返します。

以下の例で、<WarningBanner />warnというpropsの値に従ってレンダリングされます。warnfalseなら、コンポーネントはレンダリングされません。

function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

  return (
    <div className="warning">
      Warning!
    </div>
  );
}

class Page extends React.Component {
  constructor(props) {
    super(props);
    this.state = {showWarning: true};
    this.handleToggleClick = this.handleToggleClick.bind(this);
  }

  handleToggleClick() {
    this.setState(state => ({
      showWarning: !state.showWarning
    }));
  }

  render() {
    return (
      <div>
        <WarningBanner warn={this.state.showWarning} />
        <button onClick={this.handleToggleClick}>
          {this.state.showWarning ? 'Hide' : 'Show'}
        </button>
      </div>
    );
  }
}

ReactDOM.render(
  <Page />,
  document.getElementById('root')
);

CodePenで試す

コンポーネントのrenderメソッドがnullを返しても、そのコンポーネントのライフサイクルメソッドの発火には影響がありません。例えばcomponentDidUpdateは呼ばれます。

8.リストとキー

訳注:本章の「リスト」は単に複数のモノが並んだ集合という意味の英語で、特定の機能や関数を指していません。

はじめに、JavaScriptでリストを編集する方法をおさらいしましょう

以下のコードでは、配列numbersmap()関数に渡して値を2倍にし、doubledに格納してからログに出力しています。

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);

このコードはコンソールに[2, 4, 6, 8, 10]を表示します。

Reactで単なる配列をelementのリストに変換する方法は、これに良く似ています。

 複数のコンポーネントをレンダリングする

elementの集合も、JSXに波括弧{}で埋め込むことでビルドすることができます。

以下では、numbers配列をJavaScriptのmap()関数を使ってループさせています。配列の1要素につき1つの<li>要素を帰ります。最後に、出来上がったelementの配列をlistItemsに代入します。

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li>{number}</li>
);

<ul>の中にlistItems配列全体を埋め込み、DOMにレンダリングします。

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);

CodePenで試す

このコードは箇条書きのリストで1-5までの数字を表示します。

基本的なリストコンポーネント

通常、リストはコンポーネントの中でレンダリングしたいことが多いでしょう。
先程の例は、配列numbersを受け取って順番なしのelementのリストを出力する形にリファクタリングできます。

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li>{number}</li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

このコードを実行すると、list itemsにはkeyが必要だという警告が表示されます。「key」とは、elementのリストを作る時に含めなければならない特別な文字列です。なぜこれが重要なのかは、次の章で扱います。

numbers.map()の中でlistItemsにkeyを埋め込んで、この問題を解決しましょう。

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

CodePenで試す

key

keyはReactが変更されたまたは削除された要素を特定するのを助けます。elementの安定したIDとして機能させるため、keyは配列の内部で渡すべきです。

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>
    {number}
  </li>
);

要素のkeyを選ぶ一番良い方法は、兄弟要素と区別できるユニークな文字列を使うことです。データの内容をkeyとして使うことが多いでしょう。

const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);

信頼できるIDとして使えるものがないときは、最後の手段としてインデックスを使っても良いでしょう。

const todoItems = todos.map((todo, index) =>
  // リストの要素から安定したIDが取れない場合だけ利用
  <li key={index}>
    {todo.text}
  </li>
);

リスト要素の順序が変わる可能性がある場合は、インデックスの利用はお勧めしません。パフォーマンスを低下させたり、コンポーネントの状態に問題を起こします。keyを設定しなければ、Reactはデフォルトとしてインデックスをkeyとして利用します。

keyを持つコンポーネントを抽出する

keyはそれを内包している配列の中でのみ意味を持ちます。

例えばListItemコンポーネントを抽出するなら、keyはListItem自体の中のliではなく、<ListItem />で設定するべきです。

function ListItem(props) {
  const value = props.value;
  return (
    // 間違い! keyはここで指定する必要はない
    <li key={value.toString()}>
      {value}
    </li>
  );
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 間違い! keyはここで指定されるべき
    <ListItem value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

正しい例は以下です

function ListItem(props) {
  // 正解! ここでkeyを指定する必要はない
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 正解! keyは配列の内部で指定するべき
    <ListItem key={number.toString()}
              value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

CodePenで試す

経験則として、map()関数呼び出しの中にあるelementはkeyを必要としていることが多くなります。
              

keyは兄弟要素の中で一意でなければならない

配列の中で使われるkeyは兄弟要素の中で一意である必要があります。しかし、グローバルに一意である必要はありません。2つの異なる配列で同じkeyを使うことは可能です。

function Blog(props) {
  const sidebar = (
    <ul>
      {props.posts.map((post) =>
        <li key={post.id}>
          {post.title}
        </li>
      )}
    </ul>
  );
  const content = props.posts.map((post) =>
    <div key={post.id}>
      <h3>{post.title}</h3>
      <p>{post.content}</p>
    </div>
  );
  return (
    <div>
      {sidebar}
      <hr />
      {content}
    </div>
  );
}

const posts = [
  {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
  {id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
  <Blog posts={posts} />,
  document.getElementById('root')
);

CodePenで試す

keyはReactにとってのヒントとなりますが、コンポーネントには渡されません。コンポーネント内でkeyと同じ値が必要であれば、明示的にpropsとして渡してください。

const content = posts.map((post) =>
  <Post
    key={post.id}
    id={post.id}
    title={post.title} />
);

上記の例では、Postコンポーネントはprops.idを読み取れますが、props.keyは読み取れません。

JSXに埋め込まれたmap()

上記の例では、listItems変数を個別に宣言し、JSXに埋め込みました。

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <ListItem key={number.toString()}
              value={number} />

  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

JSXでは波括弧の中に任意の式を埋め込めるので、map()をインラインで設定することも可能です。

function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) =>
        <ListItem key={number.toString()}
                  value={number} />

      )}
    </ul>
  );
}

これが明快なコードにつながることもありますが、使いすぎもありえます。JavaScriptと同じく、その変数を切り出すことでコードが読みやすくなるのかは自由に判断してください。ただし、map()の中のネストが深くなったら、コンポーネントを分割する良い機会かも知れません。

9.フォーム

内部的な状態を保持する必要性から、HTMLのform要素はReactにおいて他の要素と少し違った動きをします。例えば、素のHTMLで書いたこのフォームは1つの名前を受け取ります

<form>
  <label>
    Name:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Submit" />
</form>

ユーザーがフォームを送信したとき、このフォームはHTMLのデフォルト通り新しいページを表示します。同じ挙動をReactで実現したければ可能です。しかし多くの場合、フォームの送信を検知して、ユーザーが入力したデータにアクセスできるJavaScript関数がある方が便利でしょう。それを実現する標準的な方法は、「制御されたコンポーネント(controlled components)」と呼ばれるテクニックです。

制御されたコンポーネント

HTMLでは、<input><textarea><select>といったフォーム要素は、典型的には自身の状態を管理し、ユーザー入力に従って更新します。Reactでは、可変の状態は典型的にはコンポーネントのstateプロパティに格納され、setState()によってのみ更新されます。

Reactのstateを「唯一の信頼する情報源(single source of truth)」とすることで、この2つを合体させることができます。そうすれば、フォームをレンダリングするReactコンポーネントは後のユーザー入力への対応まで制御することができます。このような形で値をReactに制御されたinput要素が「制御されたコンポーネント」と呼ばれます。

TODO フォームの章を訳す

10.stateを上流へ伝える

同じ可変のデータを反映させるコンポーネントはしばしば数個になります。おすすめの方法は、共有するstateを一番近い共通の祖先まで上げる(Lifting up)ことです。実際の動きを見てみましょう。

この章では、特定の温度で水が沸騰するかを求める温度計算機を作ります。

まずはBoilingVerdictというコンポーネントから始めます。このコンポーネントはcelsius温度をpropsとして受け取り、水が沸騰するのに十分かどうかを出力します。

※訳注:BoilingVerdict=沸騰判定  celsius=摂氏

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}

次にCalculatorコンポーネントを作ります。このコンポーネントは、ユーザーが温度を入力する<input>要素をレンダリングし、値をthis.state.temperatureに格納します。加えて、現在の温度に基づいてBoilingVerdictをレンダリングします。

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    return (
      <fieldset>
        <legend>Enter temperature in Celsius:</legend>
        <input
          value={temperature}
          onChange={this.handleChange} />

        <BoilingVerdict
          celsius={parseFloat(temperature)} />

      </fieldset>
    );
  }
}

2つ目のinputを追加する

新しい要求が来ました。摂氏での入力に加えて華氏での入力も受け付け同期させたい、というものです。手始めに、CalculatorからTemperatureInputコンポーネントを抽出しましょう。cfの値を取るscale(単位)propsを追加します。

const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
};

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

これでCalculatorを2つの別々の温度入力欄をレンダリングするよう変更できます。

class Calculator extends React.Component {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />
      </div>
    );
  }
}

CodePenで試す

これで2つの入力欄ができましたが、片方に入力しても他方は更新されません。これでは要求と矛盾します。同期させたいのです。

また、CalculatorBoilingVerdictを表示できません。現在の温度はTemperatureInputの中に隠されているので、Calculatorは知らないのです。

変換関数を書く

まず、摂氏と華氏を相互に変換する2つの関数を書きます。

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

これら2つの関数は数を変換します。さらに、文字列temperatureを受け取る関数と、文字列を返す関数を作りましょう。一方の入力から他方の値を計算するのに使います。

無効なtemperatureを受け取ると空文字を返します。出力は小数点以下3位で切り捨てられます。

function tryConvert(temperature, convert) {
  const input = parseFloat(temperature);
  if (Number.isNaN(input)) {
    return '';
  }
  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000;
  return rounded.toString();
}

例えばtryConvert('abc', toCelsius)は空文字を返し、tryConvert('10.22', toFahrenheit)'50.396'を返します。

stateを上げる

現在、TemperatureInputコンポーネントはそれぞれ独立したローカルなstateに値を保持しています。

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    // ...

しかし、この2つは互いに同期して欲しいところです。摂氏の入力を変えると華氏のほうは変換された温度を示し、逆もそうなるように。

Reactでは、stateの共有はそれを必要とするコンポーネントの一番近い共通の祖先まで移動させることで実現します。これを「stateを上げる(lifting state up)」と呼びます。今回は、TemperatureInputのローカルなstateをCalculatorに移動します。

Calculatorが共通の状態を保持すれば、両方の入力欄にとっての「情報源(source of truth)」となります。このstateに、双方が同期した値を持つよう教えることができます。TemperatureInputのpropsは同じ親であるCalculatorコンポーネントから来ているので、2つの入力は常に同期されます。

どのように動作するか、一歩づつ見ていきましょう。

まず、TemperatureInputコンポーネントの中でthis.state.temperaturethis.props.temperatureに変更します。後ほどCalculatorから渡してやる必要はありますが、今はthis.props.temperatureが既にあるものとして扱いましょう。

  render() {
    // 改造前: const temperature = this.state.temperature;
    const temperature = this.props.temperature;
    // ...

ご存知の通り、porpsは読み取り専用です。temperatureがローカルステートだったときは、TemperatureInputは単にthis.setState()でそれを更新できました。今、temperatureは親からpropsとして渡されているので、TemperatureInputには操作できません。

Reactでは、通常これはコンポーネントを「制御された(controlled)」状態にすることで実現します。DOMの<input>valueonChangeの両方のpropsを受け付けるように、TemperatureInputtemperatureonTemperatureChangeCalculatorから受け取れるようにします。

こうすると、TemperatureInputが温度を更新したいときは、this.props.onTemperatureChangeを呼びます。

  handleChange(e) {
    // Before: this.setState({temperature: e.target.value});
    this.props.onTemperatureChange(e.target.value);
    // ...

この記事はReact公式サイト  https://reactjs.org/ の部分的な和訳です。
Copyright © 2018 Facebook Inc.

72
70
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
72
70