Open6

React をざっくりキャッチアップ

zuribozuribo

React

  • React は、Facebook が開発する OSS の JavaScript ライブラリで、Web アプリのフロントエンド開発で UI を構築するのに使用されている。
zuribozuribo

Hello World!

はじめての React - ① Hello World - インフラジスティックス・ジャパン株式会社Blog

環境

  • M1 MacBook Pro
  • macOS 13.6.3

とにかく動かす

ツールのインストール

$ brew install nodebrew

$ nodebrew install stable

$ nodebrew ls
v20.10.0

current: v20.10.0

$ node -v
v20.10.0

$ npm -v
10.2.3

アプリケーションの作成

$ npx create-react-app my-first-app --template typescript

$ cd my-first-app

$ npm start

Hello World! に書き換え

src/HelloWorld.tsx を作成。

import React from 'react'

const HelloWorld = () => {
  return (
    <p>Hello World!</p>
  );
};

export default HelloWorld;

src/App.tsx でコンポーネントのインポートと配置

import HelloWorld from './HelloWorld';
import './App.css';

function App() {
  return (
    <div className="App">
      <HelloWorld />
    </div>
  );
}

export default App;

zuribozuribo

Microsoft のトレーニング

https://learn.microsoft.com/ja-jp/training/paths/react/

成果物

ポイント

  • React は Web アプリを作る時の UI を作るための OSS フレームワークである。
  • React Native を使って Mobile アプリや Desktop アプリでも使える。
  • Model-View-Controller のうち、View の部分に焦点を当てている。
  • JSX (JavaScript XML) と呼ばれる構文を使用し、HTML と JavaScript の両方を1つのファイルに統合できる。
  • 普通の JS の関数と同じ記法で、コンポーネントを定義できる。
function App() {}
  • export したコンポーネントは、他の場所で import して使える。
...
export default App;
import App from './App';
...
<App />
  • 動的データを表示するには、ハンドル {} の構文が使える。
<div>{ Date.now() }</div>
  • プロジェクトの構造
    • public/: 静的ファイルの置き場 (HTML, CSS, 画像など)
    • src/: レンダリングする必要があるファイル (.jsx ファイルなど) の置き場
    • package.json: アプリのパッケージとスクリプトのリスト
    • snowpack.config.js: Snowpack の構成オプション
    • 主要なパッケージ
      • Snowpack: JSX を HTML および JavaScript にレンダリングする(コンパイラ的なもの)
      • React: コンポーネントの作成に使える
      • React-DOM: アプリをマウントするのに使える
  • コンポーネントにはプロパティ (props) を介してデータを渡せる。
function RecipeTitle(props) {
  return (
    <section>
      <h2>{ props.title }</h2>
    </section>
  );
}
const recipe = {
  title: 'Mashed potatoes',
};

<RecipeTitle title={recipe.title} />
  • useState は状態管理を行える React フックであり、状態オブジェクトと更新用の関数が取得できる。
const [ recipe, setRecipe ] = useState(initialRecipe);
  • useEffect は特定のオブジェクトの状態が変化した時に実行される React フックであり、イベントリスナーとして使える。
const [ prepared, setPrepared ] = useState(false);

useEffect(() => {
  setPrepared(recipe.ingredients.every(i => i.prepared));
}, [recipe]);

{ prepared ? <h2>Prep work done!</h2> : <h2>Just keep chopping.</h2> }
zuribozuribo

Create React App

"Hello World!" のスレッドでは、React のプロジェクトを作成する際に npx create-react-app を使用していた。

https://github.com/facebook/create-react-app

https://create-react-app.dev/

このコマンドの中で何が起きているのかを調べていたところ、どうやら 2024 年 1 月現在ではあまり推奨されていないという情報を目にした。

https://react.dev/learn/start-a-new-react-project

公式では、以下の4つの方法が紹介されており、そこに Create React App は含まれていない。

  • Next.js
  • Remix
  • Gatsby
  • Expo

