React公式ドキュメント和訳 「主な概念」編 〜React公式入門書〜
React公式ドキュメント和訳 「主な概念」編 〜React公式入門書〜:
React公式ドキュメントは、Reactが初めての入門者に対して、以下2つのドキュメントから読者の好みで選んで読み始めるよう促しています。
Tutorialはコードを真似しながら3目並べゲームを作る内容で、既に多くの和訳記事が出回っています。
対するMain Concepts編はかなりの長文であまり訳されていないようですが、
Reactの基礎をやさしく、一歩一歩説明していく素晴らしい教科書になっています。
例えば以下の概念がJavaScriptの基礎だけを前提知識に説明されています。
最小のReactアプリケーションは、このような見た目をしています。
CodePenで試す
“Hello, world!”という見出しがページに表示されます。
上のリンクをクリックし、オンラインのエディターを開いてください。自由に編集して、出力がどのように変わるのかを見てみましょう。このガイドの多くのページには、このような編集可能な例があります。
このガイドでは、Reactアプリケーションを組み上げる積み木となる、「element(※)」と「コンポーネント」について考えていきます。この2つをマスターすれば、再利用可能な部品から複雜なアプリケーションを作ることができるようになります。
※訳注 : React elementと呼ばれる特別なオブジェクトのこと
ReactはJavascriptのライブラリですから、Javascript言語の基礎的な知識は前提となります。あまり自信がなければ、MDNのJavascriptチュートリアルに挑戦して知識レベルを測ることをおすすめします。そうすれば、このガイドで迷子になることもありません。30分〜1時間ほどかかるかもしれませんが、結果的にJavascriptとReactを同時並行で学ばなくてすみます。
この変数宣言について考えてみてください
この不思議なタグの記法は、文字列でもなければ、HTMLでもありません。
これは「JSX」と呼ばれるもので、Javascriptの拡張文法です。UIの見た目を表現するときには、Reactと一緒にJSXを使うことをおすすめします。テンプレート言語を思わせる見た目ですが、JSXはJavascriptの力を完全に受け継いでいます。
JSXはReact「element」を生成します。そのReact elementをDOMにレンダリングする件は次章で扱うとして、以下ではまずJSXの基本知識を解説します。
レンダリングのロジックは、本質的に他のUIロジック...―イベント・ハンドリング、時間経過による状態の変化、表示のためのデータ整形―...と対になるものです。Reactはこの考え方を取り入れています。
Reactは、マークアップとロジックを別々のファイルに書いて人為的に技術の領域を分けることはしません。
代わりにReactは、その両方を持つ「コンポーネント」という単位で関心を分離します。この先の章でコンポーネントを扱いますが、JSファイルにマークアップを書くことにまだ抵抗があれば、こちらのプレゼンがあなたを説得できるかもしれません。
ReactにJSXが必ず必要というわけではありませんが、Javascriptの中でUI作る場合、多くの人はJSXが視覚的に使いやすいと感じます。さらに、JSXを使うとReactのエラーメッセージがより分かりやすくなります。
下の例では、
Javascriptとして有効な式なら何であれ、波括弧で囲んでJSXの中で使えます。例えば
下の例では、
CodePenで試す
見やすいようJSXは改行しています。また、文法上必須ではありませんが、「自動セミコロン」の混乱を避けるために常にJSXをカッコ
コンパイルされた後、JSXは通常のJavasciptの関数呼び出しになり、Javascriptオブジェクトとして評価されます。
そのためJSXは、if文やforループの中に書いたり、変数に入れたり、引数として渡したり、関数から返すことができます。
属性に文字列を設定するには、クォートを使えます。
属性にJavascriptの式を設定するには、波括弧を使えます。
式を設定する時にクォートはいりません。文字列ならクォート、式なら波括弧と、どちらかを使います。
空要素の場合、XMLのように
JSXのタグは子要素を内包できます。
JSXには安全にユーザーの入力を埋め込むことができます。
デフォルトで、ReactDOMはJSXに埋め込まれた全ての変数をレンダリング前にエスケープします。そのため、アプリに明示的に書かれた以外のコードをインジェクトされる心配はありません。レンダリングされる前に全ては文字列に変換されます。この性質はXSS(クロスサイトスクリプティング)の防止に役立ちます。
BabelはJSXを
以下2つの例は等価です。
これらのオブジェクトは「React element」と呼ばれています。画面に表示したいモノについての説明文だと思えば良いでしょう。Reactはこれらのオブジェクトを読み込み、DOMを生成・更新するために利用します。
次のセクションでは、React elementsのDOMへのレンダリングについて説明します。
ElementはReactアプリケーションを組み上げる一番小さい積み木です
1つ1つのelementは、それぞれが画面に表示されたいモノを説明しています。
ブラウザのDOM要素と違って、React elementはただのJavaScriptオブジェクトで、軽量に生成できます。あとは、DOMがReact elementの状態通りになるよう、React DOMがDOMを更新してくれます。
例えばHTMLのどこかに
この要素配下の全要素をReact DOMで管理するという意味で、この要素を「ルート DOMノード(root DOM node)」と呼びます。
Reactだけで作られたアプリケーションには、通常1つのルートDOMノードがあります。既存のアプリケーションにReactを導入する場合は、何個でも互いに独立したルートDOMノードを作って構いません。
React elementをルートDOMノードの中にレンダリングするには、双方を
CodePenで試す
ページに“Hello, world”が表示されます。
React elementはイミュータブル(immutable=不変)です。後から子要素や属性を変えることはできません。elementは映画の1フレームのようなもので、ある瞬間のUIの状態を表します。
ここまでの知識でUIを更新するには、新しいelementを作ってもう一度
以下のチクタク時計のコードを見てください。
CodePenで試す
React DOMは、
先ほどのチクタク時計をブラウザのツールで見れば、そのことが確認できます。
UI全体を表すelementを毎秒生成しているにもかかわらず、React DOMが更新しているのは内容が変わったテキストノードだけです。
私達の経験上、「何を変更するか」を考えるよりも「ある瞬間にUIがどう見えるべきか」を考えるほうが、遥かにバグが少なくなります。
コンポーネントを使えば、UIを独立した、再利用可能な部品に分け、それぞれを独立したものとして考えることができます。このページではコンポーネントという概念を紹介します。詳しいAPIリファレンスはこちら
概念的には、コンポーネントはJavaScriptの関数のようなものです。propsと呼ばれる引数を受け付け、画面に表示させるべきものを説明したReact elementを返します。
コンポーネントを定義する一番単純な方法は、JavaScriptの関数を書くことです。
この関数は、データの入った1つのprops(プロパティの略)オブジェクトを受け取り、React elementを返しているので、有効なReactコンポーネントです。文字通りJavaScriptの関数なので、こうしたコンポーネントを「functionコンポーネント」と呼びます。
また、ES6のClass記法を使ってコンポーネントを定義することも可能です。
以上2つのコンポーネントは、Reactからすれば等価です。
Classコンポーネントはいくつか追加の機能を持っていますが、それは次の章で扱います。それまでは簡潔なfuntionコンポーネントを使っていきましょう。
ここまで、私たちはHTMLのタグを表すReact elementだけを扱ってきました。
しかし、elementでユーザーが定義したコンポーネントを指定することも可能です。
ユーザーが定義したコンポーネントを見つけると、ReactはJSXの各属性を1つのオブジェクトとしてコンポーネントに渡します。これを「props」と呼びます。
訳注: 英語でこの変数をpropsと呼ぶのはもちろん、コンポーネント側の仮引数名も慣習的に
例えば、以下のコードは “Hello, Sara”とページに表示します。
CodePenで試す
この例で何が起きているのか、おさらいしましょう。
コンポーネントは出力で他のコンポーネントを呼び出すことができます。そのため、様々な詳細度のコンポーネントを同じように抽象化することができます。ボタンも、フォームも、ダイアログも、画面全体も、Reactアプリーケーションではコンポーネントとして表現されます。
例えば、
CodePenで試す
典型的には、新しいReactアプリケーションは最上層に1つの
コンポーネントの分割を怖がらないでください。
例えば、この
CodePenで試す
propsとして
ネストが深く、各部分を再利用することも難しいので、扱いづらい状態です。ここからいくつかコンポーネントを抽出してみましょう。
まず、
propsの名前は、コンポーネントを呼び出す時の状況からではなく、コンポーネント自体の視点から決めることをおすすめします。
これで少しだけ
次に、ユーザー名の隣に
これで
CodePenで試す
最初はコンポーネントの抽出を面倒な作業に感じるかもしれません。しかし、大規模なアプリケーションにおいて、再利用できるコンポーネントの道具箱を持つことにはそれだけの価値があります。経験則として、UIの一部が数回以上使われる(
functionコンポーネントにせよClassコンポーネントにせよ、自身のpropsを変更してはいけません。この
このような関数は、入力を変更せず、同じ入力に対してはいつも同じ結果を返すため、「pure」なコンポーネントと呼ばれます。
対照的に、この関数は自分への入力を変更してしまうため「pure」ではありません。
Reactはかなり柔軟ですが、1つだけ厳格なルールを持っています。
全てのReactコンポーネントは、propsに対して「pure」でなければならない
もちろん、アプリケーションのUIは動的であり、時間とともに変化します。次の章では、「state」(状態)という新しい概念を紹介します。stateの働きで、Reactコンポーネントは上記のルールを破ることなく、ユーザーの行動・ネットワークのレスポンスなど色々な呼び出しに対して場合により異なる表示を返すことができます。
この章はReactコンポーネントのstate(状態)とライフサイクルという概念を説明します。詳しいAPIリファレンスはこちら
過去の章で登場したチクタク時計の例を考えてください。UIを更新する方法は1つだけでした。すなわち、
CodePenで試す
この章では、この
はじめに、時計の見た目をカプセル化してみましょう。
CodePenで試す
しかし、これではある決定的な要求が満たされていません。それは、タイマーを持ち、毎秒UIを更新するという動きを
理想的には、以下のコードを1度書くだけで
これを実装するには、
stateはpropsに似ていますが、プライベート変数で、完全にコンポーネントによってコントロールされています。
以前の章で「Classコンポーネントには追加機能がある」お話をしましたが、この「state」がそれです。
1. 元の関数と同名のES6のclassを作り、
2.
3. 元の関数の中身を
4.
5. 空になった関数宣言を削除する
CodePenで試す
これで
以下の3ステップで
親クラスのコンストラクタにpropsを渡している点に注意してください。classコンポーネントでは常にこの処理も書くべきです。
3.
CodePenで試す
次に、
多数のコンポーネントを持つアプリケーションにとっては、破棄したコンポーネントが専有していたリソースを開放することが非常に重要です。
さらに、
コンポーネントがマウント、またはアンマウントされた時に何かのコードを実行したい場合、専用の名前をもったメソッドを定義します。
これらのメソッドは「ライフサイクルメソッド」と呼ばれます。
最後に、
※訳注 : 秒針がカチカチいう時の「カチッ」を表す英語
コンポーネントのstateを更新するため、
CodePenで試す
これで、この時計は毎秒カチカチと進むようになりました。
何が起きていて、メソッドがどのような順番で実行されているのか、軽くおさらいしましょう。
以下は
例えば、これではコンポーネントは再描画されません。
代わりに
パフォーマンス向上のため、Reactは複数の
例えば、以下のコードではカウンターの更新に失敗する可能性があります。
修正するには、
上ではアロー関数を使いましたが、通常の関数でも動作します。
stateがいくつかの独立した変数を持っている場合を例にとりましょう。
別々の
このマージはshallow(浅いマージ)なので、
あるコンポーネントがステートフル(stateを持っている)かステートレスなのかは、その親コンポーネントにとっても子コンポーネントにとっても知りえないことですし、また自分の親や子がfunctionコンポーネントなのかClassコンポーネントなのかも気にするべきではありません。
これが、stateがしばしば「ローカル」だとか「カプセル化されている」と言われる理由です。stateはそれを保持しセットするコンポーネント自身以外からはアクセスできません。
コンポーネントは、自身のstateを子コンポーネントにpropsとして渡す場合があります。
これはユーザーが定義したコンポーネントでも可能です。
CodePenで試す
この流れはしばしば「トップダウン」とか「単方向の(unidirectional)」データフローと呼ばれます。全てのstateは特定のコンポーネントに属しており、そのstateから生成されたデータやUIは全て、その子孫のコンポーネントにしか影響を与えません。
コンポーネントのツリー構造を「propsが流れる滝」に例えるなら、それぞれのコンポーネントのstateは任意の点で合流する水源であり、合流地点よりから下流に流れていきます。
全てのコンポーネントが本当に独立していることを示すために、3つの
CodePenで試す
それぞれの
Reactアプリケーションでは、コンポーネントがステートフルかステートレスかはコンポーネントの実装の問題であり、変わっていくものです。ステートレスなコンポーネントの内部でステートフルなコンポーネントを利用することは可能ですし、逆もまた然りです。
この記事はReact公式サイト https://reactjs.org/ の部分的な和訳です。
Copyright © 2018 Facebook Inc.
前置き
React公式ドキュメントは、Reactが初めての入門者に対して、以下2つのドキュメントから読者の好みで選んで読み始めるよう促しています。場所 | 「こんな人におすすめ」 | URL |
---|---|---|
Tutorial | 手を動かして学びたい人へ | https://reactjs.org/tutorial/tutorial.html |
Docs > Main Concepts (主な概念) | 概念を1つ1つ学びたい人へ | https://reactjs.org/docs/hello-world.html から |
対するMain Concepts編はかなりの長文であまり訳されていないようですが、
Reactの基礎をやさしく、一歩一歩説明していく素晴らしい教科書になっています。
例えば以下の概念がJavaScriptの基礎だけを前提知識に説明されています。
- JSX
- コンポーネント
- props
- state
- Reactでのイベントハンドリング
Hello World
最小のReactアプリケーションは、このような見た目をしています。ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('root') );'
“Hello, world!”という見出しがページに表示されます。
上のリンクをクリックし、オンラインのエディターを開いてください。自由に編集して、出力がどのように変わるのかを見てみましょう。このガイドの多くのページには、このような編集可能な例があります。
このガイドの読み方
このガイドでは、Reactアプリケーションを組み上げる積み木となる、「element(※)」と「コンポーネント」について考えていきます。この2つをマスターすれば、再利用可能な部品から複雜なアプリケーションを作ることができるようになります。※訳注 : React elementと呼ばれる特別なオブジェクトのこと
ポイント
このガイドは概念を1つ1つ学んでいきたい人向けに書かれています。手を動かして学ぶほうが良ければ、実践的なチュートリアルをご覧ください。このガイドとチュートリアルはお互いへの補足になるかもしれません。
前提知識
ReactはJavascriptのライブラリですから、Javascript言語の基礎的な知識は前提となります。あまり自信がなければ、MDNのJavascriptチュートリアルに挑戦して知識レベルを測ることをおすすめします。そうすれば、このガイドで迷子になることもありません。30分〜1時間ほどかかるかもしれませんが、結果的にJavascriptとReactを同時並行で学ばなくてすみます。注意さあ、始めましょう!
このガイドではあちこちでJavascriptの新しい文法を使っています。ここ数年間Javascriptを使っていなくても、この3点が判れば大体乗り切れます。
JSXの紹介
この変数宣言について考えてみてくださいconst element = <h1>Hello, world!</h1>;
これは「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') );
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') );
見やすいよう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>;
const element = <img src={user.avatarUrl}></img>;
注意
JSXはHTMLよりJavaScriptに近いので、ReactDOM(※)はHTML属性名そのままではなく、キャメルケースの命名規則を用います。
例えば、class
はclassName
に、tabindex
はtabIndex
になります。
※訳注: JSXから(React elementを経由し)DOMを操作してくれるReactの機能
JSXで子要素を指定する
空要素の場合、XMLのように/>
ですぐに閉じることができます。const element = <img src={user.avatarUrl} />;
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>;
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 elementsのDOMへのレンダリングについて説明します。
ポイント
お使いのエディタの言語定義は“Babel”にすると、ES6とJSXのコードが正しくシンタックス・ハイライトされるのでオススメです。このサイトはBabelと互換性のあるOceanic Nextというカラースキームを使っています。
React elementのレンダリング
ElementはReactアプリケーションを組み上げる一番小さい積み木です1つ1つのelementは、それぞれが画面に表示されたいモノを説明しています。
const element = <h1>Hello, world</h1>;
注意
elementは、よりよく知られた概念である「コンポーネント」と混同されがちです。次の章でコンポーネントを紹介しますが、elementはコンポーネントを作る部品です。飛ばさずにこの章も読むことをオススメします。
elementをDOMにレンダリングする
例えばHTMLのどこかに<div>
があるとしましょう。<div id="root"></div>
Reactだけで作られたアプリケーションには、通常1つのルートDOMノードがあります。既存のアプリケーションにReactを導入する場合は、何個でも互いに独立したルートDOMノードを作って構いません。
React elementをルートDOMノードの中にレンダリングするには、双方を
ReactDOM.render()
に渡してください。const element = <h1>Hello, world</h1>; ReactDOM.render(element, document.getElementById('root'));
ページに“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);
ReactDOM.render()
をsetInterval()
から1秒ごとに呼び出しています。注意
実用上は、多くのReactアプリケーションでReactDOM.render()
を呼び出すのは一度だけです。以降の章では、「状態を持つコンポーネント(stateful components)」について学んでいきます。
それぞれの話題はお互いの上に成り立っているので、ぜひ飛ばさずに読んでください。
Reactは必要なものだけを更新する
React DOMは、- 現在のelementとその子要素
- 以前のelementとその子要素
先ほどのチクタク時計をブラウザのツールで見れば、そのことが確認できます。
UI全体を表すelementを毎秒生成しているにもかかわらず、React DOMが更新しているのは内容が変わったテキストノードだけです。
私達の経験上、「何を変更するか」を考えるよりも「ある瞬間にUIがどう見えるべきか」を考えるほうが、遥かにバグが少なくなります。
コンポーネントとprops
コンポーネントを使えば、UIを独立した、再利用可能な部品に分け、それぞれを独立したものとして考えることができます。このページではコンポーネントという概念を紹介します。詳しいAPIリファレンスはこちら概念的には、コンポーネントはJavaScriptの関数のようなものです。propsと呼ばれる引数を受け付け、画面に表示させるべきものを説明したReact elementを返します。
functionコンポーネントとClassコンポーネント
コンポーネントを定義する一番単純な方法は、JavaScriptの関数を書くことです。function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
また、ES6のClass記法を使ってコンポーネントを定義することも可能です。
class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
Classコンポーネントはいくつか追加の機能を持っていますが、それは次の章で扱います。それまでは簡潔なfuntionコンポーネントを使っていきましょう。
コンポーネントをレンダリングする
ここまで、私たちはHTMLのタグを表すReact elementだけを扱ってきました。const element = <div />;
const element = <Welcome name="Sara" />;
訳注: 英語でこの変数をpropsと呼ぶのはもちろん、コンポーネント側の仮引数名も慣習的に
props
とします。例えば、以下のコードは “Hello, Sara”とページに表示します。
function Welcome(props) { return <h1>Hello, {props.name}</h1>; } const element = <Welcome name="Sara" />; ReactDOM.render( element, document.getElementById('root') );
この例で何が起きているのか、おさらいしましょう。
- このコードは
<Welcome name="Sara" />
elementを渡してReactDOM.render()
を呼び出す - Reactは
Welcome
コンポーネントに{name: 'Sara'}
をpropsとして渡して呼び出す -
Welcome
コンポーネントは<h1>Hello, Sara</h1>
element を返す - 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') );
典型的には、新しい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> ); }
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> ); }
最初はコンポーネントの抽出を面倒な作業に感じるかもしれません。しかし、大規模なアプリケーションにおいて、再利用できるコンポーネントの道具箱を持つことにはそれだけの価値があります。経験則として、UIの一部が数回以上使われる(
Button
、Panel
、Avatar
...)か、それ単体で十分に複雜(App
、 FeedStory
、Comment
...)な場合、再利用可能コンポーネントにする候補になるでしょう。
propsは読み取り専用
functionコンポーネントにせよClassコンポーネントにせよ、自身のpropsを変更してはいけません。このSum
関数を考えてみましょう。function sum(a, b) { return a + b; }
対照的に、この関数は自分への入力を変更してしまうため「pure」ではありません。
function withdraw(account, amount) { account.total -= amount; }
全てのReactコンポーネントは、propsに対して「pure」でなければならない
もちろん、アプリケーションのUIは動的であり、時間とともに変化します。次の章では、「state」(状態)という新しい概念を紹介します。stateの働きで、Reactコンポーネントは上記のルールを破ることなく、ユーザーの行動・ネットワークのレスポンスなど色々な呼び出しに対して場合により異なる表示を返すことができます。
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);
この章では、この
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);
しかし、これではある決定的な要求が満たされていません。それは、タイマーを持ち、毎秒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()
内のprops
をthis.props
に書き換える5. 空になった関数宣言を削除する
class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.props.date.toLocaleTimeString()}.</h2> </div> ); } }
これで
Clock
はclassとして定義されました。render
メソッドは更新が起こるたびに呼び出されますが、Clock
が同じDOMノードにレンダリングされている限り、使われるClock
インスタンスは1つです。stateやライフサイクル・メソッドなどの追加機能が使えるのはそのためです。
Classにstateを足す
以下の3ステップでdate
をpropsからstateに移動します。-
render()
メソッドのthis.props.date
をthis.state.date
に変える
class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
-
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> ); } }
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') );
次に、
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') );
これで、この時計は毎秒カチカチと進むようになりました。
何が起きていて、メソッドがどのような順番で実行されているのか、軽くおさらいしましょう。
-
<Clock />
がReactDOM.render()
に渡されたとき、ReactはClock
コンポーネントのコンストラクタを呼びます。Clock
は現在の時間を表示する必要があるので、this.state
を現在時間を含むオブジェクトで初期化します。このstateは後で更新されます。 - Reactは
Clock
コンポーネントのrender()
メソッドを呼びます。これによってReactは画面に表示すべきものを知ります。そのあと、ReactはClock
のrender出力に合致するようDOMを更新します。 -
Clock
の出力がDOMに挿入されたとき、ReactはClockのcomponentDidMount()
メソッドを呼びます。その中でClock
コンポーネントはブラウザに対し、自身のtick()
メソッドを毎秒呼び出すタイマーをセットするよう頼みます。 - ブラウザは一秒ごとに
tick()
を呼びます。Clock
コンポーネントはtick()
の中でsetState()
に現在時刻を渡して呼び、this.date
を更新します。このsetState()呼出しのおかげて、Reactはstateが変わったことに気づき、render()を再度呼んで何が変わったのかを調べます。今回はrender()
内でthis.state.date
が変化しているので、render出力も異なるものとなり、Reactはそれを反映させるべくDOMを更新します。 - もし
Clock
コンポーネントがDOMから除去されれば、ReactはcomponentWillUnmount()
メソッドを呼び出し、タイマーは止まります。
stateを正しく使う
以下はsetState()
について知っておいて欲しいことです。
stateを直接変更してはいけない
例えば、これではコンポーネントは再描画されません。// 間違い this.state.comment = 'Hello';
setState()
を使ってください。// 正解 this.setState({comment: 'Hello'});
this.state
に何かを代入してよいのはコンストラクタだけです。
stateの更新は非同期の場合がある
パフォーマンス向上のため、Reactは複数のsetState()
呼び出しへの対応を一度のバッヂ処理で済ませることがあります。this.props
とthis.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 }); }); }
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>; }
この流れはしばしば「トップダウン」とか「単方向の(unidirectional)」データフローと呼ばれます。全てのstateは特定のコンポーネントに属しており、そのstateから生成されたデータやUIは全て、その子孫のコンポーネントにしか影響を与えません。
コンポーネントのツリー構造を「propsが流れる滝」に例えるなら、それぞれのコンポーネントのstateは任意の点で合流する水源であり、合流地点よりから下流に流れていきます。
全てのコンポーネントが本当に独立していることを示すために、3つの
<Clock>
をレンダリングするApp
を作ってみましょう。function App() { return ( <div> <Clock /> <Clock /> <Clock /> </div> ); } ReactDOM.render( <App />, document.getElementById('root') );
それぞれの
Clock
は各自のタイマーを持ち、独立して自身を更新しています。Reactアプリケーションでは、コンポーネントがステートフルかステートレスかはコンポーネントの実装の問題であり、変わっていくものです。ステートレスなコンポーネントの内部でステートフルなコンポーネントを利用することは可能ですし、逆もまた然りです。
この記事はReact公式サイト https://reactjs.org/ の部分的な和訳です。
Copyright © 2018 Facebook Inc.
コメント
コメントを投稿