跳至主要内容

[Redux] Hooks


自從 v7.1 版以後提供了 hooks 的函式,不再需要仰賴 connect(), 讓取得狀態與 dispatch action 改變 redux 中的狀態變得更容易,也更清楚明瞭


流程

  1. 使用者觸發 action
  2. 經過中介層,看有沒有事件要觸發
  3. 回到 Reducer 更改數據
  4. 最終回去 state 去更動 UI


前置作業

通過 createStore 將 state 存入 store, 再將 store 物件透過 Provider 提供給 App 元件使用

const store = createStore(rootReducer)

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

useSelector & useDispatch

  • useSelector : 能夠取得 redux store 中的狀態。
  • useDispatch : 能夠 dispatch action 至 redux store。

useSelector

允許我們直接從 Redux store 中的狀態提取數據到元件中。
import React from "react";
import { useSelector } from "react-redux";

export const CounterComponent = () => {
const counter = useSelector((state) => state.counter);

return <div>{counter}</div>;
};
信息
  1. useSelector 會訂閱 store, 當 action 被 dispatched 的時候, 會運行 selector。
  2. 你可能在一個元件內調用 useSelector 多次, 但是對 useSelector()的每個調用都會創建 redux store 的單個訂閱。 由於 react-reduxv7 版本使用的 react 的批量(batching)更新行為, 造成同個元件中,多次 useSelector 返回的值只會 re-render 一次, 不會導致所有元件重新渲染。

useSelector 掌握渲染的時機

在元件 A 中使用 useSelector() 取得 redux store 中 count 的數值, 當有一個 action 被 dispatch 到 redux store 時, 會用 equalityFn 嚴格判別 count 是否已經改變了, 如果改變了,才強制渲染該元件,元件 A 就會重新取得 count 的值。


useDispatch

直接回傳一個 dispatch 方法,可以直接透過它觸發 Reducer

簡單範例
import React from "react";
import { useDispatch } from "react-redux";

export const CounterComponent = ({ value }) => {
const dispatch = useDispatch();

return (
<div>
<span>{value}</span>
<button onClick={() => dispatch({ type: "increment-counter" })}>
Increment counter
</button>
</div>
);
};

父元件傳給子元件
  • 建議使用 useCallback 對其進行記憶,否則子元件接收 props 之後, 可能會產生不必要的 re-render。
import React, { useCallback } from "react";
import { useDispatch } from "react-redux";

export const CounterComponent = ({ value }) => {
const dispatch = useDispatch();
const incrementCounter = useCallback(
() => dispatch({ type: "increment-counter" }),
[dispatch],
);

return (
<div>
<span>{value}</span>
<MyIncrementButton onIncrement={incrementCounter} />
</div>
);
};

export const MyIncrementButton = React.memo(({ onIncrement }) => (
<button onClick={onIncrement}>Increment counter</button>
));

component 內自己 dispatch 方法
  • 使用 dispatch 方法會很穩定,
  • 將 dispatch 作為它的依賴項之一傳遞給我們的 useEffect 函數, 因為這不會改變。傳遞 dispatch 這將防止無限循環並確保此函數僅被調用一次。
./store/todos/actionCreators
export const fetchTodos = () => async () => {
dispatch(setLoading({ status: true, tip: "加載中..." }));

const response = await fetch("/fetchTodos", () => dbTodos);

dispatch({ type: SET_TODOS, payload: response });

dispatch(setLoading({ status: false, tip: "" }));
};

TodoApp.js

import { useDispatch } from 'react-redux'
import {fetchTodos} from './store/todos/actionCreators'

const TodoApp = () => {
const dispatch = useDispatch()

useEffect(() => {
dispatch(fetchTodos())
}, [dispatch])

...
}


useStore

這個 Hook 返回 redux <Provider>元件的所有 store 對象

import React from "react";
import { useStore } from "react-redux";

export const CounterComponent = ({ value }) => {
const store = useStore();

// 這只是個範例! 盡量不要在專案這樣做.
// 使用 useStore 可以直接存取整個 Redux store,但這種做法不推薦
// 因為它違背了 Redux 的核心原則之一——單一數據源 (Single Source of Truth),而且可能導致性能問題和不必要的重新渲染。
// 應該儘量避免在元件中直接存取整個 Redux store,而是應該使用 connect 或 useSelector hook 來選取所需的狀態片段,以保持應用程序的可預測性和可維護性
return <div>{store.getState()}</div>;
};

教材在此,可以自己去玩玩

Codesandbox 💪

https://codesandbox.io/s/infallible-silence-10lmx?file=/src/Main.js


參考資料