https://zenn.dev/nekoya/articles/dd0f0e8a2fa35f

https://zenn.dev/laravelstudy/articles/bad11d64eeac42

上記の記事にあるように、やはり Create React App は推奨されていないらしい。

zuribozuribo

Next.js

https://nextjs.org/docs

Next.js はフルスタックウェブアプリを開発するための React フレームワークである。

Next.js は、React に必要な設定や構成を抽象化・自動化し、開発者がアプリ開発に集中できるようにしてくれる。

React 基礎

https://nextjs.org/learn/react-foundations

React と Next.js

https://nextjs.org/learn/react-foundations/what-is-react-and-nextjs

Next.js は、フルスタックウェブアプリを作成するための構成要素を提供する React フレームワークである。

ウェブアプリの構成要素

モダンなアプリを作る上で考慮する必要のあることは、以下のようなものがある。

  • User Interface (UI): ユーザーがどのようにアプリとやりとりするか
  • Routing: アプリの異なるパーツ間をどのようにナビゲートするか
  • Data Fetching: どこにデータが存在し、どのように取得するか
  • Rendering: 静的もしくは動的なコンテンツをいつ・どこでレンダリングするか
  • Integrations: サードパーティサービスをどのように統合するか
  • Infrastructure: アプリをどこでデプロイ、保存、実行するか
  • Performance: どのようにアプリを最適化するか
  • Scalability: チーム、データ、通信の増加に、どのようにアプリを適応させるか
  • Developer Experience: アプリを開発・管理する上での開発者の体験をよくできるか

React とは?

React とは、インタラクティブな UI を構築するための JavaScript ライブラリである。つまり、ユーザーに見えていて、ユーザーが操作する画面上のコンポーネントを作るために使える便利な関数 (API) を提供している。

React の成功は、UI にフォーカスしており、他の部分については自由度を持たせた拡張性に起因している。結果として、Next.js を含む様々なサードパーティツールのエコシステムが構築されている。これは同時に、React アプリをゼロから構築するのは大変である。

Next.js とは

Next.js とは、React フレームワークで、ウェブアプリを作成する上での構成要素を提供する。

Next.js は、React に必要なツールや設定を行い、追加の機能や最適化などを提供する。つまり、UI の構築に React を使い、段階的に追加の Next.js の機能を使って、アプリでよく必要になる要件 (Routing, Data Fetching, Caching など) を解決す流。

UI のレンダリング

https://nextjs.org/learn/react-foundations/rendering-ui

ユーザーが Web ページを訪れた時に、サーバーはブラウザへ HTML ファイルを返す。ブラウザは HTML を読み込み、DOM (Document Object Model) を構築する。

DOM とは

DOM とは、HTML の要素を表現する方法である。コードと UI の橋渡しを行い、ツリー構造を持っている。

DOM methods や JavaScript を使って、ユーザー起因のイベントをモニタリングしたり、DOM を操作したりすることができる。

JavaScript で UI の更新

https://nextjs.org/learn/react-foundations/updating-ui-with-javascript

以下のコードでは、DOM methods を使って、'app' の ID がついた <div> 要素の中に、'Develop. Preview. Ship.' というテキストが入った <h1> 要素を追加している。

<html>
    <body>
        <div id="app"></div>
        <script type="text/javascript">
            // Select the div element with 'app' id.
            const app = docuemtn.getElementById('app');

            // Create a new H1 element.
            const header = document.createElement('h1');

            // Create a new text node for the H1 element.
            const text = 'Develop. Preview. Ship.';
            const headerContent = document.createTextNode(text);

            // Append the text to the H1 element.
            header.appendChild(headerContent);

            // Place the H1 element inside the div.
            app.appendChild(header);
        </script>
    </body>
</html>

HTML vs. DOM

HTML ファイルは先ほどの通りだが、DOM では <h1> が追加された状態のものをいう。

<html>
    <body>
        <div id="app">
            <h1>Develop. Preview. Ship.</h1>
        </div>
        <script type="text/javascript">...</script>
    </body>
