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

状态
让我们从商店的状态对象开始:
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 是一个很好的方法。
我们如何实现异步强类型的动作?我们将在下一篇文章中找到答案。