Open17

Reactの新しいドキュメント(英語版)を読む

さとのわさとのわ

React初学者です。

Reactの公式ドキュメントには、以下の2つがあります。

初学者がいきなり英語のドキュメントに挑戦することは少なく、新版のドキュメントを読んだ・チュートリアルに挑戦したという話はあまり聞きません。そこで、DeepLを片手に試行錯誤する過程をスクラップしていきます。

はじめに

  • ReactはUIを「コンポーネント」と呼ばれる部品に分解して整理するためのライブラリです。
  • 新版ドキュメントでは、すべての説明が、クラスではなく、Hooksを使って書かれています。
  • ここではドキュメントの翻訳ではなく、理解したことをまとめています。

書き手のレベル:HTML/CSS/javascript/PHPを使ったサイト制作経験あり。モダンなWEB開発には疎く、最近ではメールマガジンで化石のようなコーディングをしています。進化したい。

さとのわさとのわ

環境構築編

https://react.dev/learn/installation
Reactは、既存サイトに必要な分だけ段階的に導入することができるように設計されています。Reactを始める方法はいくつもあります。

CodeSandboxのようなブラウザ統合開発環境を使う

CodeSandboxはとても使いやすいです。インストール不要でReactをすぐに試すことができます。公式ドキュメントでは、コードの解説でCodeSandboxを使っています。便利。

ローカルで試す

以下のHTMLを使います。エディタとブラウザで開いて、returnのあたりを編集してみてください。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World</title>
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
    
      function MyApp() {
        return <h1>Hello, world!</h1>;
      }

      const container = document.getElementById('root');
      const root = ReactDOM.createRoot(container);
      root.render(<MyApp />);

    </script>
  </body>
</html>

既存ページにReactを追加する

既存のサイトにReactを少しだけ追加するのであれば、<script>タグを埋め込みます。

<!-- ... Reactを表示させたい場所に目印となる要素を追加する ... -->
<div id="like-button-root"></div>

<!-- body閉じタグの上にscriptタグを追加 -->
    <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<!-- jsファイルにReactコードを記述する -->
    <script src="like-button.js"></script>
  </body>
</html>

React単体のプロジェクトを始める

React開発に最低限必要なものを詰め込んだスターターセットがあるので、簡単にセットアップすることができます。カードゲームのスタートデッキみたいなものです。スターターセットではなく、もっと良い機能をいっぱい詰め込んだフレームワークもいろいろあるので、そちらを使ってもOKです。

さとのわさとのわ

新しいReactプロジェクトをはじめるには

React開発に最低限必要なものを詰め込んだスターターセットやフレームワークを紹介する前に、ローカルにNode.jsをインストールする必要があります。
Node.js:https://nodejs.org/ja/

Node.jsとは

クロスプラットフォームのjavascript実行環境です。javascriptはクライアントサイドで動くプログラミング言語で、主に、ユーザーのブラウザ上で実行されます。このjavascriptを、サーバーサイドでも動かせるように環境を整えてくれるのがNode.jsです。クロスプラットフォームとは、異なる「土台となる環境」であっても同じように動くという意味です。

インストール後、バージョンを確認したところ、以下の通りでした。

  • Node.js:v18.15.0
  • npm:9.5.0
さとのわさとのわ

Reactはあくまで、UIを「コンポーネント」と呼ばれる部品に分解して整理するためのライブラリです。ルーティングやデータ管理は担当しません。

データを呼び出したり(fetch)、複数ページのURLを制御したり(router)する場合は、それぞれの機能が組み込まれたフレームワークを使用します。

このあたりは技術選定をするときに整理したい情報です。そもそもReactを使うべきか。

さとのわさとのわ

Create React App

React.jsはMeta(Facebook)が開発しています。Create React AppはMeta公式が提供するReactの雛形作成ツールセットで、大変な各種設定(WebpackやBabelなど)をスキップして、すぐに着手できる環境を整えてくれます。
Node.jsをインストール後、コマンドプロンプトで以下を実行します。

npx create-react-app my-app