</html>

つまり、HTML は最初に渡されたファイルであり、DOM は JavaScript などによって動的に変化する現時点でのページの内容を示すものである。

JavaScript を使って DOM を更新するのは、非常に強力である一方で冗長である。つまり、<h1> 要素を1つ追加するためのだけに、何行かのコードを書かないといけない。

Imperative vs. Declarative Programming

  • Imperative Programming: UI をどのように更新するかを記述する (例: シェフにピザを作るための step-by-step のレシピを渡す)
  • Declerative Programming: UI がどのように表示されて欲しいかを記述する (例: どのようにピザが作られているかを気にせずに、ピザを注文する)

React は、UI を構築する上で使える Declerative なライブラリである。

React: Declerative な UI ライブラリ

React では、開発者が UI に起きて欲しいことを要求し、React がそれを実現するための方法を見つけ出して実行してくれる。

Getting Started with React

https://nextjs.org/learn/react-foundations/getting-started-with-react

React を使うには、2つの React スクリプトを外部サイト (unpkg.com) から読み込んでくる必要がある。

  • react: React のコアライブラリ
  • react-dom: React を DOM と一緒に使うための DOM 固有のメソッドを提供するライブラリ

React は、JSX と呼ばれる形式で記述する。これは HTML の記法を JavaScript 内で使えるようにする拡張である。しかし、ブラウザは JSX をそのまま解釈することができないので、Babel などの JavaScript コンパイラを使って、JSX のコードを JavaScript に変換する必要がある。

<html>
    <body>
        <div id="app"></div>
        <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>
        <script type="text/jsx">
            const app = document.getElementById('app');
            const root = ReactDOM.createRoot(app);
            root.render(<h1>Develop. Preview. Ship.</h1>)
        </script>
    </body>
</html>

Components を使った UI の構築

https://nextjs.org/learn/react-foundations/building-ui-with-components

React のコアコンセプト

React アプリを構築する上で理解しておくべき2つのコアコンセプトがある。

  • Components
  • Props
  • State

Components

UI は Components と呼ばれるより小さな構成要素に分解することができる。

Components は、自己完結的な再利用可能なコードを構築できる。Components を LEGO ブロックと考え、これらのブロックを組み合わせてより大きな構造を作ることができる。UI の一部を更新する必要があるとき、特定のブロックを更新すれば良い。

このようにモジュールに分割することによって、コードがよりメンテナンスしやすくなる。

Components の作成

Component を作成するには、普通の JavaScript と同じように関数宣言のシンタックスが使え、UI 要素を返すようにすれば良い。気を付けるべきは、(1) 関数の頭文字は大文字である必要がある点と (2) Component を利用する時には通常の HTML タグと同じように <> で囲う必要がある点である。

<html>
    <body>
        <div id="app"></div>
        <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>
        <script type="text/jsx">
            const app = document.getElementById('app');

            function Header() {
                return (<h1>Develop. Preview. Ship.</h1>);
            }

            const root = ReactDOM.createRoot(app);
            root.render(<Header />);
        </script>
    </body>
</html>

Props を使ってデータを表示する

https://nextjs.org/learn/react-foundations/displaying-data-with-props

<Header /> Component を再利用した場合、同じコンテンツが表示される。

function Header() {
  return <h1>Develop. Preview. Ship.</h1>;
}

function HomePage() {
  return (
    <div>
      <Header />
      <Header />
    </div>
  );
}

外部ソースからデータを取ってくる必要があり、事前に何を表示したいかがわからなく、テキストをその場に応じて表示したい場合にどうするか。

React では、Properties (props) を介して、Component を定義する関数にデータを渡すことができる。

