# ⚪️ React Hooks を基礎から理解する

# 🔷 useState とは

useState()は、関数コンポーネントで state を管理(state の保持と更新)するための React フックであり、最も利用されるフックです。

state とはコンポーネントが内部で保持する「状態」のことで、画面上に表示されるデータ等、アプリケーションが保持している状態を指しています。state は props と違い後から変更することができます

# ▫️ useState の注意点

1. Accepts a single parameter, the initial state value, which can be any data type:

唯一のパラメータを受け取り、それが状態の初期値であり、初期値は任意のデータ型であることができます。

// Example with a string initial state
const [text, setText] = useState("Hello");

// Example with a number initial state
const [count, setCount] = useState(0);

// Example with an object initial state
const [person, setPerson] = useState({ name: "John", age: 25 });

return (
  <div>
    <p>{text}</p>
    <p>{count}</p>
    <p>{person.name}</p>
    <p>{person.age}</p>
    <button onClick={() => setText("Bye")}>Change Text</button>
  </div>
);

2. Returns an array containing the state value and a method to update the state:

戻り値は、状態値と状態を更新するためのメソッドが含まれた配列で、メソッド名は "set" で始まり、後に状態名が続きます。

// Example using a boolean state
const [isActive, setIsActive] = useState(false);

3. useState() can be used multiple times to store multiple states:

useState()は複数回使用でき、異なる状態値を保存できます。

// Example with two different states
const [name, setName] = useState("John");
const [age, setAge] = useState(25);

4. Parameter can be a function, and the initial state is determined by the function's return value. The function is only called once, it used when the initial value is dynamic.:

パラメータは関数であることができ、関数が返すものが初期状態になります。関数は一度だけ呼び出され、初期の動的値の場合に使用されます。

// Example with a function as the initial state
function App(props) {
  // 從外部 props 中取得 count 的值,如果不存在,預設為 0
  const propsCount = props.count || 0;

  // 使用 useState,將初始值設置為一個函數,這個函數會在初始化時被調用一次
  const [count, setCount] = useState(() => {
    // 這裡可以進行一些邏輯處理,例如動態計算初始值
    return propsCount;
  });
}

