木曜日, 6月 12, 2025
- Advertisment -
ホームニューステックニュース【図解解説】これ1本12分でReactのコンセプト全20種を理解できる教科書 #React - Qiita

【図解解説】これ1本12分でReactのコンセプト全20種を理解できる教科書 #React – Qiita



【図解解説】これ1本12分でReactのコンセプト全20種を理解できる教科書 #React - Qiita

// HTML:

JSXの場合classではなくclassNameを使います。

image.png

カーリーブレスを利用することで条件付きレンダリングも可能です。

const isLoggedIn = true;
const element = (
  div>
    {isLoggedIn ? button>ログアウトbutton> : button>ログインbutton>}
  div>
);

isLoggedInの値によって表示する画面を切り替えています。

image.png

propsは「Properties(プロパティ)」の略で、Reactコンポーネントに渡すデータのことです。

Propsの渡し方

親コンポーネントから子コンポーネントにpropsを渡すには、HTMLの属性のような構文を使います

function App() {
  return (
    div>
      Welcome name="太郎" age={25} isAdmin={true} />
    div>
  );
}

Propsの受け取り方

function Welcome(props) {
  return (
    div>
      h1>こんにちは、{props.name}さん!h1>
      p>あなたは{props.age}歳です。p>
      p>{props.isAdmin ? '管理者権限があります' : '一般ユーザーです'}p>
    div>
  );
}

または分割代入で受け取ることもできます。

function Welcome({ name, age, isAdmin }) {
  return (
    div>
      h1>こんにちは、{name}さん!h1>
      p>あなたは{age}歳です。p>
      p>{isAdmin ? '管理者権限があります' : '一般ユーザーです'}p>
    div>
  );
}

image.png

コンポーネントの開始タグと終了タグの間に記述された内容を参照するための特別なPropsです。

childrenを活用することで以下のことが実現可能です。

function Card({ title, children }) {
  return (
    div className="card">
      div className="card-header">{title}div>
      div className="card-body">{children}div>
    div>
  );
}

Card title="ユーザー情報">
  p>名前: 山田太郎p>
  p>職業: エンジニアp>
Card>

ただしChildrenの使用は一般的ではないのでコードが壊れやすくなる可能性があります

image.png

key propはReactのリスト要素に対して設定する特別な属性です。Reactがリスト内の各要素を一意に識別し、効率的な再レンダリングを行うために使用されます。

1. 効率的な更新をする

Reactは変更があった部分だけを効率的に更新します。
たとえばリストの順序を変える例を考えてみます。

  • アイテム1
  • アイテム2
// リストの先頭に新しい項目を追加
  • 新しいアイテム
  • // 新しい項目
  • アイテム1
  • // 「アイテム1」だったDOM要素が更新される
  • アイテム2
  • // 「アイテム2」だったDOM要素が更新される

このとき、Reactは全ての項目が変更されたと判断し、全てを再描画します。
ここでKeyがある場合は

  • アイテム1
  • アイテム2
// リストの先頭に新しい項目を追加
  • 新しいアイテム
  • // 新しいDOM要素が作成される
  • アイテム1
  • // 既存のDOM要素がそのまま使われる
  • アイテム2
  • // 既存のDOM要素がそのまま使われる

必要な部分だけを更新することが可能です。

コンポーネントの状態維持

keyはコンポーネントのアイデンティティを決定します。同じkeyを持つコンポーネントはReactによって同じコンポーネントと認識され、状態が維持されます。keyが変わると、Reactはそのコンポーネントを破棄して新しいコンポーネントを作成します。

function App() {
  const [items, setItems] = useState(['アイテム1', 'アイテム2']);
  
  return (
    ul>
      {items.map((item, index) => (
        ItemWithState key={item} text={item} />
      ))}
    ul>
  );
}

function ItemWithState({ text }) {
  const [isChecked, setIsChecked] = useState(false);
  
  return (
    li>
      input
        type="checkbox"
        checked={isChecked}
        onChange={() => setIsChecked(!isChecked)}
      />
      {text}
    li>
  );
}

image.png

この例では、各ItemWithStateコンポーネントは自身のチェックボックスの状態を持っています。適切なkeyがあれば、リストの項目が並べ替えられても、各チェックボックスの状態は対応する項目に紐付いたままになります。

Keyはリストで一意性がありレンダリングされても変わらない安定した値をしようしましょう

image.png

レンダリングとはコンポーネントがReactによって実行され、画面に表示される要素を決定するプロセスのことです。

image.png

