React TypeScript 应用中便捷使用Redux Toolkit方法详解

 更新时间:2022年11月22日 10:44:59   作者:BokFang  
这篇文章主要为大家介绍了React TypeScript 应用中便捷使用Redux Toolkit方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

本文介绍的主要内容是 Redux-Toolkit 在 React + TypeScript 大型应用中的实践,主要解决的问题是使用 createSlice 的前提下消费 redux 状态仍旧有点繁琐的问题。

阅读本文需要的前置知识:了解 React 、Redux-Toolkit 、TypeScript 的使用。

关于 Redux-Toolkit 提供的各种函数的使用,大家可以去官网 redux-toolkit.js.org/ 学习。

背景

前阵子接到一个任务:在使用 redux 作为状态管理工具的前提下,优化一下消费 redux 的步骤,并制定一套使用规范,让大家在开发这个项目消费 redux 状态时能按照规范来。

说到简化消费 redux 步骤,我第一时间想到的就是 redux 官方推荐的 Redux-Toolkit,于是我就去学习了一下 Redux-Toolkit。

了解完官网和网上各种文章后,我知道了 Redux-Toolkit 在项目中的使用,但是仍然有一个疑问:使用了 createSlice 后,仍然需要在项目的组件中使用 useDispatch 来更新状态,还是有点麻烦。对于组件使用者来说,有没有更方便的方式消费 redux 状态?

在网上逛来逛去,没找到有人发相关的文章,所以我准备自己动手试试看。

Redux-Toolkit 常规使用

我们先来看看目前使用 Redux-Toolkit 消费 redux 状态的方式。举个例子,假设我们现在的业务和蛋糕有关,有两个状态存在 redux,分别是 nameOfCake 和 numOfCakes,我们使用 createSlice 来创建 reducer 和 actions:

// 文件位置: app/src/store/reducers/cake.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
type InitialState = {
  numOfCakes: number;
  nameOfCake: string;
};
const initialState: InitialState = {
  numOfCakes: 20,
  nameOfCake: 'great cake',
};
const cakeSlice = createSlice({
  name: 'cake',
  initialState,
  reducers: {
    updateCakeNum: (state, action: PayloadAction<number>) => {
      state.numOfCakes = action.payload;
    },
    updateCakeName: (state, action: PayloadAction<string>) => {
      state.nameOfCake = action.payload;
    },
  },
});
export default cakeSlice.reducer;
export const { updateCakeNum, updateCakeName } = cakeSlice.actions;

接着我们在组件中消费:

// 文件位置: app/src/pages/Cake/components/CakeView/index.tsx
import { RootState } from '@/src/store';
import { useSelector, useDispatch } from 'react-redux';
import {
  useRecordReduxFunction,
  updateCakeNum,
  updateCakeName,
} from '@/src/store/reducers/cake';
export const CakeView = () => {
  const numOfCakes = useSelector((state: RootState) => state.cake.numOfCakes);
  const nameOfCake = useSelector((state: RootState) => state.cake.nameOfCake);
  const dispatch = useDispatch();
  const updateNum = () => {
    dispatch(updateCakeNum(100));
  };
  const updateName = () => {
    dispatch(updateCakeName('best cake'));
  };
  return (
    <div>
      <h3>Number of cakes - {numOfCakes}</h3>
      <h3>Name of cakes - {nameOfCake}</h3>
      <button onClick={updateName}>change cake's name</button>
      <button onClick={updateNum}>change cake's number</button>
    </div>
  );
};

现状的繁琐点:

  • 每次使用 useSelector 来获取 redux 中状态的时候,都需要给 state 加一个 ts 类型 RootState
  • 每次修改 redux 状态时,都需要引入 useDispatch 和一个 updateData,再将调用 updateData 返回的action 给 dispatch

我们的目标就是优化现状的这两个繁琐点。

优化方案

优化 useDispatch 和 useSelector

对于繁琐点1,我们可以对 useDispatch 和 useSelector 进行简单的封装,增加 ts 类型校验。代码如下:

