「超详细React项目搭建教程六」集成 Redux/Typescript

1,721 阅读4分钟

Redux 是 React 应用状态管理的一个比较流行的库!

在本篇文章中,我们来交流下

  • 如何使用 Typescript 以强类型的方式管理 Redux 状态
  • 如何使用 React-Redux 的 Hooks 函数让 React 组件和 Redux 状态进行交互

安装依赖

yarn add redux react-redux

yarn add @types/react-redux --dev

# 安装 redux开发者工具
$ yarn add redux-devtools --dev

话说实践出真知,下面我们通过 Redux 实现一个用户名的添加和删除。以便加深我们对 Redux 的理解

reduxDemo-tongbu

State

我们将以下类型作为 stores 的状态类型

// src/redux/data.d.ts

// 用户的字段类型
type Person = {
  id: number;
  name: string;
};

// 所有用户的类型
type AppState = {
  people: Person[];
};

可以发现,我们的应用状态是一个 people ,它是包含 id/name 的数组类型

状态我们尽量简化,因为我们主要专注于 Redux Store 以及 React 组件如何以强类型的方式进行交互。

Actions 和 action 创建函数

众所周知,在 Redux 中改变状态必须发起一个 action,action 中必须包含

  • 发起的动作是什么
  • 发起的动作需要传递什么东西

在我们的例子中有两个 action

  • AddPerson:当一个用户名被添加时触发,该动作中包含了新的用户的名称
  • RemovePerson:当一个用户名被删除时触发,该动作中包含了用户的id

接下来是 action 创建函数,它的作用是创建并返回 action 对象,下面的是我们为两个 action 实现的 action 创建函数

// actionTypes
enum actionTypes {
  ADD_PERSON = 'ADD_PERSON',
  REMOVE_PERSON = 'REMOVE_PERSON',
}

export default actionTypes;

// src/redux/actions/index.ts

import actionTypes from "./actionTypes";

export const addPerson = (personName: string) => {
  return {
    type: actionTypes.ADD_PERSON,
    payload: personName,
  } as const;
};

export const removePerson = (id: string) => {
  return {
    type: actionTypes.REMOVE_PERSON,
    payload: id,
  } as const;
};

注意,我们没有给payload 明确类型,因为 action 创建函数可以自动推断是什么类型。

Reducer

reducer 是一个接收 state 参数和 action,并用于更新 state 的函数,

首先我们为 action 参数定义 Typescript 类型

type Actions = ReturnType<typeof addPerson> | ReturnType<typeof removePerson>;

这是所有 action 的联合类型。 我们使用 typeof 关键字获取 action 创建函数的类型,然后使用ReturnType获取这些函数的返回类型。 使用这种方法,我们不需要显式为 action 对象创建类型。

reducer 函数如下所示

import { addPerson, removePerson } from "../actions/index";
import type { Person } from "../data.d";
import actionTypes from "../actions/actionTypes";

type Actions = ReturnType<typeof addPerson> | ReturnType<typeof removePerson>;

const initialState: Person[] = [{ id: "1", name: "小萝莉" }];

export default function peopleReducer(state = initialState, action: Actions) {
  switch (action.type) {
    case actionTypes.ADD_PERSON:
      return state.concat([
        {
          id: (Math.random() * 1000000).toFixed(0),
          name: action.payload,
        },
      ]);
    case actionTypes.REMOVE_PERSON:
      return state.filter((person) => person.id !== action.payload);
    default:
      break;
  }
  return state;
}

我们显式地设置了函数参数类型,且不指定返回值的类型(由 TS 的类型推断来判断)。

请注意, switch 语句中的 action.type是强类型的,因此,如果我们在case中输入错误的值,将引发报错。

Store

我们使用 Redux 中的 createStore 来创建一个生成 store 的函数

import { combineReducers, createStore } from "redux";
import { Store } from "redux";
import { AppState } from "./data.d";

import peopleReducer from "./reducers/index";

const rootReducer = combineReducers<AppState>({
  people: peopleReducer,
});

function configureStore(): Store<AppState> {
  const store = createStore(rootReducer, undefined);
  return store;
}

const storeData = configureStore();

export default storeData;

使用 Redux 中的combinedReducers函数创建rootReducer

store 的类型设置也很简单:使用 Redux 中的Store泛型类型,在我们的示例中为AppState

链接组件

终于走到组件这一步了,<( ̄ ▽  ̄)/

首先我们需要使用 react-redux 中的 Provider 组件包裹顶层组件,然后将 store 传递到Provider组件中:

import { Provider } from "react-redux";
import store from './redux/store'

const App = () => (
  <Provider store={store}>
    <Page />
  </Provider>
);

在子组件内部,我们可以使用 React ReduxuseSelector钩子从 store 中获取数据

// src/pages/home/homeByFunc.tsx

import React from "react";
import { useSelector } from 'react-redux'
import type { Person, AppState } from "@/redux/data.d"

const Index: React.FC = () => {
  .......
    const people: Person[] = useSelector((state: AppState) => state.people);
  .......
}

useSelector函数接收的参数也是一个函数,该函数从 store 获取状态并返回相关数据。 并且使用AppState类型显式设置 state 参数的类型。

接下来,我们使用 React Redux 的useDispatch钩子来调用 action:

// src/pages/home/homeByFunc.tsx

import React from "react";
import { useDispatch } from 'react-redux'

const Index: React.FC = () => {
  .......
    const dispatch = useDispatch();
  .......
}

useDispatch返回一个我们称为dispatch的函数。 然后,我们通过将 action 创建函数传递到dispatch中来触发 action:

// src/pages/home/homeByFunc.tsx
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
  dispatch(addPerson(newPerson));
  ...
};

const dispatchNewPerson = (id: number) => () => {
  dispatch(removePerson(id));
};
...
<button onClick={dispatchNewPerson(person.id)}>Remove</button>

难道这就结束了吗?🙅‍♂️ 不,离搭建一个完整的 React 应用有些距离。那么在下一篇文章中,我会告诉你如何在项目中实现异步Redux