仮想DOMは、実際のブラウザDOMの軽量な JavaScript表現です。Reactがメモリ上に保持する単なるJavaScriptオブジェクトであり、実際のDOMの状態のコピーのようなものです。

レンダリングは以下のプロセスで行われます。

1. レンダーフェーズ

コンポーネントがレンダリングされると、Reactはコンポーネントから仮想DOM要素を作成します。

1.JSX記法がReact.createElement()の呼び出しに変換される
2.render()メソッドや関数コンポーネントの本体が実行される
3.Reactは新しい仮想DOMツリーを構築する

2. 調整フェーズ
新しく生成された仮想DOMと前回のレンダリング結果(DOM)を比較し、実際に変更が必要な箇所を特定します。

1.異なる型の要素は完全に再構築される
2.同じ型の要素はプロパティのみが更新される
3.key属性を使って子要素の追加・削除・移動を効率的に検出する

3. コミットフェーズ
実際のDOM操作を行われます。

1.前のフェーズで特定された変更のみを実際のDOMに適用
2.副作用(useEffectなど)の実行

仮想DOMを利用することで変更箇所だけを効率的に更新ができる利点があります。

再レンダリングのトリガーには4つあります。

1.状態(State)の変更

const [count, setCount] = useState(0);
// setCount(1) を実行するとコンポーネントが再レンダリング

2.プロパティ(props)の変更

// Parent component:
Child name={userName} />
// userNameが変わると、Childコンポーネントが再レンダリング

3.親コンポーネントの再レンダリング

function Parent() {
  const [count, setCount] = useState(0);
  // countが変わると、ChildコンポーネントもProps変更がなくても再レンダリング
  return (
    div>
      Child />
      button onClick={() => setCount(count + 1)}>+button>
    div>
  );
}

4.コンテキスト(Context)の変更

const value = useContext(ThemeContext);
// ThemeContextの値が変更されると再レンダリング

不要なレンダリングを減らすためにuseCallbackuseMemoなどを利用してレンダリングを最適化することも可能です。

image.png

ボタンをクリックしたり、フォームを入力したりWebアプリには様々なユーザー操作があります。
ユーザー操作が行われたらそれに反応して何かしらのフィードバックを返すために利用するのがイベントハンドリングです。

function Button() {
  function handleClick() {
    alert('ボタンがクリックされました!');
  }

  return (
    button onClick={handleClick}>
      クリックしてください
    button>
  );
}

ボタンをクリックした時に関数を実行するにはonClickが利用できます。
クリックをするとhandleClick関数が実行されます。

HTMLで普通に書くとイベントハンドラは小文字ですが、Reactではキャメルケースで記述します。onclickではなくonClickのように書きます。

他にもReactには様々なイベントハンドラが用意されています。

// フォームの入力変更を検知
function InputForm() {
  function handleChange(event) {
    console.log('入力値:', event.target.value);
  }

  return input onChange={handleChange} />;
}

// フォーム送信時
function Form() {
  function handleSubmit(event) {
    // フォームのデフォルト送信動作を防止
    event.preventDefault();
    console.log('フォームが送信されました');
  }

  return (
    form onSubmit={handleSubmit}>
      input type="text" />
      button type="submit">送信button>
    form>
  );
}

またイベントハンドラは簡単なものであればこのように書くことも可能です。

function App() {
  return (
    button onClick={() => alert('クリックされました!')}>
      クリック
    button>
  );
}

image.png

stateはReactコンポーネントが内部で管理する可変データです。
stateが変更されると、Reactはコンポーネントを再レンダリングし、変更を画面に反映します。これにより、動的なユーザーインターフェースを構築できます。

初心者がやってしまう間違いが以下のようなコードです。

function Counter() {
  // 通常の変数として宣言
  let count = 0;

  function handleClick() {
    // letで宣言した変数を更新
    count = count + 1;
    console.log('現在のカウント:', count); // 値は増えているがUIは更新されない
  }

  return (
    div>
      p>カウント: {count}p>
      button onClick={handleClick}>増やすbutton>
    div>
  );
}

このコードではcountの値は変更されていますが画面には反映されません。
これはステートを利用していないことが理由です。

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    div>
      p>カウント: {count}p>
      button onClick={() => setCount(count + 1)}>増やすbutton>
    div>
  );
}

このようにステートを利用することで再レンダリングが起きて画面に反映されます。

ステートの使い方は簡単です。

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

useSateというHooksを利用します。0は初期値です。
countに実際のステートの値、setCountはステートの値を更新する関数です。

