Blog#96: Reactをマスターする:すべての開発者に必要な5つの基本概念

image.png

こんにちは、私は東京からのフルスタックWebデベロッパーであるTUANです。

今後の便利で面白い記事を見逃さないように、私のブログをフォローしてください。

Reactは簡単に学べる、人気のあるフロントエンドのライブラリです。しかし、高速かつパフォーマントなコードを書くためには開発者はいくつかの概念を理解する必要があります。この記事では、Reactでの状態とエフェクトの仕組みに関連する注意点と概念を詳しく説明します。

状態を導出する

時には、別の状態変数に依存する状態変数を作成する必要があります。それを行う方法の1つは、useEffectフックを使用して依存する状態変数を更新することです。 例えば、ユーザーIDのリストを表示するセレクト要素があるとします。選択されたユーザーIDをuserId状態変数で追跡したいと考えます。そうするためには、以下のようにします:

import { useState } from "react"  
  
const users = [  
  { id: "1", name: "User One" },  
  { id: "2", name: "User Two" },  
  { id: "3", name: "User Three" },  
]  
  
function Users () {  
  const [userId, setUserId] = useState("1")  
    
  return(  
    <select value={userId} onChange={e => setUserId(e.target.value)}>  
      <option value="1">User One</option>  
      <option value="2">User Two</option>  
      <option value="3">User Three</option>  
    </select>  
  );  
}

今、選択したユーザーを画面に表示したいとします。selectedUserという別の状態変数を作成し、userIdが変わったときにuseEffectフックを使用してそれを更新することができます。

import { useState, useEffect } from "react"  
  
function Users () {  
  const [userId, setUserId] = useState("")  
  const [selectedUser, setSelectedUser] = useState(undefined)  
    
  useEffect(() => {  
    setSelectedUser(users.find(u => u.id === userId))  
  }, [userId])  
    
  return(  
    <>  
      <select value={userId} onChange={e => setUserId(e.target.value)}>  
        <option>Select a user</option>  
        <option value="1">User One</option>  
        <option value="2">User Two</option>  
        <option value="3">User Three</option>  
      </select>  
      {selectedUser && <p>The selected user is: {selectedUser.name}</p>}  
    </>  
  );  
}

このアプローチは機能しますが、selectedUserを更新するための最も効率的な方法ではありません。この方法の問題は、コンポーネントがuserIdが変更されたときに最初にレンダーされ、その後にuseEffectフックがトリガーされるため、userIdが依存配列に渡されるためです。これは、selectedUserを更新するためにコンポーネントが2回レンダリングされることを意味して、最初にuserIdが変更されたとき、そして2回目にuseEffectフックがselectedUserを更新したときです。

もっと良いアプローチは、selectedUserの値をuserIdから直接導出することです。

function Users () {  
  const [userId, setUserId] = useState("")  
  const selectedUser = users.find(u => u.id === userId)  
    
  return(  
    <>  
      <select value={userId} onChange={e => setUserId(e.target.value)}>  
        <option>Select a user</option>  
        <option value="1">User One</option>  
        <option value="2">User Two</option>  
        <option value="3">User Three</option>  
      </select>  
      {selectedUser && <p>The selected user is: {selectedUser.name}</p>}  
    </>  
  );  
}

これは、selectedUseruserIdから直接導出され、別の状態変数として追跡されないため機能します。これは、userIdが変更されたときだけコンポーネントが再レンダリングされることを意味し、前の例のように2回再レンダリングすることはありません。

イベントハンドラー内で状態をセット

時には、クリックイベントやフォーム提出イベントなどのイベントハンドラー内で状態を更新する必要があります。これらの場合、状態更新が非同期に実行されることを確認することが重要で、イベントハンドラーが実行を終える前にコンポーネントが再レンダリングする機会を持つことができるようにします。

イベントハンドラー内で状態を正しく設定する方法の例:

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

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>The count is: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

この例では、クリックしたときにカウントをインクリメントするボタンがある「Counter」というコンポーネントがあり、カウントを表示します。 handleClickイベントハンドラーは、setCount関数を使用してcount状態変数を更新します。

setCount関数が非同期であることに注意することが重要で、つまり状態更新はすぐにコンポーネントに反映されない可能性があります。代わりに、イベントハンドラーが実行を終えると、コンポーネントは再レンダリングされます。

現在の状態の値に依存する操作を実行する必要がある場合は、setCount関数に関数を渡すことができます。例えば:

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

  const handleClick = () => {
    setCount((currentCount) => currentCount + 1);
  };

  return (
    <div>
      <p>The count is: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

この例では、setCount関数は現在のcountの値を引数として、新しい値を返す関数を渡して呼び出されます。これは、コンポーネントが再レンダリングする機会がなくても、状態更新が正しく実行されることを保証します。

クリーンアップ関数

時には、コンポーネントがアンマウントされたり、ある条件がもう満たされなくなった場合にクリーンアップアクションを実行する必要があります。これらの場合には、useEffectフックとともにクリーンアップ関数を使用することができます。

クリーンアップ関数は、コンポーネントがアンマウントされたり、useEffectフックの依存関係が変更された場合に呼び出される関数です。ここは、ネットワークリクエストのキャンセルやイベントリスナーの削除などの必要なクリーンアップを行う良い場所です。

useEffectフックとともにクリーンアップ関数を使用する方法の例:

function Users() {
  const [userId, setUserId] = useState(1);
  const [user, setUser] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      const response = await fetch(`/users/${userId}`);
      const data = await response.json();
      setUser(data);
    };

    fetchUser();

    return () => {
      // Cleanup function
      console.log("Cleaning up...");
    };
  }, [userId]);

  return (
    <div>
      {user ? (
        <p>The user is: {user.name}</p>
      ) : (
        <p>Loading...</p>
      )}
      <button onClick={() => setUserId(2)}>Load User 2</button>
    </div>
  );
}

