[Redux] Hooks
自從 v7.1 版以後提供了 hooks 的函式,不再需要仰賴 connect(), 讓取得狀態與 dispatch action 改變 redux 中的狀態變得更容易,也更清楚明瞭
流程
- 使用者觸發 action
- 經過中介層,看有沒有事件要觸發
- 回到 Reducer 更改數據
- 最終回去 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>;
};
信息
- useSelector 會訂閱 store, 當 action 被 dispatched 的時候, 會運行 selector。
- 你可能在一個元件內調用 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