結構時間かかります。そのあと、ディレクトリを移動して、npm(Node Package Manager)をスタート。

cd my-app
npm start

同じようなことが出来る雛形作成ツールとして、2つ例示してくれています。
・Vite(pnpmというコマンドのもの)
・Parcel(yarnというコマンドのもの)

技術系ブログで見たことのないコマンドがあると読み疲れてしまうので、あれとこれは同じような役割の仕事をするのかと、なんとなく理解しているといろいろ捗る気がします。

さとのわさとのわ

全部入りのフレームワークを選ぶなら

React公式のおすすめは、Next.js。ルーティングもスタイリングもサーバーサイドレンダリングも、あらゆる機能が全部入った軽量フレームワークです。チュートリアルも充実しているそうです。
Next.js: https://nextjs.org/learn/foundations/about-nextjs

他には…

機能別にツールセットを探している人へ

  • パッケージマネージャ:npm、Yarn、pnpm

  • コンパイラ:Bable、Typescript、swc

    • Babelは、JSX構文でReactに埋め込んだHTMLコードを、JSON形式みたいなものに翻訳してくれます。Create-React-Appに含まれています。
    • Typescriptは、Microsoftが公開したプログラミング言語で、コンパイルするとjavascriptのコードへと変換してくれます。
  • バンドル:webpack、Parcel、esbuild、swc
    モジュール化されたコードを小さなパッケージにまとめて、ロード時間を最適化してくれます。バンドルは、束ねるとか、セットにするという意味です。webpackはCreate-React-Appに含まれています。

  • ミニファ:Terser、swc
    コードをコンパクトにします。

  • サーバー:Express

  • リンター:ESLint
    コードチェックしてくれます。Create-React-Appに含まれています。

  • テストランナー:Jest

ゼロからセットアップするとき、一部機能を再作成するときに、この機能別ガイドを活用します。

さとのわさとのわ

Reactを既存サイトに埋め込む方法

https://react.dev/learn/add-react-to-an-existing-project

  1. Reactツリーを挿入させる目印(root)をHTMLタグで埋め込む
example.html
<!-- ... existing HTML ... -->

<div id="like-button-root"></div>

<!-- ... existing HTML ... -->
  1. スクリプトタグを</body>の直前に埋め込む
    react.development.jsは、Reactコンポーネントを定義するjsファイル。
    react-dom.development.jsは、HTML要素をDOMにレンダリングするためのjsファイル。
example.html
    <!-- end of the page -->
    <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
    <script src="like-button.js"></script>
  </body>
</html>
  1. Reactコンポーネントの中身を記述する
    ここではJSX構文を使っていません。JSX構文を使うためには、Babelのようなコンパイラも用意する必要があります。今回は省略。
like-button.js
'use strict';

function LikeButton() {
  const [liked, setLiked] = React.useState(false);

  if (liked) {
    return 'You liked this!';
  }

  return React.createElement(
    'button',
    {
      onClick: () => setLiked(true),
    },
    'Like'
  );
}
  1. Reactコンポーネントをページに埋め込む
    このコードがやっていることは、最初のステップでHTMLに追加した<div>を見つけ、Reactルートを作成し、その中に「いいね」ボタンのReactコンポーネントを表示することです。コンポーネントは何度でも再利用できます。ページ内に複数表示させることもできます。
like-button.js
const rootNode = document.getElementById('like-button-root');
const root = ReactDOM.createRoot(rootNode);
root.render(React.createElement(LikeButton));
  1. 本番用のjsを最小化する
    ページロード時間を短縮するために、下記リンクのやり方で、javascriptファイルを最小化します。
    最小化のやり方
example.html
<script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>
さとのわさとのわ

#ReactでJSXを使うには
1つ上のセクションの3番目、like-button.jsでは、javascript関数で「Reactが画面に表示する内容」を返すよう記述しています。

like-button.js
return React.createElement('button', {onClick: () => setLiked(true)}, 'Like');

これは、以下のようなJSX構文を使って記述することもできます。2つのコードは同等です。

like-button.js
return <button onClick={() => setLiked(true)}>Like</button>;

JSX構文を使うためには、JSX構文をブラウザが理解できるカタチに翻訳してくれるBabelコンパイラを<script>タグで追加します。

example.html
  <script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>
+  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
+  <script src="like-button.js" type="text/babel"></script>
</body>

JSXプリプロセッサの導入方法

Node.jsがインストールされている環境で、コマンドプロンプトに以下のコードを叩きます。

npm init -y
npm install @babel/cli@7 babel-preset-react-app@10

これだけで、プロジェクトにJSXプリプロセッサが追加されます。

JSXプリプロセッサを実行するには、まず、プロジェクトにsrcという名前のフォルダを作成します。次に、以下のコマンドを実行。ウォッチャー(ファイルを逐一調べるようなもの?)を起動させます。

npx babel --watch src --out-dir . --presets babel-preset-react-app/prod

JSXで書いたlike-button.jsをsrcフォルダに移動します。ウォッチャーは、srcフォルダにあるファイルをチェックして、ブラウザに適したJavaScriptコードに変換したjsファイルを作成します。

JSXを使わない・使えない場合

2つのやり方があります。

  1. コンパイラの代わりにjavascriptのテンプレート文字列を使用するhtmのようなものを代わりに使う
  2. React.createElement()を使う
function Hello(props) {
  return React.createElement('div', null, 'Hello ', props.toWhat);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  React.createElement(Hello, { toWhat: 'World' }, null)
);

構文で書くとReact.createElement(component, props, ...children).
componentには、HTML要素を表す文字列か、関数コンポーネントが入る。
propsには、渡したい変数・属性などが入る。
...childrenには、テキスト文字列や子要素などが入る。

さとのわさとのわ

エディタのセットアップ

VS CodeやWebStormなどを使って、Reactコードをより読みやすく、速く書けるようにします。以下、おすすめの拡張機能です。

  • リント Linting
    コードを書いている間に、コードの問題を発見し、早期に修正することを支援してくれます。例:ESLint

  • フォーマット Formatting
    tabやスペースなどを、あらかじめ設定されたルールに従って整形してくれます。例:Prettier

保存のたびにフォーマットしてくれる設定もあるので試してみました。が、コードの配置がガラッと変わってしまって脳内をリセットされるみたいでした。タイピングのたびに、コードを整形してくれるものもあるようなので、今はそちらを試しています。普段から良くない書き方をしているので、書き方を変えていくべきなのでしょう。反省です。

さとのわさとのわ

React Developer Tools

各ブラウザのReact対応のデベロッパーツールを使えば、コンポーネントやprops、stateなどの情報を調査しやすくなります。拡張機能を追加するだけなので、とても簡単に導入できます。

WEBブラウザだけでなく、React Nativeを使ったモバイルアプリにおいても、デベロッパーツールを使うことができます。

ここまでがインストール、環境構築の話でした。

さとのわさとのわ

Quick Start

公式ドキュメントのトップページには2つの導線が用意されています。

Reactを学ぶ方は、クイックスタートから始めます。ここではReactの概念の8割を紹介してくれます。

コンポーネントとネストをつくる

Reactアプリはコンポーネントでつくられています。コンポーネントとは、独自のロジックと見た目をもつUIのかたまりです。ボタンのような小さいものから、ページ全体といった大きいものまであります。以下のように、マークアップを返すjavascriptの関数として宣言します。

function MyButton() {
  return (
    <button>I'm a button</button>
  );
}

コンポーネント名は必ず大文字で始めます(例:MyButton)。コンポーネントは、他のコンポーネントで使用することができます(ネスト)。

export default function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <MyButton />
    </div>
  );
}

export defaultを使って宣言することで、ファイル内のメインコンポーネントを指定します。

JSXを使ってマークアップを記述する方法

ReactではJSXと呼ばれるマークアップ構文をよく使用します。これは必須ではありません。
JSXは、HTMLと似ていますが、HTMLより厳格なルールをもっています。
<br />のようにタグを閉じなければなりません。また、コンポーネントは複数のJSXタグを返すことができないので、<div>...</div>のようなラッパーで囲う必要があります。

function AboutPage() {
  return (
    <>
      <h1>About</h1>
      <p>Hello there.<br />How do you do?</p>
    </>
  );
}

スタイルを追加する方法

cssのclassを指定するときはclassNameを使います。

<img className="avatar" />

他にも見慣れない書き方があるようなので、いくつか気づいたものをここに残しておきます。 camelCaseという命名ルールを使っています。

//テーブルタグをいろいろしたいとき
<tr>
   <th colSpan="2">
     セルを横に結合する
   </th>
</tr>

//ラジオボタンのラベルを付けたいとき
<div key="itemid">
 <input id="itemid" type="radio" />
 <label htmlFor="itemid" />
</div>

key属性を付けるために<input><label><div>で囲わないといけないのが冗長な気がしてなんだかなぁという気分です。

さとのわさとのわ

データ(変数)を表示する方法

JSXでは、中カッコ{}を使うことで、javascriptの変数を埋め込んで表示することができます。マークアップタグの属性で変数を使いたいときは、"..."引用符の代わりに、中カッコ{}を使う必要があります。以下の例だと、<img>タグのsrc属性として{user.imageUrl}の変数値を渡しています。

return (
 <div>
  <h1>
    {user.name}
  </h1>
   <img
     className="avatar"
     src={user.imageUrl}
   />
 </div>
);

また、中カッコ{}の中には、式を入れることもできます。文字列を連結したりできます。

  return (
    <>
      <h1>{user.name}</h1>
      <img
        className="avatar"
        src={user.imageUrl}
        alt={'Photo of ' + user.name} //連結
        style={{
          width: user.imageSize,
          height: user.imageSize
        }}
      />
    </>
  );
}

style属性の書き方は見慣れない感じです。これは、style={}の中カッコの中に、javascriptの通常オブジェクトが入っています。

さとのわさとのわ

条件付きのレンダリングについて

Reactではjavascriptのコードを書くときと同じテクニックを使って条件分岐を記述します。

let content;
if (isLoggedIn) {
 content = <AdminPanel />;
} else {
 content = <LoginForm />;
}
return (
 <div>
   {content}
 </div>
);

条件演算子(X ? A: B;)や論理積(&&)も使えます。コンポーネント単位だけでなく、属性の指定においても使えます。

//条件演算子
<div>
  {isLoggedIn ? (
    <AdminPanel />
  ) : (
    <LoginForm />
  )}
</div>

//論理積
<div>
  {isLoggedIn && <AdminPanel />}
</div>

リストのレンダリングについて

コンポーネントのリストを表示するには、for loopや配列のmap()関数など、javascriptの機能を利用します。例えば、以下のような商品オブジェクトの配列があるとします。

const products = [
  { title: 'Cabbage', id: 1 },
  { title: 'Garlic', id: 2 },
  { title: 'Apple', id: 3 },
];

コンポーネント内部では、map()メソッドを使って、商品の配列を<li>の配列に変換します。map()は、与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列を生成してくれます。元の配列に変更を加えず、新しい配列を返してくれるのがポイント。変更前と変更後の状態を管理しやすいからか、reactのあちこちで活躍しているように見えます。

const listItems = products.map(product =>
  <li key={product.id}>
    {product.title}
  </li>
);

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

配列の一つひとつの要素である<li>にはユニークなkey属性を持たせます。項目の並べ替えや削除を制御しやすくするためです。

さとのわさとのわ

イベントへの応答

コンポーネント内部で、イベントハンドラ関数を宣言することで、イベント発火時の対処を記述することができます。

function MyButton() {
  function handleClick() {
    alert('You clicked me!');
  }

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

onClick={handleClick}の部分ですが、onClick={handleClick()}と書いてしまうと、関数をすぐに呼び出してしまいます。()は書かず、関数オブジェクトを渡すだけでOKです。

画面の更新について

コンポーネントに何かの情報を「記憶」させ、それを表示させることができます。まず、状態を管理するstate変数を扱えるようにします。

import { useState } from 'react';

これで、コンポーネント内部でstate変数を宣言することができます。

function MyButton() {
  const [count, setCount] = useState(0);

useStateでは、現在の状態countと、それを更新するための関数setCountの2つを得ることができます。useState()countの初期値に0を渡しています。

//MyButton関数コンポーネントの宣言
function MyButton() {
  //state変数を定義
  const [count, setCount] = useState(0);

  //クリックイベントのイベントハンドラ関数を定義。ここでstate変数を更新
  function handleClick() {
    setCount(count + 1);
  }

  return (
      //MyButton関数コンポーネントが返す要素
    <button onClick={handleClick}>
      Clicked {count} times
    </button>
  );
}

最初にボタンを表示するときは0、ボタンをクリックするたびにhandleClickの中身のsetCount()関数が呼び出され、カウンタの値は1増えます。

同じコンポーネントを複数回レンダリングした場合、それぞれが独自の状態を「記憶」し、他のボタンに影響を与えません。

App.js
import { useState } from 'react';

export default function MyApp() {
  return (
    <div>
      <h1>Counters that update separately</h1>
+      <MyButton />
+      <MyButton />
    </div>
  );
}

function MyButton() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      Clicked {count} times
    </button>
  );
}

Hooksフックを使うには

useで始まる関数はHooks、フックと呼ばれます。フックはReactの組み込み関数で、他にもいろいろなものが用意されています。

  • useStete
  • useEffect
  • useContext
  • useRef
  • useMemo
  • useCallback

詳細はReact APIリファレンスにてご確認ください。

ざっくり言うとfunction()の直下に書けってことでしょうか。もしかすると、フックを呼び出す順番で結果が変わるケースもあるのかもしれないですね。

さとのわさとのわ

コンポーネント間でデータを共有する方法

このセクションは、公式の図が圧倒的に分かりやすいです。
https://react.dev/learn#sharing-data-between-components

1つ上のセクションの最後で取り上げたApp.jsでは、MyAppコンポーネント内に、2つのMyButtonコンポーネントを入れた構造を持っています。それぞれのMyButtonコンポーネントは自分専用のカウンタをもっており、クリックされたボタンのカウンタだけが変化する仕様でした。

しかし、複数のコンポーネント間でデータを共有し、常に一緒に更新したい場合もあります。

両方のMyButtonコンポーネントが同じカウンタの値を表示し、一緒に更新するためには、個々のボタンの状態を「上へ」移動させます。すべてのボタンを含む最小公倍数的な親コンポーネントに移動させます。

まず、state変数をMyButtonからMyAppに移動します。

App.js
export default function MyApp() {
+  const [count, setCount] = useState(0);

+  function handleClick() {
+    setCount(count + 1);
+  }

  return (
    <div>
      <h1>Counters that update separately</h1>
      <MyButton />
      <MyButton />
    </div>
  );
}

function MyButton() {
-  // もともとconst [count, setCount] = useState(0);たちがあった場所
}

次に、親のMyAppから子のMyButtonへ、クリックハンドラと状態変数を渡します。

App.js
export default function MyApp() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <h1>Counters that update separately</h1>
+      <MyButton count={count} onClick={handleClick} />
+      <MyButton count={count} onClick={handleClick} />
    </div>
  );
}

最後に、MyButtonを変更して、親から引き渡されたpropsを読み込みます。

App.js
+function MyButton({ count, onClick }) {
  return (
+    <button onClick={onClick}>
      Clicked {count} times
    </button>
  );
}

流れをまとめます。

state変数(count)の初期値に0が入っている。
⇒MyButton1とMyButton2がレンダリングされ、ブラウザに表示される
⇒ユーザーがどっちかをクリック
⇒クリックされたボタンに仕掛けられた「handleClick」イベントが発火!setState()によりstate変数(count)が更新
⇒stateが変わったので、MyAppコンポーネント全体をみて、変化したところを探す
⇒変化したところだけを再レンダリング
⇒<MyButton />の{count}部分が使われているボタンが再レンダリングされる。
⇒すべてのボタンに新しい値が表示される

この流れを「lifting state up」「stateのリフトアップ」「状態を上にあげる」などと言うようです。