// 文件位置: app/src/hooks/useReduxHook.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from '../store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

其中 RootState 和 AppDispatch 在 store 中导出:

import { configureStore } from '@reduxjs/toolkit';
import cakeReducer from './reducers/cake';
const store = configureStore({
  reducer: {
    cake: cakeReducer,
  }
})
export default store;
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

通过简单的封装,我们在业务层就用 useAppSelector 和 useAppDispatch 代替 useSelector 和 useDispatch:

import { useAppSelector, useAppDispatch } from '@src/hooks/useReduxHook';
// ...
export const CakeView = () => {
  const numOfCakes = useAppSelector((state) => state.cake.numOfCakes);
  const nameOfCake = useAppSelector((state) => state.cake.nameOfCake);
  const dispatch = useAppDispatch();
	// ...
  return (
	// ...
  );
};

可以看到这样就有 ts 类型提示或者校验了:

优化修改 redux 状态的步骤

我们现在想要在业务组件中修改 redux 状态,我们就需要引入 useDispatch 和通过 createSlice 生成的 updateData 函数,再调用 dispatch(updateData(data)) 来更新状态。

能不能优化成,业务组件只需要调用一个 updateData 函数,就可以更新 redux 状态呢?

最简单的方式就是将 dispatch(updateData(data)) 给抽出去:

const useUpdateCakeName = () => {
  const dispatch = useAppDispatch();
  return (payload: InitialState['nameOfCake']) => {
    dispatch(updateCakeName(payload));
  };
};

但是这对于开发者来讲,换汤不换药,还是需要写一个 hook 来 dispatch action。

那我们就来写一个能自动生成 「用于dispatch action 的 updateData 函数」的 hook 吧。先来个 js 版:

const useCakeReduxFunction = (action) => {
  const dispatch = useAppDispatch();
  return (payload) => {
    dispatch(cakeSlice.actions[action](payload));
  };
};

这个倒是好用了一些,业务组件使用起来是这样的:

import { useCakeReduxFunction } from '@/src/store/reducers/cake';
// ...
export const CakeView = () => {
  const updateNum = useCakeReduxFunction('updateCakeNum');
  const updateName = useCakeReduxFunction('updateCakeName');
	// ...
  return (
	// ...
  );
};

但是还有个问题,这样的话每个 reducer 里都要写一个 useDataReduxFunction,还是不够便捷。我们就自然而然的想到写一个 useCreateReduxFunction 来简化开发流程。useCreateReduxFunction 的代码如下;

type GetArrFirst<T> = T extends [infer Res, ...infer P] ? Res : unknown;
export const useCreateReduxFunction = <S extends Slice>(slice: S) => {
  return <T extends keyof S['actions']>(name: T) => {
    const dispatch = useAppDispatch();
    const actionCreator = (slice.actions as S['actions'])[name];
    return (payload: GetArrFirst<Parameters<typeof actionCreator>>) => {
      dispatch(actionCreator(payload));
    };
  };
};

这样就好办了,我们在 reducer 中这样使用:

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { useCreateReduxFunction } from '../../app/hooks';
type InitialState = {
  numOfCakes: number;
  cakeName: string;
};
const initialState: InitialState = {
  numOfCakes: 20,
  cakeName: 'great cake',
};
const cakeSlice = createSlice({
  name: 'cake',
  initialState,
  reducers: {
    updateCakeNum: (state, action: PayloadAction<number>) => {
      state.numOfCakes = action.payload;
    },
    updateCakeName: (state, action: PayloadAction<string>) => {
      state.cakeName = action.payload;
    },
  },
});
export const useCakeReduxFunction = useCreateReduxFunction(cakeSlice);
export default cakeSlice.reducer;
export const { updateCakeNum, updateCakeName } = cakeSlice.actions;

在业务方这么使用:

import { useAppSelector } from '@src/hooks/useReduxHook';
import { useCakeReduxFunction } from '@src/store/reducers/cake';
export const CakeView = () => {
  const numOfCakes = useAppSelector((state) => state.cake.numOfCakes);
  const nameOfCake = useAppSelector((state) => state.cake.cakeName);
  const updateNum = useCakeReduxFunction('updateCakeNum');
  const updateName = useCakeReduxFunction('updateCakeName');
  return (
    <div>
      <h3>Number of cakes - {numOfCakes}</h3>
      <h3>Name of cakes - {nameOfCake}</h3>
      <button onClick={() => {updateName('best cake')}}>change cake's name</button>
      <button onClick={() => {updateNum(100)}}>change cake's number</button>
    </div>
  );
};

这样就达成我们的目标了。

总结

核心代码如下:

type GetArrFirst<T> = T extends [infer Res, ...infer P] ? Res : unknown;
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useCreateReduxFunction = <S extends Slice>(slice: S) => {
  return <T extends keyof S['actions']>(name: T) => {
    const dispatch = useAppDispatch();
    const actionCreator = (slice.actions as S['actions'])[name];
    return (payload: GetArrFirst<Parameters<typeof actionCreator>>) => {
      dispatch(actionCreator(payload));
    };
  };
};

经过上面的优化方案,我们封装了 useAppSelector 、 useAppDispatch 和 useCreateReduxFunction三个 hook,解决了目前使用 Redux-Toolkit 还会存在的一些繁琐点。

以上就是React TypeScript 应用中便捷使用Redux Toolkit方法详解的详细内容,更多关于React TypeScript使用Redux Toolkit的资料请关注脚本之家其它相关文章!

相关文章

  • 浅谈从React渲染流程分析Diff算法

    浅谈从React渲染流程分析Diff算法

    这篇文章主要介绍了浅谈从React渲染流程分析Diff算法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • React Hooks--useEffect代替常用生命周期函数方式

    React Hooks--useEffect代替常用生命周期函数方式

    这篇文章主要介绍了React Hooks--useEffect代替常用生命周期函数方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • 基于React实现搜索GitHub用户功能

    基于React实现搜索GitHub用户功能

    在本篇博客中,我们将介绍如何在 React 应用中搜索 GitHub 用户并显示他们的信息,文中通过代码示例给大家介绍的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-02-02
  • Remix如何支持原生 CSS方法详解

    Remix如何支持原生 CSS方法详解

    这篇文章主要为大家介绍了Remix如何支持原生CSS的方法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • react执行【npx create-react-app my-app】出现常见错误的解决办法

    react执行【npx create-react-app my-app】出现常见错误的解决办法

    文章主要介绍了在使用npx创建React应用时可能遇到的几种常见错误及其解决方法,包括缺少依赖、网络问题和npx解析错误等,并提供了相应的解决措施,此外,还提到了使用腾讯云云产品来支持React应用开发
    2024-11-11
  • react-native-fs实现文件下载、文本存储的示例代码

    react-native-fs实现文件下载、文本存储的示例代码

    本篇文章主要介绍了react-native-fs实现文件下载、文本存储的示例代码,具有一定的参考价值,有兴趣的可以了解下
    2017-09-09
  • React.cloneElement的使用详解

    React.cloneElement的使用详解

    这篇文章主要介绍了React.cloneElement的使用详解,帮助大家更好的理解和学习使用React框架,感兴趣的朋友可以了解下
    2021-04-04
  • React自定义实现useWatch的方式和特点

    React自定义实现useWatch的方式和特点

    这篇文章主要介绍了React自定义实现useWatch的方式和特点,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2025-05-05
  • 如何应用 SOLID 原则在 React 中整理代码之开闭原则

    如何应用 SOLID 原则在 React 中整理代码之开闭原则

    React 不是面向对象,但这些原则背后的主要思想可能是有帮助的,在本文中,我将尝试演示如何应用这些原则来编写更好的代码,对React SOLID原则开闭原则相关知识感兴趣的朋友一起看看吧
    2022-07-07
  • React使用高德地图的实现示例(react-amap)

    React使用高德地图的实现示例(react-amap)

    这篇文章主要介绍了React使用高德地图的实现示例(react-amap),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04

最新评论