setCountに値をいれることでステートの更新をすることができます。(つまり画面が再レンダリングされる)

image.png

HTMLのフォーム要素(input、textarea、selectなど)は通常、自身で状態を保持し更新します。しかし、Reactではstateが「信頼できる唯一の情報源」であるべきという考え方があります。

Controlled Componentでは
1.フォーム要素の値はReactのstateによって制御される
2.入力値の変更はイベントハンドラを通じてstateを更新する
3.表示される値は常にReactのstateから取得される

これらの原則によって、Reactコンポーネントはフォーム要素の動作を完全に制御できるようにデザインされています。

function InputForm() {
  const [inputValue, setInputValue] = useState('');
  
  function handleChange(event) {
    setInputValue(event.target.value);
  }
  
  return (
    div>
      input
        type="text"
        value={inputValue} // stateを値として設定
        onChange={handleChange} // 変更時にstateを更新
      />
      p>入力された値: {inputValue}p>
    div>
  );
}

Controlled Componentのメリットは入力した値を簡単にカスタマイズできることです。
例えばバリデーションをするとこのようになります。

function EmailInput() {
  const [email, setEmail] = useState('');
  const [isValid, setIsValid] = useState(true);
  
  function validateEmail(email) {
    const re = /^[^s@]+@[^s@]+.[^s@]+$/;
    return re.test(email);
  }
  
  function handleChange(event) {
    const newEmail = event.target.value;
    setEmail(newEmail);
    setIsValid(validateEmail(newEmail) || newEmail === '');
  }
  
  return (
    div>
      input
        value={email}
        onChange={handleChange}
      />
      {!isValid && p style={{ color: 'red' }}>有効なメールアドレスを入力してくださいp>}
    div>
  );
}

メールアドレスの入力が始まるとhandleChangeが動きます。

 function handleChange(event) {
    const newEmail = event.target.value;
    setEmail(newEmail);
    setIsValid(validateEmail(newEmail) || newEmail === '');
  }

ここではsetIsValidの中でvalidateEmailが実行されてリアルタイムでメールアドレスが有効化をチェックしています。

  function validateEmail(email) {
    const re = /^[^s@]+@[^s@]+.[^s@]+$/;
    return re.test(email);
  }

入力値に対して即座に反応する必要があるUIを構築する際にControlled Componentは有効です。

image.png

stateやライフサイクルメソッドなどのReactの機能を関数コンポーネントで使えるようにする仕組みです。
Hooksを使うことで、コードの再利用性が高まり、コンポーネントの複雑さが軽減され、関連する機能をまとめやすくなります。

こちらに関してはすべて学べる教材を作成していますのでぜひご覧ください👇

image.png

Reactにおける純粋性は、関数型プログラミングの重要な概念の一つで、コンポーネントの予測可能性と信頼性を高める原則です。

純粋関数とは、以下の特性を持つ関数のことです。

1. 同じ入力に対して常に同じ出力を返す

以下は純粋な関数の例です。

function add(a, b) {
  return a + b;
}

常にaとbが決まれば同じ値が関数から返ってきます。

2. 副作用がない

関数は外部の状態を変更せず、関数の実行は入力と出力のみに依存します

以下は純粋な関数ではありません。

let total = 0;

function addToTotal(value) {
  total += value;
  return total;
}

呼び出す回数によって返却値が変わってしまいます。
純粋性があることによって「パフォーマンス最適化」「バグの減少」などにつながります。

image.png

潜在的な問題を早期に発見するための開発ツールです。本番環境では追加の動作をせず、開発時のみ動作する特別なモードです。

Strict Modeを利用することで「問題の早期発見」「非推奨なAPIの使用警告」「よりよい実装パターンの提案」が可能になっています。

タグで囲むことで利用することができます。

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
  React.StrictMode>
    App />
  React.StrictMode>,
  document.getElementById('root')
);

image.png

Strict Modeでは意図的に2回実行をするので初心者のコンソールが2回出てると思うあるあるが起きます。

image.png

副作用とは、コンポーネントの表示(レンダリング)以外で行われる処理のことです。

  • データの取得(API呼び出し、データベースの操作)
  • タイマーの設定(setTimeout, setIntervalなど)
  • イベントリスナーの登録/削除
  • Cookieやローカルストレージへのアクセス
  • 外部サービスとの連携

これらは副作用が発生する代表的な例です。
Reactは「純粋」でなければならないためレンダリング中には副作用を発生させてはいけないので、別に副作用を管理する必要があるのです。

useEffectは副作用を管理する代表的なHooksです。