function Header(props) {
  return <h1>{props.title}</h1>;
}
function Header({ title }) {
  return <h1>{title}</h1>

呼び出し側では以下のように書く。

function HomePage() {
  return (
    <div>
      <Header title="React" />
      <Header title="A new title" />
    </div>
  );
}

リストの各要素に対して同じ操作を適用したい場合には、map() を使用することができる。
なお、React では、DOM 要素が更新されたかを一意に認識するために、key を渡す必要がある。この例では names の要素内容自体が一意なので、そのまま key に使うことができる。

function HomePage() {
  const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
  return (
    <div>
      <Header title="Develop. Preview. Ship." />
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>
    </div>
  );

State を使ってインタラクティブに

https://nextjs.org/learn/react-foundations/updating-state

React が、State と Event Handler を使って、インタラクティブな操作を定義することができる。

イベントの監視

以下のように onClick を使って、ボタン要素のクリックのイベントを認識することができる。

function HomePage() {
  // ...
  return (
    <div>
      {/* ... */}
      <button onClick={}>Like</button>
    </div>
  );
}

イベントの処理

以下のようにしてイベントをハンドリングすることができる。

function HomePage() {
  // ...

  function handleClick() {
    console.log("increment like count");
  }

  return (
    <div>
      {/* ... */}
      <button onClick={handleClick}>Like</button>
    </div>
  );
}

State と Hooks

React に Hooks と呼ばれる関数群があり、Hooks によって Component に State のようなロジックを追加で加えることができる。State とは、ユーザーのインタラクションなどによって時間と共に変化する UI に関する情報のことである。

例えば、"Like" ボタンがクリックされた回数を保存し、クリックの度に数を1増やしたいとする。この場合、以下のように行うことができる。React.useState() 関数は、変数とその変数を更新するための関数のペアが返され、引数としてその変数の初期値を与える。作られた変数 likeshandleClick() 内でインクリメントして setLikes() でセットしなおすことによって、その likes の数を画面に反映させることができる。

function HomePage() {
  // ...
  const [likes, setLikes] = React.useState(0);

  function handleClick() {
    setLikes(likes + 1);
  }

  return (
    <div>
      {/* ... */}
      <button onClick={handleClick}>Like ({likes})</button>
    </div>
  );
}

React から Next.js へ

https://nextjs.org/learn/react-foundations/from-react-to-nextjs

とりあえず、これまでに構築したものは以下の通り。

<html>
  <body>
    <div id="app"></div>
 
    <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>
 
    <script type="text/jsx">
      const app = document.getElementById("app")
 
      function Header({ title }) {
        return <h1>{title ? title : "Default title"}</h1>
      }
 
      function HomePage() {
        const names = ["Ada Lovelace", "Grace Hopper", "Margaret Hamilton"]
 
        const [likes, setLikes] = React.useState(0)
 
        function handleClick() {
          setLikes(likes + 1)
        }
 
        return (
          <div>
            <Header title="Develop. Preview. Ship." />
            <ul>
              {names.map((name) => (
                <li key={name}>{name}</li>
              ))}
            </ul>
 
            <button onClick={handleClick}>Like ({likes})</button>
          </div>
        )
      }
 
      const root = ReactDOM.createRoot(app);
      root.render(<HomePage />);
    </script>
  </body>
</html>

React は UI を構築するのに優れている一方で、完全に機能するスケーラブルなアプリを構築するのには手間がかかる。Nest.js は、React アプリを構築するのを助ける様々な追加の機能があり、設定や構成の多くを処理してくれる。

Next.js のインストール

プロジェクトで Next.js を使う場合には、もう unpkg.com から reactreact-dom を手動でロードする必要はない。代わりに、npm やその他のパッケージマネージャーを使って、これらのパッケージをインストールすることが可能である。

まず初めに、index.html が置いてある同じディレクトリに、追加で package.json ファイルを空のオブジェクトの状態で作成する。

{}

次にターミナルから以下を実行する。

npm install react@latest react-dom@latest next@latest

インストールが完了すると、package.json が以下のように変わっている。

{
  "dependencies": {
    "next": "^14.0.4",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

同様に package-lock.json も構築されており、各パッケージの正確なバージョンの情報が列挙されている。

最初のページの作成

その上で、app/ ディレクトリを作成し、その中に page.jsx ファイルを作成し、index.html から必要のない要素を取り除いた以下の内容を保存する。

import { useState } from 'react';

function Header({ title }) {
  return <h1>{title ? title : "Default title"}</h1>
}

export default function HomePage() {
  const names = ["Ada Lovelace", "Grace Hopper", "Margaret Hamilton"]

  const [likes, setLikes] = useState(0)

  function handleClick() {
    setLikes(likes + 1)
  }

  return (
    <div>
      <Header title="Develop. Preview. Ship." />
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>

      <button onClick={handleClick}>Like ({likes})</button>
    </div>
  )
}

その上で package.json に以下のコマンドを追加する。

{
  "scripts": {
    "dev": "next dev"
  },
  "dependencies": {
    "next": "^14.0.4",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

その上で、そのコマンドを実行する。

$ npm run dev

> dev
> next dev

   ▲ Next.js 14.0.4
   - Local:        http://localhost:3000

 ✓ Ready in 2.4s

対象の URL にアクセスすると、以下のエラーが表示される。

./app/page.jsx
ReactServerComponentsError:

You're importing a component that needs useState. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
Learn more: https://nextjs.org/docs/getting-started/react-essentials

   ╭─[/Volumes/workplace/test/my-first-nextjp-app/app/page.jsx:1:1]
 1 │ import { useState } from 'react';
   ·          ────────
 2 │ 
 3 │ function Header({ title }) {
 4 │   return <h1>{title ? title : "Default title"}</h1>
   ╰────

Maybe one of these should be marked as a client entry with "use client":
  ./app/page.jsx

エラーにある通り、useState は Client Component のみで動作するものである。デフォルトの設定は Server Component である。

Server / Client Components

https://nextjs.org/learn/react-foundations/server-and-client-components

サーバーとクラインと

  • クライアントとは、ユーザーのデバイス上で動作するブラウザで、サーバーへリクエストを送信する。サーバーからのレスポンスを UI に反映させる。
  • サーバーとは、データセンター上のコンピュータのことで、アプリのコードを実行し、クライアントからのリクエストを処理し、レスポンスを返す。

ネットワーク境界 (Network Boundary)

Network Boundary とは、異なる環境を分離する概念的な線のことである。

React では、Component Tree 上でどこに Network Boundary を配置するか選択することができる。例えば、Server 上でデータの取得とユーザーのポストのレンダリングを行い、Client 側で各ポストごとのインタラクティブな LikeButton をレンダリングするなどを決定できる。

その背景では、Component は2つのモジュールグラフに分けられる。Server Module Graph はサーバー上でレンダリングされる全ての Server Component を含んでおり、Client Module Graph は全ての Client Component を含んでいる。

Server Component がレンダリングされた後は、React Server Component (RSC) Payload というフォーマットでクラインとにデータが送信される。RSC には、Server Component のレンダリングした結果と、Client Component がレンダリングされるはずの場所のプレースホルダーが含まれる。

Client Components を使う

Next.js はデフォルトで Server Component を使っている。

先ほどの章で見たエラーは、インタラクティブな "Like" ボタンを Client Component に移動させることで解消することができる。

まずは app/ ディレクトリ配下に新しいファイル like-button.jsx を作成し、LikeButton コンポーネントを Client Component としてエクスポートする。具体的には 'use client'; ディレクティブをファイルの先頭に置く。

'use client';

import { useState } from "react";

export default function LikeButton() {
    const [likes, setLikes] = useState(0);

    function handleClick() {
        setLikes(likes + 1);
    }

    return <button onClick={handleClick}>Like ({likes})</button>
}

それに合わせて、app/page.jsx を変更する。

import LikeButton from "./like-button"

function Header({ title }) {
  return <h1>{title ? title : "Default title"}</h1>
}

export default function HomePage() {
  const names = ["Ada Lovelace", "Grace Hopper", "Margaret Hamilton"]

  return (
    <div>
      <Header title="Develop. Preview. Ship." />
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>

      <LikeButton />
    </div>
  )
}