redux-toolkit使用指南

260 阅读2分钟

官网地址:redux.js.org

以下是我使用redux-toolkit库的一个目录结构:

.
├── README.md
├── package.json
├── tsconfig.json
├── next.config.mjs
└── app
		  ├── layout.tsx
		  └── page.tsx
		  └── storeProvider.tsx
└── store
     	├── features
     	│   ├── counter.ts
      		│   ├── counterSlice.ts
      		│   ├── counterApi.ts
      └── index.ts
      └── hooks.ts
      └── createAppSlice.ts

附上所用版本:

"next": "14.1.4",
"react": "^18",
"@reduxjs/toolkit": "^2.2.3",
"react-redux": "^9.1.0"

安装

在项目中使用redux,一般要装 reduxreact-reduxredux-thunk 三种插件。

@reduxjs/toolkit 相当于安装了redux的同时也内置了thunk中间件。

yarn add @reduxjs/toolkit
yarn add react-redux

使用

redux主要分为三部分,storeactionreducer

创建store

// store/index 
// 在入口文件导出创建store的函数
import { combineSlices, configureStore } from "@reduxjs/toolkit";
import { counterSlice } from "./features/counter/counterSlice";

// combineSlices相当于redux的combineReducers,把reducer组合起来
const rootReducer = combineSlices(counterSlice);

export const makeStore = () => {
  // 通过configureStore创建store,相当于redux的createStore
  return configureStore({
    // 参数传reducer就可以
    reducer: rootReducer,
    // middleware默认就是thunk。使用thunk的话下面可以不写,可以自定义
    middleware: (getDefaultMiddleware) => {
       return getDefaultMiddleware();
    },
  });
};

创建slice

slice将reducer和action封装到了一起,所有的相关操作都独立在一个文件中完成。

// store/features/counter/counterSlice
import { createAppSlice } from "@/store/createAppSlice";
import type { AppThunk } from "@/store/index";
import type { PayloadAction } from "@reduxjs/toolkit";
import { fetchCount } from "./counterAPI";

export interface CounterSliceState {
  value: number;
  status: "idle" | "loading" | "failed";
}
const initialState: CounterSliceState = {
  value: 0,
  status: "idle",
};

// createAppSlice是自定义文件中导出的buildCreateSlice函数,它可以使slice中的action具有thunk的功能
// 你仍然可以在slice之外导出一个副作用action
export const counterSlice = createAppSlice({
  // 命名空间,可以自动的把每一个action进行独立,解决了action的type出现同名的文件
  // 在使用的时候默认会把使用name/actionName
  name: "counter",
  // state数据的初始值
  initialState,
  reducers: (create) => ({
    increment: create.reducer((state) => {
      state.value += 1;
    }),
    decrement: create.reducer((state) => {
      state.value -= 1;
    }),
    incrementByAmount: create.reducer(
      (state, action: PayloadAction<number>) => {
        state.value += action.payload;
      }
    ),
  	// 这是通过create.asyncThunk创建一个副作用action
    incrementAsync: create.asyncThunk(
      async (amount: number) => {
        const response = await fetchCount(amount);
        // 这里的返回值会进入到fulfilled的action.payload中
        return response.data;
      },
      {
        pending: (state) => { state.status = "loading" },
        fulfilled: (state, action) => {
          state.status = "idle";
          state.value += action.payload;
        },
        rejected: (state) => { state.status = "failed" },
      }
    ),
  }),
  selectors: {
    selectCount: (counter) => counter.value,
    selectStatus: (counter) => counter.status,
  }
});

// 导出action
// 尤其是slice中的副作用action,用于组件中使用
export const { decrement, increment, incrementByAmount, incrementAsync } =
  counterSlice.actions;
export const { selectCount, selectStatus } = counterSlice.selectors;

// 自定义的副作用action
export const incrementByAsync =
  (amount: number): AppThunk =>
  (dispatch, getState) => {
    setTimeout(() => {
      dispatch(incrementByAmount(amount));
    }, 1000);
  };

在组件中使用

// app/layout
import { StoreProvider } from "./storeProvier";
import "./globals.css";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    // 在最外层包裹一层provider
    <StoreProvider>
      <html lang="en">
        <body className={inter.className}>{children}</body>
      </html>
    </StoreProvider>
  );
}
// app/page
"use client";

import Link from "next/link";
import { useEffect, useRef } from "react";
import { useAppDispatch, useAppSelector } from "@/store/hooks";
import {
  incrementAsync,
  incrementByAsync,
} from "@/store/features/counter/counterSlice";

export default function Home() {
  // 使用dispatch
  const dispatch = useAppDispatch();
  // 使用store
  const counter = useAppSelector((state) => state.counter) || {};

  const onChangeCounter = () => {
    // 纯action => counter就是之前定义slice时的name,再加上reducer中action的名字
    dispatch({ type: "counter/increment" });
    // 副作用action => incrementAsync是slice中定义的函数action
    dispatch(incrementAsync(5))
    // 副作用action => 可以写但不推荐,业务逻辑多了会很复杂,redux的目的就是让你专注于偏平的action
    dispatch(
      ((value) => (dispatch) => {
        setTimeout(() => {
          dispatch({
            type: "counter/incrementByAmount",
            payload: value,
          });
        }, 1000);
      })(2)
    );
    // 副作用action => 同上
    setTimeout(() => {
      dispatch({ type: "counter/increment" });
    }, 1000)
  };

  return (
    <main className="flex min-h-screen flex-col">
      <code className="font-mono font-bold">welcome to next!</code>
      <div>当前counter的值为: {counter.value}</div>
      <Link href="#" onClick={onChangeCounter}>
        点击改变counter的值
      </Link>
      <Link href="/about">关于</Link>
    </main>
  );
}

我之前用过redux + thunk和react + saga,光说配置其实感觉大同小异。

不知道大家觉得redux-toolkit是否好用呢。

附上项目地址:快! 点我