この例では、userId状態変数に基づいてバックエンドAPIからユーザーを取得するUsersというコンポーネントがあります。userIdが変更されたときにフェッチをトリガーするためにuseEffectフックが使用されています。

useEffectフックには、コンポーネントがアンマウントされたり、userId状態変数が変更された場合に呼び出されるクリーンアップ関数もあります。この場合、クリーンアップ関数は単にコンソールにメッセージをログします。

重要なのは、クリーンアップ関数はuseEffectフックの依存関係が変更されたり、コンポーネントがアンマウントされた場合にのみ呼び出されることです。これは、userId状態変数が変更されてもuseEffectフックの依存関係が変更されない場合、クリーンアップ関数は呼び出されないことを意味しています。

プロップが変更されたときに状態を更新

時には、コンポーネントに渡されるプロップに基づいてコンポーネントの状態を更新する必要があります。これらの場合には、useEffectフックを使用して、プロップが変更されたときを検出し、それに応じて状態を更新することができます。

プロップが変更されたときに状態を更新する方法の例:

function User({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      const response = await fetch(`/users/${userId}`);
      const data = await response.json();
      setUser(data);
    };

    fetchUser();
  }, [userId]);

  return user ? <p>The user is: {user.name}</p> : <p>Loading...</p>;
}

この例では、userIdプロップに基づいてバックエンドAPIからユーザーを取得する「User」というコンポーネントがあります。 userIdプロップが変更されたときにフェッチをトリガーするためにuseEffectフックが使用されています。

重要なのは、useEffectフックの依存関係配列には、エフェクトが依存するプロップや状態変数を含める必要があることです。この場合、エフェクトはuserIdプロップに依存しているため、依存関係配列に含まれています。

useEffectフックの依存関係が正しく指定されていない場合、エフェクトが不必要にトリガーされる可能性があり、パフォーマンスの問題を引き起こす可能性があります。

同じ位置で状態を保存

時には、同じ位置でページを再描画するときにコンポーネントの状態を保持する必要があります。これらの場合は、useStateフックとuseMemoフックを使用して状態を保存できます。

useMemoフックは、依存関係が変更された場合にのみ再計算されるように値をメモ化することができるパフォーマンス最適化です。これは、同じ位置で再レンダリングされたときにコンポーネントの状態を保持するために便利です。

useMemoフックを使用してコンポーネントの状態を保存する方法の例:

function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);

  const increment = useMemo(() => {
    return () => setCount(count + 1);
  }, [count]);

  return (
    <div>
      <p>The count is: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

この例では、「カウンタ」というコンポーネントがあり、クリックしたときにカウントを増やすボタンがあり、カウントが表示されています。「increment」関数は、useMemoフックを使用して作成され、count状態変数が変更された場合にのみ再計算されます。これは、「increment」関数が常に「Counter」コンポーネントが同じ位置で再描画された場合でも「count」の正しい値を持つことを意味します。

重要なのは、useMemoフックの依存関係配列には、メモ化された値が依存するプロップや状態変数を含める必要があることです。この場合、「increment」関数はcount状態変数に依存しているため、依存関係配列に含まれています。

useMemoフックの依存関係が正しく指定されていない場合、メモ化された値が不必要に再計算される可能性があり、パフォーマンスの問題を引き起こす可能性があります。

結論

Reactにおける状態とエフェクトに関連したいくつかの概念を話しました。状態を派生すること、イベントハンドラ内で状態を設定すること、useEffectフックとクリーンアップ関数を使用すること、プロップが変更されたときに状態を更新すること、そして同じ位置で再レンダリングされたときにコンポーネントの状態を保持することなどについてカバーしました。

これらの概念の理解は、Reactで効率的でパフォーマントなコードを書くために重要であり、またReactフレームワークで状態や効果がどのように動作するかを理解するためにも重要です。

いつものように、この記事を楽しんで新しいことを学んでいただけたと思います。

ありがとうございました。次の記事でお会いしましょう!

この記事が気に入ったら、いいねをして購読してサポートしてください。ありがとうございます。

NGUYỄN ANH TUẤN

Xin chào, mình là Tuấn, một kỹ sư phần mềm đang làm việc tại Tokyo. Đây là blog cá nhân nơi mình chia sẻ kiến thức và kinh nghiệm trong quá trình phát triển bản thân. Hy vọng blog sẽ là nguồn cảm hứng và động lực cho các bạn. Hãy cùng mình học hỏi và trưởng thành mỗi ngày nhé!

Đăng nhận xét

Mới hơn Cũ hơn