React公式ドキュメント和訳 「主な概念」編 〜React公式入門書〜

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章ー第5章)です。いくつかのリンクを除き、筆者の理解の限界からくる誤訳(ご指摘歓迎)を除いてほぼそのままの内容です。またの機会に6章以降も訳したい。



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点が判れば大体乗り切れます。
さあ、始めましょう!


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というカラースキームを使っています。


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がどう見えるべきか」を考えるほうが、遥かにバグが少なくなります。


コンポーネントと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」と呼びます。

訳注: 英語でこの変数をpropsと呼ぶのはもちろん、コンポーネント側の仮引数名も慣習的に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コンポーネントは上記のルールを破ることなく、ユーザーの行動・ネットワークのレスポンスなど色々な呼び出しに対して場合により異なる表示を返すことができます。


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


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

Copyright © 2018 Facebook Inc.

コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)