//這樣寫是有錯誤的 ❌,因為每次渲染都會調用 useState,這樣會導致每次渲染都會調用函數,這樣就不是只調用一次了。 正確寫法如下:
function App(props) {
    const [count , setCount] = useState(() => {
        return props.count || 0;
    });
    //這樣,這個函數就只會在渲染的時候執行。

5.状態の設定メソッドの引数は値または関数にすることができます(つまり、値を渡すことも、関数を渡すこともできます。関数の戻り値によって新しい状態が設定されます。

setCount() の引数は値であるか、あるいは関数であるか選択できます。関数の場合、その関数の引数は現在の状態値であり、関数の戻り値が新しい状態値となります。

// 値を渡す
setCount(新しい値);

// 関数を渡す
setCount((現在のCount) => {
  // ここで計算やロジックを行います
  // 戻り値が新しい状態値になります
  return 何らかの計算(現在のCount);
});
// Example with a function as the parameter
function App() {
  const [count, setCount] = useState(0);

  function handleCountChange() {
    // setCount(count + 1);
    setCount((prevCount) => prevCount + 1);
  }

  return (
    <div>
      <p>{count}</p>
      <button onClick={handleCountChange}>Change Count</button>
    </div>
  );
}

6. 状態の設定メソッド自体が非同期です

状態の更新は非同期で行われるため、setCount を即座に呼び出しても、React はすぐに状態を更新しません。状態更新後に何らかの操作を実行する必要がある場合は、useEffect を使用するか、関数式の更新で前の状態値を使用します。

// useEffect を使用する例
useEffect(() => {
  // ここで何らかの操作を実行します。これは状態が更新された後に実行されます
  console.log("Count updated:", count);
}, [count]); // count を useEffect の依存リストに追加することを忘れずに

// あるいは関数式の更新で前の状態値を使用する例
setCount((前のCount) => {
  // ここで前の状態値(前のCount)を使用します
  return 前のCount + 1;
});

7. useState() can be used in a custom hook:

useState()はカスタムフックで使用できます。

// Custom hook
function useCounter(initialCount) {
  const [count, setCount] = useState(initialCount);

  function increment() {
    setCount((prevCount) => prevCount + 1);
  }

  function decrement() {
    setCount((prevCount) => prevCount - 1);
  }

  return [count, increment, decrement];
}

// Component using the custom hook
function App() {
  const [count, increment, decrement] = useCounter(0);

  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}

# 🔶 useReducer とは

React hooks を基礎から理解する (useReducer 編) (opens new window)

状態管理のためのフックで、useState と似たような機能。useStateuseReducer に内部実装されています。

(state, action) => newState という型の reducer を受け取り、現在の statedispatch 関数の両方を返します。

const [state, dispatch] = useReducer(reducer, "初期値");

reducerstate を更新するための関数で、dispatch は、reducer を実行するための呼び出し関数です。 (変数を宣言するときに、state の更新方法をあらかじめ設定しておくことが出来る。) dispatch(action)で実行 action は何をするのかを示すオブジェクト  {type: increment, payload: 0}のように、type プロパティ(action の識別子)と値のプロパティで構成されている。

# ▫️ useReducer の注意点

1. useReduceruseState の代替ではない

useState と useReducer の比較

特性 useState useReducer
扱える state 的 type 数値、文字列、オブジェクト、論理値 オブジェクト、配列
関連する state 的取り扱い 複数を同時に取り扱うことが出来る
ローカル or グローバル ローカル グローバル useContext() と一緒に取り扱う

# 🔷 useContext とは

React コンポーネントのツリーに対して「グローバル」とみなすデータについて利用するように設計されています。 コンポーネントの再利用をより難しくする為、慎重に利用しなくてはなりません。

Context によってコンポーネントツリー間におけるデータの橋渡しについて、すべての階層ごとに渡す必要性がなくなり、props バケツリレーをしなくても下の階層で Context に収容されているデータにアクセスできるようになりました。

useContext とは、Context 機能をよりシンプルに使えるようになった機能。 親から Props で渡されていないのに、Context に収容されているデータへよりシンプルにアクセスできるというものです。

[React] useContextの使い方を改めて確認してみた


  • without useReducer
const { useState } = React;

function Child(props) {
  const { count, setCount } = props;
  return (
    <>
      <h2>Child - {count}</h2>
      <button onClick={() => setCount(count + 1)}>add</button>
    </>
  );
}

function Parent(props) {
  const { count, setCount } = props;
  return (
    <>
      <h2>Parent - {count}</h2>
      <Child count={count} setCount={setCount} />
    </>
  );
}

function MyApp() {
  const [count, setCount] = useState(0);
  return (
    <>
      <h2>Root - {count}</h2>
      <Parent count={count} setCount={setCount} />
    </>
  );
}

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

The above code leads to the problem that the more nested levels are, the more props are passed, and the more difficult the code is to maintain.

  • useReducer 使わない場合
const { useState, createContext, useContext } = React;

// 1. Create a context object
const MyContext = createContext();

function Child() {
  // 3. Retrieve values from the context object using useContext and destructure the object
  const { count, setCount } = useContext(MyContext);
  return (
    <>
      <h2>Child - {count}</h2>
      <button onClick={() => setCount(count + 1)}>add</button>
    </>
  );
}

function Parent() {
  // const { count, setCount } = props;
  // 4. Retrieve values from the context object using useContext and destructure the object
  const { count } = useContext(MyContext);
  return (
    <>
      <h2>Parent - {count}</h2>
      {/* <Child count={count} setCount={setCount} />
          Child component can access context values directly */}

      <Child/>
    </Child>
  );
}

function MyApp() {
  const [count, setCount] = useState(0);
  return (
    <>
        {/* 2. Configure the context provider and pass the value.
           The 'value' prop contains the data to be passed, and all child components,
           including nested ones, can access it using useContext. */}
      <MyContext.Provider value={{ count, setCount }}>
        <h2>Root - {count}</h2>
        {/* Parent component can access context values directly */}
        <Parent />
      </MyContext.Provider>
    </>
  );
}

const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
root.render(<MyApp />);
  • useReducer と useContext を使わない場合
const { useState, createContext, useContext, useReducer } = React;

// Define the initial state and reducer function
const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    default:
      return state;
  }
}

// Create a context object
const MyContext = createContext();

function Child() {
  // Retrieve values from the context object using useContext and destructure the object
  const { state, dispatch } = useContext(MyContext);
  return (
    <>
      <h2>Child - {state.count}</h2>
      <button onClick={() => dispatch({ type: "increment" })}>add</button>
    </>
  );
}