import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  // データ取得の副作用
  useEffect(() => {
    setLoading(true);
    
    fetch(`https://api.example.com/users/${userId}`)
      .then(response => response.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      })
      .catch(error => {
        console.error('Error fetching user:', error);
        setLoading(false);
      });
  }, [userId]); // userIdが変わったときだけ実行

  if (loading) return div>Loading...div>;
  if (!user) return div>User not founddiv>;

  return (
    div>
      h1>{user.name}h1>
      p>Email: {user.email}p>
    div>
  );
}

取得できるデータは常に同じとは限らないため純粋性がなく、useEffectの中で取得しています。
※ データ取得はuseEffectの中ではやらないほうがいいのでSWRやReactQueryを利用しましょう

image.png

Refsは、ReactでDOMノードや React要素に直接アクセスするための方法です。
詳しくは以下の記事で解説しています👇

image.png

Reactコンポーネントツリーを通じてデータを直接渡す方法を提供します。通常のReactアプリケーションでは、データは親から子へpropsを通して「トップダウン」に渡されますが、コンテキストを使うと、中間コンポーネントを経由せずにデータを共有できます。

image.png

コンポーネントが深くネストされた状況で、多くのコンポーネントに同じデータを渡す必要がある場合(テーマ、ユーザー情報など)、props経由でデータを渡していくと「Propsのバケツリレー」が発生します。コンテキストはこの問題を解決します。

image.png

Portalは、親コンポーネントのDOM階層の外側にある別のDOM要素にコンポーネントをレンダリングするための機能です。通常、最も近い親ノードのDOM階層内にレンダリングされますが、ポータルを使うと階層外の任意の場所に要素を「転送」できます。

import { createPortal } from 'react-dom';

function Modal({ children }) {
  const modalRoot = document.getElementById('modal-root');
  
  // createPortal(レンダリングする内容, 実際にレンダリングされるDOM要素)
  return createPortal(
    div className="modal">
      {children}
    div>,
    modalRoot
  );
}

実際にHTMLではコンポーネントを表示したい箇所にidを振ってあげます

index.html

id="root">

id="modal-root">

特定のUIコンポーネント(モーダル、ツールチップ、通知など)を実装する際に非常に便利なツールです。
ただし通常のフローから外れるため、必要な場合にのみ使用してください。

image.png

Suspenseは、コンポーネントがレンダリングを一時停止して、何らかのリソース取得が完了するのを待つための機能です。非同期操作(データ取得など)の間にローディング状態を代わりに表示できます。

ReactQueryやSWRなどSuspenseに対応しているライブラリを利用することで、データ取得時にローディングを出すことができます。

import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import { Suspense } from 'react';

const queryClient = new QueryClient();

function UserData({ userId }) {
  const { data } = useQuery(
    ['user', userId], 
    () => fetchUser(userId),
    { suspense: true }
  );

  return div>ユーザー名: {data.name}div>;
}

function App() {
  return (
    QueryClientProvider client={queryClient}>
      h1>ユーザープロフィールh1>
      
      Suspense fallback={div>ユーザーデータを読み込み中...div>}>
        UserData userId={1} />
      Suspense>
    QueryClientProvider>
  );
}

React 18ではstartTransitionやuseTransitionといったAPIが導入され、Suspenseと組み合わせることで、ユーザー体験を向上させることができます

const ProfileTab = lazy(() => {
  return new Promise(resolve => {
    setTimeout(() => resolve(import('./ProfileTab')), 1500);
  });
});

const SettingsTab = lazy(() => {
  return new Promise(resolve => {
    setTimeout(() => resolve(import('./SettingsTab')), 1500);
  });
});

function App() {
  const [activeTab, setActiveTab] = useState('home');
  const [isPending, startTransition] = useTransition();

  function handleTabChange(tab) {
    startTransition(() => {
      setActiveTab(tab);
    });
  }

  return (
    div className="app">
      div className="tabs">
        button 
          onClick={() => handleTabChange('home')}
          className={activeTab === 'home' ? 'active' : ''}
        >
          ホーム
        button>
        button 
          onClick={() => handleTabChange('profile')}
          className={activeTab === 'profile' ? 'active' : ''}
        >
          プロフィール
        button>
        button 
          onClick={() => handleTabChange('settings')}
          className={activeTab === 'settings' ? 'active' : ''}
        >
          設定
        button>
      div>

      {isPending && div className="loading-indicator">切り替え中...div>}
      
      div className="tab-content">
        Suspense fallback={div className="loading">コンテンツ読み込み中...div>}>
          {activeTab === 'home' && div>ホームコンテンツdiv>}
          {activeTab === 'profile' && ProfileTab />}
          {activeTab === 'settings' && SettingsTab />}
        Suspense>
      div>
    div>
  );
}

