Reactの新しいドキュメント(英語版)を読む
React初学者です。
Reactの公式ドキュメントには、以下の2つがあります。
- 日本語に翻訳されている旧版:https://ja.reactjs.org/
- 翻訳されていない新版:https://beta.reactjs.org/
⇒ これを書いた当初はベータ版でしたが、20230317に正式ローンチしてURLが変わりました!
https://react.dev/
初学者がいきなり英語のドキュメントに挑戦することは少なく、新版のドキュメントを読んだ・チュートリアルに挑戦したという話はあまり聞きません。そこで、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
他には…
- Gatsby: https://www.gatsbyjs.com/
- Remix: https://remix.run/
- Remix: https://razzlejs.org/
機能別にツールセットを探している人へ
-
パッケージマネージャ: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
- Reactツリーを挿入させる目印(root)をHTMLタグで埋め込む
<!-- ... existing HTML ... -->
<div id="like-button-root"></div>
<!-- ... existing HTML ... -->
- スクリプトタグを</body>の直前に埋め込む
react.development.js
は、Reactコンポーネントを定義するjsファイル。
react-dom.development.js
は、HTML要素をDOMにレンダリングするためのjsファイル。
<!-- 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>
- Reactコンポーネントの中身を記述する
ここではJSX構文を使っていません。JSX構文を使うためには、Babelのようなコンパイラも用意する必要があります。今回は省略。
'use strict';
function LikeButton() {
const [liked, setLiked] = React.useState(false);
if (liked) {
return 'You liked this!';
}
return React.createElement(
'button',
{
onClick: () => setLiked(true),
},
'Like'
);
}
- Reactコンポーネントをページに埋め込む
このコードがやっていることは、最初のステップでHTMLに追加した<div>を見つけ、Reactルートを作成し、その中に「いいね」ボタンのReactコンポーネントを表示することです。コンポーネントは何度でも再利用できます。ページ内に複数表示させることもできます。
const rootNode = document.getElementById('like-button-root');
const root = ReactDOM.createRoot(rootNode);
root.render(React.createElement(LikeButton));
- 本番用のjsを最小化する
ページロード時間を短縮するために、下記リンクのやり方で、javascriptファイルを最小化します。
最小化のやり方
<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が画面に表示する内容」を返すよう記述しています。
return React.createElement('button', {onClick: () => setLiked(true)}, 'Like');
これは、以下のようなJSX構文を使って記述することもできます。2つのコードは同等です。
return <button onClick={() => setLiked(true)}>Like</button>;
JSX構文を使うためには、JSX構文をブラウザが理解できるカタチに翻訳してくれるBabelコンパイラを<script>
タグで追加します。
<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つのやり方があります。
- コンパイラの代わりにjavascriptのテンプレート文字列を使用するhtmのようなものを代わりに使う
- 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
保存のたびにフォーマットしてくれる設定もあるので試してみました。が、コードの配置がガラッと変わってしまって脳内をリセットされるみたいでした。タイピングのたびに、コードを整形してくれるものもあるようなので、今はそちらを試しています。普段から良くない書き方をしているので、書き方を変えていくべきなのでしょう。反省です。
Quick Start
公式ドキュメントのトップページには2つの導線が用意されています。
- クイックスタート:https://react.dev/learn
- APIの仕様まとめ:https://react.dev/reference/react
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増えます。
同じコンポーネントを複数回レンダリングした場合、それぞれが独自の状態を「記憶」し、他のボタンに影響を与えません。
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()の直下に書けってことでしょうか。もしかすると、フックを呼び出す順番で結果が変わるケースもあるのかもしれないですね。
コンポーネント間でデータを共有する方法
このセクションは、公式の図が圧倒的に分かりやすいです。
1つ上のセクションの最後で取り上げたApp.jsでは、MyApp
コンポーネント内に、2つのMyButton
コンポーネントを入れた構造を持っています。それぞれのMyButton
コンポーネントは自分専用のカウンタをもっており、クリックされたボタンのカウンタだけが変化する仕様でした。
しかし、複数のコンポーネント間でデータを共有し、常に一緒に更新したい場合もあります。
両方のMyButton
コンポーネントが同じカウンタの値を表示し、一緒に更新するためには、個々のボタンの状態を「上へ」移動させます。すべてのボタンを含む最小公倍数的な親コンポーネントに移動させます。
まず、state変数をMyButton
からMyApp
に移動します。
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へ、クリックハンドラと状態変数を渡します。
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を読み込みます。
+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のリフトアップ」「状態を上にあげる」などと言うようです。
ここまでが概要です。次はチュートリアルです。〇×ゲームをつくります。
分かりやすさを重視して、チュートリアルは別のスクラップに隔離しておくことにします。
チュートリアルの追加課題もまとめました。これは目次をつけるために、別記事に隔離しています。