function Parent() {
  // Retrieve values from the context object using useContext and destructure the object
  const { state } = useContext(MyContext);
  return (
    <>
      <h2>Parent - {state.count}</h2>
      {/* Child component can access context values directly */}
      <Child />
    </>
  );
}

function MyApp() {
  // Use useReducer to manage state
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      {/* Configure the context provider and pass the value */}
      <MyContext.Provider value={{ state, dispatch }}>
        <h2>Root - {state.count}</h2>
        {/* Parent component can access context values directly */}
        <Parent />
      </MyContext.Provider>
    </>
  );
}

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

# 🔷 useEffect とは

'useEffect' を使うと、'useEffect' に渡された関数はレンダーの結果が画面に反映された後に動作します。 つまり 'useEffect' とは、「関数の実行タイミングを React のレンダリング後まで遅らせる 'hook'」です。

副作用の処理(DOM の書き換え、変数代入、API 通信など UI 構築以外の処理)を関数コンポーネントで扱えます。 クラスコンポーネントでのライフサイクルメソッドに当たります。

'componentDidMount' 'componentDidUpdate' 'componentWillUnmount'

副作用フックの利用法 (opens new window)

# ▫️ 副作用を実行、制御するために useEffect を利用する

useEffect()の基本構文は以下の通りです。関数コンポーネントのトップレベルで宣言します。

useEffect(() => {
  /_ 第 1 引数には実行させたい副作用関数を記述_/;
  console.log("副作用関数が実行されました!");
}, [依存する変数の配列]); // 第 2 引数には副作用関数の実行タイミングを制御する依存データを記述

第 2 引数を指定することにより、第 1 引数に渡された副作用関数の実行タイミングを制御することができます。React は第 2 引数の依存配列の中身の値を比較して、副作用関数をスキップするかどうかを判断します。

# ▫️ 執行時機

可以把useEffect 函數卡奴走 componentDidiMount、componentDidUpdate、componentWillUnmount 三個生命週期函數的組合體。

  • useEffect(() => {}); componentDidMount + componentDidUpdate;
  • useEffect(() => {}, []); componentDidMount;
  • useEffect(() => () => {}); componentWillUnmount;

以下是 useEffect 的理解:

  1. useEffect(() => {}); - componentDidMount + componentDidUpdate:

    • この形式の useEffect は、コンポーネントのレンダリング後に実行され、コンポーネントが更新されるたびにも実行されます。
    • componentDidMountcomponentDidUpdate の組み合わせに相当します。
    useEffect(() => {
      // ここにあるコードはコンポーネントのレンダリング後および更新時に実行されます
    });
    
  2. useEffect(() => {}, []); - componentDidMount:

    • この形式の useEffect は、コンポーネントのレンダリング後にのみ実行され、コンポーネントの更新時には実行されません。
    • componentDidMount だけが実行された場合と同じです。
    useEffect(() => {
      // ここにあるコードはコンポーネントのレンダリング後に一度だけ実行されます
    }, []);
    
  3. useEffect(() => () => {}); - componentWillUnmount:

    • この形式の useEffect では、返り値として返された関数はコンポーネントがアンマウント(消える)される際に実行され、componentWillUnmount に相当します。
    • 返り値として返された関数はコンポーネントがアンマウントされる前に実行され、リソースのクリーンアップや購読の解除に使用できます。
    useEffect(() => {
      // ここにあるコードはコンポーネントのレンダリング後に実行されます
    
      // 返り値として返された関数はコンポーネントがアンマウントされる前に実行されます
      return () => {
        // リソースのクリーンアップや購読の解除などのコード
      };
    }, []);
    

# ▫️ useEffect の使い方

  1. 為 window 對象添加滾動事件 - componentDidMount + componentDidUpdate
import React, { useEffect } from "react";

function App() {
  function onScroll() {
    console.log("onScroll");
  }
  useEffect(() => {
    // 組件渲染後,添加滾動事件
    window.addEventListener("scroll", onScroll);
    return () => {
      // 組件卸載時,移除滾動事件
      window.removeEventListener("scroll", onScroll);
    };
  }, []);
  return <div>App</div>;
}

export default App;
  1. 定時器:掛載定時器,要在組件渲染後才能掛載,所以要在 useEffect 中掛載定時器