export default App;

タブを切り替えると(わざと)遅延が発生します。

  function handleTabChange(tab) {
    startTransition(() => {
      setActiveTab(tab);
    });
  }

まずはタブが切り替えられてstartTransitionが動きます。
レンダリングが可能になるまではisPendingがtrueになります。

  const [isPending, startTransition] = useTransition();

  (省略)

      {isPending && div className="loading-indicator">切り替え中...div>}

タブがレンダリング可能になったあとは、実際にレンダリングを行おうとします。
わざと遅延させているのでその間はSuspense状態になり、読み込み画面が変わりに表示されます。

const ProfileTab = lazy(() => {
  return new Promise(resolve => {
    setTimeout(() => resolve(import('./ProfileTab')), 1500);
  });
});

(省略)

        Suspense fallback={div className="loading">コンテンツ読み込み中.../div>}>

Suspenseを使うことでif/elseや条件付きレンダリングの代わりに、Suspenseを使ってローディング状態を宣言的に表現できます。

image.png

Reactアプリケーション内でJavaScriptエラーをキャッチし、エラーが発生した場合にフォールバックUIを表示するためのReactコンポーネントです。Error Boundaryを使用することで、アプリケーション全体がクラッシュすることなく、エラーが発生した部分だけを適切に処理できます。

import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    div role="alert">
      p>エラーが発生しました:p>
      pre>{error.message}pre>
      button onClick={resetErrorBoundary}>再試行button>
    div>
  );
}

function MyComponent() {
  return (
    ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() => {
        // リセット時に追加で行いたい処理
      }}
      resetKeys={[/* 変更時にエラー状態をリセットする依存配列 */]}
    >
      ComponentThatMightError />
    ErrorBoundary>
  );
}

多くのアプリケーションでは、サードパーティのライブラリ「react-error-boundary」を使用しています。

の中でエラーが発生するとアプリケーション全体がクラッシュしますが、Error Boundaryのコンポーネントで囲んであげることによってChildrenでエラーが出た時に変わりの画面を表示しています。

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    div role="alert">
      p>エラーが発生しました:p>
      pre>{error.message}pre>
      button onClick={resetErrorBoundary}>再試行button>
    div>
  );
}

イベント伝播とは、DOMツリーを通じてイベントが親要素や子要素に伝わる仕組みです。イベント伝播のメカニズムを理解することで、効率的なイベントハンドリングが可能になります。

イベント伝播には2つのフェーズがあります

1.キャプチャリングフェーズ:イベントがルート要素から発生した要素へ降りていく
2.バブリングフェーズ:イベントが発生した要素からルート要素へ上がっていく

div onClick={handleDivClick}> {/* 3. 最後にここへ */}
  button onClick={handleButtonClick}> {/* 2. 次にここへ */
    span onClick={handleSpanClick}>クリックspan> {/* 1. ここで最初にクリックが発生 */}
  /button>
div>

この例では、spanをクリックすると、イベントはバブリングにより、spanからbuttonへ、そしてdivへと伝播します。

内側のボタンをクリックしたらすべてのボタンが反応しても困ります。なのでイベントを制御する方法があります。

1. バブリングの停止

function Button() {
  const handleClick = (e) => {
    e.stopPropagation();
    console.log('ボタンがクリックされました');
    // このイベントは親要素へ伝播しない
  };

  return (
    div onClick={() => console.log('div がクリックされました')}>
      button onClick={handleClick}>クリックbutton>
    div>
  );
}

e.stopPropagationを利用することで止めることができます。

2. デフォルト動作の阻止

function Form() {
  const handleSubmit = (e) => {
    e.preventDefault(); // フォームの送信を阻止
    // カスタム処理
  };

  return (
    form onSubmit={handleSubmit}>
      button type="submit">送信button>
    form>
  );
}

preventDefault()で、要素のデフォルト動作を止めることができます。

複雑なコンポーネント階層を持つアプリケーションでは、イベントの伝播とその制御方法を知ることが重要です。

いかがでしたでしょうか?
Reactのコンセプトの中にも知らないことなどあったのではないでしょうか?
基礎を理解することでよりReactを使いこなせるようになるはずです。詳しく解説した動画を投稿しているのでよかったらみてみてください!

プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページからお気軽にカウンセリングをお申し込みください!
▼▼▼





Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -