Less is more

  • 我们反对繁琐且不必要的各种模板
  • redux的核心功能必须使用大量的模板
  • 为了降低redux使用的复杂性,减少模板代码的使用,redux-toolkit( RTX )应运而生

redux-toolkit的基本使用

官方文档
GitHub

安装

  1. yarn add @reduxjs/toolkit

创建slice

  1. import {createSlice, PayloadAction} from '@reduxjs/toolkit'
  2. interface ProductDetailState {
  3. loading: boolean,
  4. error: string | null,
  5. data: any
  6. }
  7. const initialState: ProductDetailState = {
  8. loading: true,
  9. error: null,
  10. data: null
  11. }
  12. export const productDetailSlice = createSlice({
  13. name: 'productDetail',
  14. initialState,
  15. reducers: {
  16. fetchStart: (state) => {
  17. // return {...state, loading: true}
  18. state.loading = true
  19. },
  20. fetchSuccess: (state, action) => {
  21. state.loading = false
  22. state.data = action.payload
  23. state.error = null
  24. },
  25. fetchFail: (state, action: PayloadAction<string|null>) => {
  26. state.error = action.payload
  27. state.loading = false
  28. }
  29. }
  30. })
  • reducers 将 action 和 reducer 结合在一起了
  • 因为RTK使用了 immer 这个依赖,在 reducer 中可以直接修改 state

    改用RTK的combineReducer

    ```tsx import { createStore,applyMiddleware } from ‘redux’ import languageReducer from ‘./language/languageReducer’ import recommendProductsReducer from ‘./recommendProducts/recommendProductsReducer’ import thunk from ‘redux-thunk’ import { actionLog } from ‘./middlewares/actionLog’

import { productDetailSlice } from ‘./productDetail/slice’ import { combineReducers } from ‘@reduxjs/toolkit’

const rootReducer = combineReducers({ language: languageReducer, recommendProducts: recommendProductsReducer, productDetail: productDetailSlice.reducer })

const store = createStore(rootReducer, applyMiddleware(thunk, actionLog)) export type RootState = ReturnType export type AppDispatch = typeof store.dispatch export default store

  1. <a name="zWFut"></a>
  2. #### 在组件中使用state和dispatch
  3. ```tsx
  4. // ...
  5. import { useSelector, useDispatch } from "../../redux/hooks";
  6. import { productDetailSlice } from "../../redux/productDetail/slice";
  7. export const DetailPage: React.FC = () => {
  8. const { touristRouteId } = useParams();
  9. // const [loading, setLoading] = useState<boolean>(true);
  10. // const [product, setProduct] = useState<any>(null);
  11. // const [error, setError] = useState<string | null>(null);
  12. const loading = useSelector(state => state.productDetail.loading)
  13. const error = useSelector(state => state.productDetail.error)
  14. const product = useSelector(state => state.productDetail.data)
  15. const dispatch = useDispatch()
  16. useEffect(() => {
  17. const fetchData = async () => {
  18. dispatch(productDetailSlice.actions.fetchStart())
  19. try {
  20. const { data } = await axios.get(
  21. `/api/touristRoutes/${touristRouteId}`
  22. );
  23. dispatch(productDetailSlice.actions.fetchSuccess(data))
  24. } catch (error:any) {
  25. dispatch(productDetailSlice.actions.fetchFail(error.message))
  26. }
  27. };
  28. fetchData();
  29. }, []);
  30. // ...
  31. }

RTK中使用thunk处理异步请求

在RTK中使用thunk处理异步请求,必须使用 configureStorecreateAsyncThunk

使用 configureStore 代替 createStore

  1. import { createStore,applyMiddleware } from 'redux'
  2. import languageReducer from './language/languageReducer'
  3. import recommendProductsReducer from './recommendProducts/recommendProductsReducer'
  4. import thunk from 'redux-thunk'
  5. import { actionLog } from './middlewares/actionLog'
  6. import { productDetailSlice } from './productDetail/slice'
  7. import { combineReducers, configureStore } from '@reduxjs/toolkit'
  8. const rootReducer = combineReducers({
  9. language: languageReducer,
  10. recommendProducts: recommendProductsReducer,
  11. productDetail: productDetailSlice.reducer
  12. })
  13. // const store = createStore(rootReducer, applyMiddleware(thunk, actionLog))
  14. // 接受一个对象作为参数
  15. const store = configureStore({
  16. reducer: rootReducer,
  17. // getDefaultMiddleware获取默认中间件
  18. middleware: (getDefaultMiddleware) => [...getDefaultMiddleware(), actionLog],
  19. // 浏览器使用devTools插件可以查看redux状态
  20. devTools: true,
  21. })
  22. export type RootState = ReturnType<typeof store.getState>
  23. export type AppDispatch = typeof store.dispatch
  24. export default store

image.png

使用 createAsyncThunk 定义异步action creator

  1. import axios from 'axios';
  2. import {createAsyncThunk, createSlice, PayloadAction} from '@reduxjs/toolkit'
  3. interface ProductDetailState {
  4. loading: boolean,
  5. error: string | null,
  6. data: any
  7. }
  8. const initialState: ProductDetailState = {
  9. loading: true,
  10. error: null,
  11. data: null
  12. }
  13. export const getProductDetail = createAsyncThunk(
  14. 'productDetailSlice/getProductDetail', // type
  15. async (touristRouteId: string, thunkAPI) => {
  16. thunkAPI.dispatch(productDetailSlice.actions.fetchStart())
  17. try {
  18. const { data } = await axios.get(
  19. `/api/touristRoutes/${touristRouteId}`
  20. );
  21. thunkAPI.dispatch(productDetailSlice.actions.fetchSuccess(data))
  22. } catch (error:any) {
  23. thunkAPI.dispatch(productDetailSlice.actions.fetchFail(error.message))
  24. }
  25. }
  26. )
  27. export const productDetailSlice = createSlice({
  28. name: 'productDetail',
  29. initialState,
  30. reducers: {
  31. fetchStart: (state) => {
  32. // return {...state, loading: true}
  33. state.loading = true
  34. },
  35. fetchSuccess: (state, action) => {
  36. state.loading = false
  37. state.data = action.payload
  38. state.error = null
  39. },
  40. fetchFail: (state, action: PayloadAction<string|null>) => {
  41. state.error = action.payload
  42. state.loading = false
  43. }
  44. }
  45. })

在组件中 dispatch

  1. useEffect(() => {
  2. const fetchData = async () => {
  3. dispatch(getProductDetail(touristRouteId))
  4. };
  5. fetchData();
  6. }, []);

使用 promise 生命周期

createAsyncThunk会返回 3 种状态,将这三种状态对应的操作写在 slice 的 extraReducers 中,在对应的 promise生命周期就会执行对应的操作, createAsyncThunk中只需要有返回值(promise), 不需要有dispatch操作

  1. import axios from 'axios';
  2. import {createAsyncThunk, createSlice, PayloadAction} from '@reduxjs/toolkit'
  3. interface ProductDetailState {
  4. loading: boolean,
  5. error: string | null,
  6. data: any
  7. }
  8. const initialState: ProductDetailState = {
  9. loading: true,
  10. error: null,
  11. data: null
  12. }
  13. export const getProductDetail = createAsyncThunk(
  14. 'productDetailSlice/getProductDetail', // type
  15. async (touristRouteId: string, thunkAPI) => {
  16. const { data } = await axios.get(
  17. `/api/touristRoutes/${touristRouteId}`
  18. );
  19. return data
  20. }
  21. )
  22. export const productDetailSlice = createSlice({
  23. name: 'productDetail',
  24. initialState,
  25. reducers: {
  26. // fetchStart: (state) => {
  27. // // return {...state, loading: true}
  28. // state.loading = true
  29. // },
  30. // fetchSuccess: (state, action) => {
  31. // state.loading = false
  32. // state.data = action.payload
  33. // state.error = null
  34. // },
  35. // fetchFail: (state, action: PayloadAction<string|null>) => {
  36. // state.error = action.payload
  37. // state.loading = false
  38. // }
  39. },
  40. extraReducers: {
  41. [getProductDetail.pending.type]: (state) => {
  42. // return {...state, loading: true}
  43. state.loading = true
  44. },
  45. [getProductDetail.fulfilled.type]: (state, action) => {
  46. state.loading = false
  47. state.data = action.payload
  48. state.error = null
  49. },
  50. [getProductDetail.rejected.type]: (state, action: PayloadAction<string|null>) => {
  51. state.error = action.payload
  52. state.loading = false
  53. }
  54. }
  55. })