Redux 思想
刚开始接触 Redux 时,我认为这仅仅是一个管理 state 的小工具。我想象了一个巨大的 component,它的 state 异常繁杂,数组套数组,对象套对象。这时 redux 把编码焦点从 state 上转移,只重视 action,如:在 todolist 的最后加上"写作业"一项,只需 dispatch 一个{ type: “ADD_TODO”, text: “写作业”}的 action 就够了,避免直接和 state 打交道。
更深入一些后,我了解到 Redux 可以解决“全局状态”问题。状态提升究竟繁琐了,对于一些 state,不知道哪个边缘的小组件突然要用到它,而这将会带来一场很大的变动。
随后,“时间旅行”这一样例给我带来了不小的惊讶,不止是能看到当前的 state,每一次 state 的变更都被记录以备查验,且随时可以跳转到任意时刻的 state 中。这背后当然是不可变更新思想起了作用,不是直接更新 state,而是更新副本。
install
npm install @reduxjs/toolkit react-redux
create Redux Store
src/app/store.js
import { configureStore } from '@reduxjs/toolkit';
export default configureStore({
reducer: {},
});
使用 Provider 将整个 React 组件树包裹起来
将 <Provider> 放置在 index 的顶层包裹,使得整个组件树都在其中。store 是刚才定义的 configureStore。<Provider> 组件使 Redux store 可供任何嵌套组件使用。
src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import store from './app/store';
import { Provider } from 'react-redux';
// As of React 18
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
创建 redux state slice
“slice”是应用程序中单个功能的 Redux reducer 逻辑和操作的集合,通常在单个文件中一起定义。该名称来自于将根 Redux 状态对象拆分为多个 state “切片”。
features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
// 这里看似是直接修改了 state,事实上背后进行了不可变更新
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
在具体的 React 组件中使用 store 的数据
useSelector
从 store 的 state 中读取一个值并订阅更新,而 useDispatch
返回 store 的 dispatch 方法来让你 dispatch action。
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
decrement,
increment,
incrementByAmount,
incrementAsync,
selectCount,
} from './counterSlice';
import styles from './Counter.module.css';
export function Counter() {
const count = useSelector(selectCount);
const dispatch = useDispatch();
return (
<div>
<div className={styles.row}>
<button
className={styles.button}
aria-label='Increment value'
onClick={() => dispatch(increment())}
>
+
</button>
<span className={styles.value}>{count}</span>
<button
className={styles.button}
aria-label='Decrement value'
onClick={() => dispatch(decrement())}
>
-
</button>
</div>
</div>
);
}