React by Examples 第1回: Create React App テンプレート, JSX

React のサンプルはすでに数多くあるが, Redux を使うものばかり。やたらにパッケージを使わず、最低限のもので作っていく。

ソースコードはこちら; netsphere / react-by-examples · GitLab

今回のサンプルは、まずは永続化はせず、ボタンへの反応と、HTMLの表示のみを取り扱う。

Reactとは

React は, Webブラウザ上のユーザインタフェイスを構築するための JavaScriptライブラリ。Single Page Application (SPA) を作れる。

Facebook 社が開発している, オープンソース (ライセンスは MIT).

特徴:

  • 宣言的. 状態 state を更新するアクションと, state に基づきHTMLを出力するレンダラが厳密に分離されている。DOMを直接操作してはならない。内部では仮想DOM (Virtual DOM) を使っている。
  • コンポーネントベース。Webページをコンポーネントに分割し、コンポーネント単位で作成する。それぞれのコンポーネントが状態 state を持つ (stateful コンポーネント)。
  • 往年のE4Xに似た JSX 記法で, コンポーネント / HTMLタグを記述する。JavaScriptソースコードに埋め込む (別ファイルではない)。他方, CSSは別ファイルを import する。

2020年5月現在, JavaScript front-endフレームワークとしては不動の地位を占める。一時 Vue.js が迫ってきていたが, かわした。Angular は廃れつつある。

事前準備

Fedora 32 には nodejs パッケージ, npm パッケージがある。CentOS 8 も同様。

パッケージ Fedora 32 CentOS 8
nodejs v12.16.1 v10.19.0 nodeコマンド
npm v6.13.4 v6.13.4 npm, npx コマンド。

最近のパッケージマネジャの人気は yarn のようだが, あらかじめ入っている npm のほうを使う。

アプリケーションのテンプレート

Create React App で雛形を作るのが簡単。CRA と略されることがあるが、いくら何でも略しすぎなのでは。

npx コマンドに渡す。これで、新しいアプリケーションの雛形を作ってくれる。

 $ npx create-react-app アプリ名

アプリ名 のディレクトリが掘られて、次のファイルが生成される。

  • .gitignore
  • package.json
  • package-lock.json
  • public
    • favicon.ico
    • index.html
    • logo192.png
    • logo512.png
    • manifest.json
    • robots.txt.
  • README.md
  • src
    • App.css
    • App.js
    • App.test.js
    • index.css
    • index.js
    • logo.svg
    • serviceWorker.js
    • setupTests.js

package-lock.json ファイルは、npm install で自動的に生成されるため, リポジトリに保存しなくてもよい。

プロジェクトの概要は package.json ファイルに記述する。コメントを記入できないのが辛い。dependencies が依存パッケージ。scriptsnpm のサブコマンド。あとは大体、書いてあるとおり。

JavaScript
[RAW]
  1. {
  2. "name": "sample01",
  3. "version": "0.1.0",
  4. "private": true,
  5. "dependencies": {
  6. "@testing-library/jest-dom": "^4.2.4",
  7. "@testing-library/react": "^9.5.0",
  8. "@testing-library/user-event": "^7.2.1",
  9. "react": "^16.13.1",
  10. "react-dom": "^16.13.1",
  11. "react-scripts": "3.4.1"
  12. },
  13. "scripts": {
  14. "start": "react-scripts start",
  15. "build": "react-scripts build",
  16. "test": "react-scripts test",
  17. "eject": "react-scripts eject"
  18. },
  19. "eslintConfig": {
  20. "extends": "react-app"
  21. },
  22. "browserslist": {
  23. "production": [
  24. ">0.2%",
  25. "not dead",
  26. "not op_mini all"
  27. ],
  28. "development": [
  29. "last 1 chrome version",
  30. "last 1 firefox version",
  31. "last 1 safari version"
  32. ]
  33. }
  34. }

作成する