import React, { useEffect, useState } from "react";
import ReactDom from "react-dom";

function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const timerId = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => {
      clearInterval(timerId);
    };
  }, []);
  return (
    <div>
      <span>{count}</span>
      <button onClick={() => ReactDOM.unmountComponentAtNode()}>
        卸載組件
      </button>
    </div>
  );
}

# ▫️ useEffect 依賴項

依存配列の値が変化した場合のみ副作用関数を実行させる useEffect()の第2引数に[count]を渡すと、count に変化があったときだけ副作用関数を実行します。

import React, { useState, useEffect } from "react";
import { makeStyles } from "@material-ui/core/styles";
import ButtonGroup from "@material-ui/core/ButtonGroup";
import Button from "@material-ui/core/Button";
import Input from "@material-ui/core/Input";

const useStyles = makeStyles((theme) => ({
  root: {
    "& > *": {
      margin: theme.spacing(1),
    },
  },
}));

const EffectFunc = () => {
  const classes = useStyles();
  const [count, setCount] = useState(0);
  const [name, setName] = useState({
    lastName: "",
    firstName: "",
  });
  useEffect(() => {
    document.title = `${count}回クリックされました`;
  }, [count]);

  return (
    <>
      <p>{`${count}回クリックされました`}</p>
      <ButtonGroup color="primary" aria-label="outlined primary button group">
        <Button onClick={() => setCount((prev) => prev + 1)}>ボタン</Button>
        <Button onClick={() => setCount(0)}>リセット</Button>
      </ButtonGroup>
      <p>{`私の名前は${name.lastName} ${name.firstName}です`}</p>
      <form className={classes.root} noValidate autoComplete="off">
        <Input
          placeholder="姓"
          value={name.lastName}
          onChange={(e) => {
            setName({ ...name, lastName: e.target.value });
          }}
        />
        <Input
          placeholder="名"
          value={name.firstName}
          onChange={(e) => {
            setName({ ...name, firstName: e.target.value });
          }}
        />
      </form>
    </>
  );
};

export default EffectFunc;

# 🔷 useRef とは

useRef 是 React 提供的一个 Hook,其主要特点是用于在函数组件中保存和访问可变的引用。以下是 useRef 的主要特点:

  1. 保存可变的引用: useRef 创建一个对象,该对象的 current 属性可以被赋值为任何可变的值。这使得在整个组件的生命周期内都可以访问和修改该值,而不引发组件重新渲染。

  2. 不触发重新渲染:useRefcurrent 属性被修改时,不会触发组件的重新渲染。这使得它非常适合存储不需要引起视图更新的数据,例如 DOM 元素引用、定时器 ID、或其他持久性数据。

  3. 持久性数据存储: useRef 创建的引用是持久的,即使组件重新渲染,引用仍然保持不变。这与 useState 不同,后者在重新渲染时会创建新的状态。

  4. 访问 DOM 元素: useRef 常用于引用 DOM 元素。当 ref 属性附加到 JSX 元素上时,useRefcurrent 属性将持有该元素的引用。

  5. 适用于保存上一个值: useRef 也常用于保存上一个状态值,因为它不会引发重新渲染,可以在渲染期间保留其值。

下面是一个简单的示例,演示了 useRef 保存 DOM 元素引用的情况:

import { useRef, useEffect } from "react";

