背景
如果你的React项目中使用react hook、redux、redux-thunk,可能你需要用 redux-toolkit (以下简称RTK)优化你的项目结构,它看起来可以这么清爽
Redux 使用常见问题
- 配置复杂,devtool...
- 模板代码太多,创建constant,action,reducer...
- 需要添加很多依赖包,如redux-thunk、immer...
# 优化前
/counter
constants.ts
actions.ts
reducer.ts
saga.ts
index.tsxÏ
# 优化后
/counter
slice.ts
index.tsx
RTK干了哪些事?
configureStore()
包裹createStore,并集成了redux-thunk
、Redux DevTools Extension
,默认开启createReducer()
创建一个reducer,action type 映射到 case reducer 函数中,不用写switch-case,并集成immer
createAction()
创建一个action,传入动作类型字符串,返回动作函数createSlice()
创建一个slice,包含 createReducer、createAction的所有功能createAsyncThunk()
创建一个thunk,接受一个动作类型字符串和一个Promise的函数- ...
怎么样使用
以 Counter Component 为例
新的项目
create-react-app 初始化项目,最受欢迎的脚手架之一
# 使用 redux-typescript 模板,推荐使用typescript
npx create-react-app react-rtk-ts --template redux-typescript
# 使用redux 模板
# npx create-react-app react-rtk-ts --template redux
老的项目
- 安装 @reduxjs/toolkit
# 使用 npm
npm install @reduxjs/toolkit
# 使用 yarn
yarn add @reduxjs/toolkit
- configureStore 替换 createStore
import React from "react";
import { render } from "react-dom";
- import { createStore } from "redux";
+ import { configureStore } from "@reduxjs/toolkit";
import { Provider } from "react-redux";
import App from "./components/App";
import rootReducer from "./reducers";
- const store = createStore(rootReducer);
+ const store = configureStore({
+ reducer: rootReducer,
+});
创建Action
# 创建 action
const increment = createAction('INCREMENT')
const decrement = createAction('DECREMENT')
# 创建reducer
const counter = createReducer(0, {
[increment]: state => state + 1,
[decrement]: state => state - 1
})
以上看起比原来结构上好一些,创建action、reducer方便了,但是看着还是不爽,action也可以去掉
创建Slice
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
increment: state => state + 1,
decrement: state => state - 1
}
})
# action
counterSlice.action;
# reducer
counterSlice.reducer;
counterSlice 看起起来像
{
name: "counter",
reducer: (state, action) => newState,
actions: {
increment: (payload) => ({type: "counter/increment", payload}),
increment: (payload) => ({type: "counter/increment", payload})
},
caseReducers: {
increment: (state, action) => newState,
increment: (state, action) => newState,
}
}
只需要createSlice,包含着 action,reducer的创建
创建 thunk
# 对于一部请求API,可以配合async/await使用
export const incrementAsync = (amount: number): AppThunk => dispatch => {
setTimeout(() => {
dispatch(incrementByAmount(amount));
}, 1000);
};
创建 selecter
# 配合 react-redux 中 useSelector hook使用
export const selectCount = (state: RootState) => state.counter.value;
完整 slice文件
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { AppThunk, RootState } from '../../app/store';
interface CounterState {
value: number;
}
const initialState: CounterState = {
value: 0,
};
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: state => {
state.value += 1;
},
decrement: state => {
state.value -= 1;
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export const incrementAsync = (amount: number): AppThunk => dispatch => {
setTimeout(() => {
dispatch(incrementByAmount(amount));
}, 1000);
};
export const selectCount = (state: RootState) => state.counter.value;
export default counterSlice.reducer;
页面使用
import React, { useState } 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();
const [incrementAmount, setIncrementAmount] = useState('2');
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 className={styles.row}>
<input
className={styles.textbox}
aria-label="Set increment amount"
value={incrementAmount}
onChange={e => setIncrementAmount(e.target.value)}
/>
<button
className={styles.button}
onClick={() =>
dispatch(incrementByAmount(Number(incrementAmount) || 0))
}
>
Add Amount
</button>
<button
className={styles.asyncButton}
onClick={() => dispatch(incrementAsync(Number(incrementAmount) || 0))}
>
Add Async
</button>
</div>
</div>
);
}
常见问题
1. Typescript 必须的吗?
不是,都0202年了,可以尝试ts了
2. React hook 必须的吗?
如果你用RTK的话,最好所有的库都用hook,最好带上ts
3. 不用 redux-thunk,我想用redux-saga?
可以通过getDefaultMiddleware({ thunk: false })
获取middleware,configureStore
引入 mmiddlewares
const middlewares = [...getDefaultMiddleware({ thunk: false }), sagaMiddleware];
const store = configureStore({
reducer: rootReducer,
middleware: middlewares,
});
4. 只创建xxxSlice.ts 文件就可以吗?这个文件会不会很大?
可以,文件大不取决于很多处理都放在一起,多封装util,还有一个原因是设计问题,state 过大
5. 使用 react-router 等路由的库,怎么使用?
推荐使用,@reach/router
和 redux-first-history
来管理路由,也支持hook
Demo地址
参考
redux-toolkit.js.org/introductio…
如果不对或者存在异议的地方,可以评论留言,及时改正