Create React App で生成したアプリケーションのエントリポイントは public/index.html, src/index.js になる。いずれも react-scripts/config/paths.js で定められている。

src/index.js は, 次のコードで, App コンポーネントを呼び出す。大文字で始めるとコンポーネント, 小文字だと HTML タグと解釈される。

JavaScript
[RAW]
  1. ReactDOM.render(<App />, document.getElementById('root'));

どうして JavaScript ファイルに XML/HTML のようなリテラル (これを JSX と呼ぶ。) を書いても動くのかは、後述。

src/App.js は次のようにする。クラスを React.Component から派生させると, コンポーネントになる。構築子 constructor と render() メソッドが必須。

JavaScript
[RAW]
  1. // index.js から呼び出される.
  2. // コンポーネントは, React.Component から派生させる.
  3. class App extends React.Component
  4. {
  5. constructor(props) {
  6. super(props);
  7. this.state = {
  8. value: 10, // カウンタの初期値
  9. };
  10. }
  11. // function() *ではない*. this の扱いが違う.
  12. onIncr = () => {
  13. // state の更新によって、DOM に反映させる
  14. this.setState( {value: this.state.value + 1} );
  15. }
  16. // function() *ではない*
  17. onDecr = () => {
  18. this.setState( {value: this.state.value - 1} );
  19. }
  20. render() {
  21. return (<div>
  22. カウンタ: {this.state.value}
  23. <div>
  24. <button type="button" onClick={this.onIncr}>+</button>
  25. <button type="button" onClick={this.onDecr}>-</button>
  26. </div>
  27. </div>
  28. );
  29. }
  30. }

コンストラクタでは、必ず super() を呼び出し, this.state を設定する。React では,「状態」は必ず state を通じて変更する。

render() は描画を担当する。このメソッド内で state を変更してはならない。戻り値として JSX を返す。

onIncr, onDecr がイベントハンドラ。この中で state を更新すると, 自動的に仮想 DOM が再計算されて, 必要なコンポーネントのレンダラが呼び出される。

React では, DOMを直接変更しないだけでなく, レンダラ呼出しも自動になっている。state の更新は, 必ず setState() メソッドで行う。これがきっかけになる。

サンプルの実行

次のコマンドで, Webサーバが開始し, ついでに Webブラウザが開く。
$ npm install
$ npm start


スクリーンショット

仮想 DOM と JSX

Reactの特徴の一つに、仮想DOM (Virtual DOM) がある。render() ではいつでも, XML/HTMLコード片 (JSX) を完全に描画するように記述する。

React は, メモリ上に生成された仮想のDOMを保持する。この仮想DOM がどのように変化するかという差分を計算し、それに基づいて実際の Webブラウザ上に表示される DOM を書き換える、という2段構えになっている。

なので、変更点を直接更新するようなコードを書く必要がない。依存関係を気にしなくてよくなるため、非常に楽になる。

JSX も, React の特徴の一つ。JSX は往年の E4X ではない。というか, E4X はすでに廃れて、現代のWebブラウザでは文法エラーになってしまう。

JSX を含む JavaScript コードは, 実行前に Babel がコンパイルし、Webブラウザで動作する JavaScript に変換する。例えば, JavaScript に埋め込まれた次の文字列は、

  <App onclick={this.foo} />
次のように変換される;
JavaScript
[RAW]
  1. React.createElement(App, {
  2. onclick: this.foo
  3. })

仮想DOM の計算は、render() を持つクラスとは別の文脈で実行される。JavaScript の this はダイナミックスコープのため, 呼出し側の文脈で決まる。this をあらかじめクラスに bind しておく必要がある。function 式でメソッドを定義してはならない理由がこれだ。ハンドラの定義は, アロー関数を使わなければならない。

このように展開されるので, return と行を替えて JSX を書く場合は return 直後に ( が必要だが, 続けて書く場合は特に括弧がなくてもよい。