const ExampleComponent = () => {
  const inputRef = useRef < HTMLInputElement > null;

  useEffect(() => {
    // 在组件挂载后,通过 inputRef.current 访问 input 元素
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, []);

  return <input ref={inputRef} />;
};

在这个例子中,inputRef 用于引用 input 元素,而不需要通过 document.getElementById 或其他方式获取。

key characteristics of the useRef hook:

  1. Mutable Reference: useRef creates a mutable object that persists across renders.

  2. Preservation of Value: The value assigned to current property of the useRef object persists between renders.

  3. Doesn't Trigger Re-renders: Updating the current property of a useRef does not trigger a re-render of the component.

  4. Preserving Values Without Re-renders: It's useful for holding values that need to be preserved between renders but don't require triggering a re-render.

  5. Common Use Cases:

  • Managing and maintaining references to DOM elements.
  • Holding values that should persist without causing re-renders.
  • Storing mutable values without triggering component updates.
  1. When to Use: useRef is useful for managing and maintaining references to DOM elements, storing mutable values that don't trigger re-renders, and storing mutable values that you want to persist across renders.

  2. When Not to Use: useRef should not be used to manage state or trigger re-renders.

Note: useRef is not a replacement for useState. Always use useState when you need to manage state that changes over time. useRef is useful for storing mutable values that don't change over time, such as a reference to a DOM node or a value computed in a previous render.

Note: useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

Note: useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.

# 🔷 useCallback とは

# 🔺 Example

在實際應用中,useCallback 的使用場景通常涉及到優化性能,特別是在 React 應用中傳遞回調函數給子組件的情況。以下是一個實際應用的例子:

假設你有一個點餐應用(類似 SkipTheDishes),你有一個頁面顯示用戶的訂單列表。每個訂單都有一個狀態,比如"已下單"、"準備中"、"配送中"等。你的應用可能會有一個組件來顯示單個訂單的詳細信息,並且你希望在點擊"更新狀態"按鈕時觸發一個回調函數來更新訂單的狀態。

import React, { useState, useCallback } from "react";

const OrderDetails = ({ order, onUpdateStatus }) => {
  // ...

  const handleUpdateStatus = useCallback(() => {
    // 在這裡處理更新訂單狀態的邏輯
    onUpdateStatus(order.id, newStatus);
  }, [onUpdateStatus, order.id, newStatus]);

  return (
    <div>
      {/* 顯示訂單詳細信息 */}
      <p>Order ID: {order.id}</p>
      <p>Status: {order.status}</p>

      {/* 更新狀態按鈕 */}
      <button onClick={handleUpdateStatus}>Update Status</button>
    </div>
  );
};

const OrderList = ({ orders }) => {
  // ...

  const handleUpdateStatus = useCallback((orderId, newStatus) => {
    // 在這裡處理更新訂單狀態的邏輯
    // 這個函數可能會在 OrderDetails 組件中使用,因此使用 useCallback 進行優化
    console.log(`Updating status of order ${orderId} to ${newStatus}`);
  }, []);

  return (
    <div>
      <h2>Order List</h2>
      {/* 顯示訂單列表 */}
      {orders.map((order) => (
        <OrderDetails
          key={order.id}
          order={order}
          onUpdateStatus={handleUpdateStatus}
        />
      ))}
    </div>
  );
};

const SkipTheDishesApp = () => {
  const [orders, setOrders] = useState(/* 初始訂單數據 */);

  // ...

  return <OrderList orders={orders} />;
};

在這個例子中,handleUpdateStatus 函數是一個回調函數,當你點擊"Update Status"按鈕時,這個函數會被觸發。由於它被傳遞給 OrderDetails 組件,我們使用 useCallback 來確保它只在 onUpdateStatusorder.idnewStatus 改變時才重新創建。這樣可以避免不必要的組件重新渲染,提高應用性能。


# 🔷 useMemo とは

Understanding useMemo and useCallback-Josh (opens new window)

# 🔶 Hooks compare

# ▫️ useCallback vs useMemo

useMemouseCallback接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于 useMemo 返回的是函数运行的结果,useCallback 返回的是函数,这个回调函数是经过处理后的也就是说父组件传递一个函数给子组件的时候,由于是无状态组件每一次都会重新生成新的 props 函数,这样就使得每一次传递给子组件的函数都发生了变化,这时候就会触发子组件的更新,这些更新是没有必要的,此时我们就可以通过 useCallback 来处理此函数,然后作为 props 传递给子组件。

/* 用react.memo */
const DemoChildren = React.memo((props) => {
  /* 只有初始化的时候打印了 子组件更新 */
  console.log("子组件更新");
  useEffect(() => {
    props.getInfo("子组件");
  }, []);
  return <div>子组件</div>;
});

const DemoUseCallback = ({ id }) => {
  const [number, setNumber] = useState(1);
  /* 此时usecallback的第一参数 (sonName)=>{ console.log(sonName) }
     经过处理赋值给 getInfo */
  const getInfo = useCallback(
    (sonName) => {
      console.log(sonName);
    },
    [id]
  );
  return (
    <div>
      {/* 点击按钮触发父组件更新 ,但是子组件没有更新 */}
      <button onClick={() => setNumber(number + 1)}>增加</button>
      <DemoChildren getInfo={getInfo} />
    </div>
  );
};