使用Redux以强类型的方式用TypeScript来管理应用程序中的一些状态

93 阅读4分钟

Redux是一个流行的库,用于管理React应用中的状态。在这篇文章中,我们将使用Redux以强类型的方式用TypeScript来管理应用程序中的一些状态。我们将使用钩子方法来与React组件中的Redux存储进行交互。

React, Redux and TypeScript

状态

让我们从商店的状态对象开始:

type Person = {
  id: number;
  name: string;
};
type AppState = {
  people: Person[];
};

所以,我们的应用状态包含一个人的数组。

这个应用程序将被刻意简化,以便我们能够专注于Redux商店以及React组件如何以强类型的方式与之互动。

行动和行动创建者

对状态的改变是由一个动作发起的,这个动作是一个包含我们需要进行改变的一切的对象。在我们的例子中,我们有两个动作。

  • AddPerson。当一个人被添加到状态中时,这个动作被触发。该动作对象将包含要添加的人。
  • RemovePerson.当一个人被从状态中移除时,这个动作会被触发。动作对象将包含要删除的人的ID。

行动创建者是创建和返回行动对象的函数。这里是我们的两个动作的动作创建者:

function addPerson(personName: string) {
  return {
    type: "AddPerson",
    payload: personName,
  } as const;
}

function removePerson(id: number) {
  return {
    type: "RemovePerson",
    payload: id,
  } as const;
}

请注意,我们在返回对象上使用了const断言,所以动作中的属性是只读的。

还要注意的是,我们没有为我们的动作明确地创建类型,因为我们要从动作创建者的函数中推断出这些类型。

减速器

还原器是一个将更新状态的函数。首先,我们将为action 参数创建一个类型,为这个函数的实现做准备:

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

这是一个所有动作的联合类型。我们用typeof 关键字来获取动作创建者的类型,然后用ReturnType 实用类型来获取这些函数的返回类型。使用这种方法,我们不需要为动作对象明确创建类型:

减速器的功能如下:

function peopleReducer(
  state: Person[] = [],
  action: Actions
) {
  switch (action.type) {
    case "AddPerson":
      return state.concat({
        id: state.length + 1,
        name: action.payload,
      });
    case "RemovePerson":
      return state.filter(
        (person) => person.id !== action.payload
      );
    default:
      neverReached(action);
  }
  return state;
}

function neverReached(never: never) {}

我们显式地对函数参数进行建模,并允许推断出返回类型。

注意,action type 属性上的switch 语句是强类型的,所以,如果我们错误地输入了一个值,就会出现错误。

switch 语句分支内的action 参数,其类型已经缩小到与分支相关的特定动作。如果我们将鼠标悬停在分支中的payload 属性上,我们会看到它已经被缩小到了正确的类型。

请注意,我们在default switch 分支中使用了never 类型,向TypeScript编译器发出信号,表明应该不可能到达这个分支。这在我们的应用程序成长过程中很有用,我们需要实现新的动作。

商店

我们创建一个函数来创建商店,它使用Redux的createStore 函数:

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

储存器的类型很简单。我们使用Redux核心库中的通用类型Store ,并传入我们应用状态的类型,在我们的例子中是AppState

Redux中的combineReducers 函数被用来创建rootReducer :

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

combineReducers 有一个通用的类型参数,用于商店的状态类型,我们将其传入。

连接组件

现在进入连接组件的阶段。

首先,我们需要将来自React Redux的Provider 组件包裹在需要访问商店的最上面的组件周围。我们需要将我们的商店传入Provider 组件:

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

在这个组件中,我们可以使用React Redux的useSelector 钩子来从商店中获取数据:

const people: Person[] = useSelector(
  (state: AppState) => state.people
);

我们将一个函数传入useSelector ,从存储中获取状态并返回相关数据。我们明确地用我们的AppState 类型来输入state 参数。

我们可以使用useDispatch 钩子来调用存储动作:

const dispatch = useDispatch();

useDispatch 返回一个我们命名为 的函数。然后,我们使用 ,将我们的动作创建者传递给它,从而调用动作:dispatch dispatch

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>

这个例子可以在CodeSandbox中找到:codesandbox.io/s/react-typ…

包裹起来

当动作是同步的,我们可以以相当直接的方式实现强类型的Redux代码,大量使用推理。TypeScript在还原器中缩小动作类型的方式非常聪明,使用never 是一个很好的方法。

我们如何实现异步强类型的动作?我们将在下一篇文章中找到答案。