2019.5追記
https://ja.reactjs.org/
↑React公式サイト全体が和訳されました この記事より↑をご覧いただくのが便利です!
前置き
本記事は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')
);'
“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')
);
見やすいよう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属性名そのままではなく、キャメルケースの命名規則を用います。
例えば、
class
はclassName
に、tabindex
はtabIndex
になります。
※訳注: 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'));
ページに“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とその子要素
を比較し、必要なDOM更新だけを行います。
先ほどのチクタク時計をブラウザのツールで見れば、そのことが確認できます。
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')
);
この例で何が起きているのか、おさらいしましょう。
- このコードは
<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」なコンポーネントと呼ばれます。
対照的に、この関数は自分への入力を変更してしまうため「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);
この章では、この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>
);
}
}
親クラスのコンストラクタに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')
);
次に、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
});
});
}
このマージは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>;
}
この流れはしばしば「トップダウン」とか「単方向の(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アプリケーションでは、コンポーネントがステートフルかステートレスかはコンポーネントの実装の問題であり、変わっていくものです。ステートレスなコンポーネントの内部でステートフルなコンポーネントを利用することは可能ですし、逆もまた然りです。
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')
);
JSXのコールバックにおけるthisの意味に気をつけてください。JavaScriptのデフォルトでは、クラスメソッドのthisは束縛されていません。this.handleClick
をbindせずにonClick
に渡すと、実際に関数が呼ばれた時にthis
はundefined
になります。
これは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')
);
この例ではisLoggedIn
propsの値によって異なる挨拶をレンダリングします。
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')
);
このように変数を宣言し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')
);
これが動作するのは、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の値に従ってレンダリングされます。warn
がfalse
なら、コンポーネントはレンダリングされません。
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')
);
コンポーネントのrender
メソッドがnull
を返しても、そのコンポーネントのライフサイクルメソッドの発火には影響がありません。例えばcomponentDidUpdate
は呼ばれます。
8.リストとキー
訳注:本章の「リスト」は単に複数のモノが並んだ集合という意味の英語で、特定の機能や関数を指していません。
はじめに、JavaScriptでリストを編集する方法をおさらいしましょう
以下のコードでは、配列numbers
をmap()
関数に渡して値を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')
);
このコードは箇条書きのリストで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')
);
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')
);
経験則として、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')
);
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
コンポーネントを抽出しましょう。c
かf
の値を取る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>
);
}
}
これで2つの入力欄ができましたが、片方に入力しても他方は更新されません。これでは要求と矛盾します。同期させたいのです。
また、Calculator
はBoilingVerdict
を表示できません。現在の温度は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.temperature
をthis.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>
がvalue
とonChange
の両方のpropsを受け付けるように、TemperatureInput
がtemperature
とonTemperatureChange
をCalculator
から受け取れるようにします。
こうすると、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.