由微软推出的TypeScript自从一推出就广受关注,现在无论开发前端React/Vue,还是后端API,很多项目中都广泛接受了TypeScript。下面介绍如何通过TypeScript使用React中的useReducer Hook。和useState最大的区别就是,当处理复杂逻辑的时候,是需要使用reducer来分割处理逻辑,使得代码更容易维护的。

TypeScript
定义typings
添加文件src/types/index.ts:
TypeScript
export type CounterState = {
count: number
}
export type CounterAction = {
type: string
payload: number
}
export enum COUNTER_ACTION_TYPE {
INCREASE = 'increase',
DECREASE = 'decrease',
DOUBLE = 'double'
}
定义Reducer
添加文件src/reducers/CountReducer.ts:
TypeScript
import { CounterState, CounterAction, COUNTER_ACTION_TYPE } from '../typings';
const countReducer = (state: CounterState, action: CounterAction) => {
switch(action.type) {
case COUNTER_ACTION_TYPE.INCREASE:
return {count: state.count + action.payload}
case COUNTER_ACTION_TYPE.DECREASE:
return {count: state.count - action.payload}
case COUNTER_ACTION_TYPE.DOUBLE:
return {count: state.count * 2}
default:
return state;
}
}
export default countReducer;
需要注意的是,一定不要忘记最后的default分支,否则TS可能无法做类型推断。
另外,在上面switch分支中,相当于又重新定义了CounterState的结构(只有一个count属性),这种做法在这个例子中还可以用,但当CounterState的结构较为复杂,在reducer中只修改其中部分属性值的时候,可以使用下面这种方式:
TypeScript
switch(action.type) {
case COUNTER_ACTION_TYPE.INCREASE:
return {...state, count: state.count + (action as CounterPayloadAction).payload}
case COUNTER_ACTION_TYPE.DECREASE:
return {...state, count: state.count - (action as CounterPayloadAction).payload}
case COUNTER_ACTION_TYPE.DOUBLE:
return {...state, count: state.count * 2}
default:
return state;
}
UI部分
TypeScript
import { useReducer } from "react";
import { COUNTER_ACTION_TYPE } from "../typings";
import countReducer from '../reducers/CountReducer';
const initialState = { count: 0};
export default function Hello() {
const [state, dispatch] = useReducer(countReducer, initialState);
const handleIncrease = () => {
dispatch({
type: COUNTER_ACTION_TYPE.INCREASE,
payload: 1
});
}
const handleDecrease = () => {
dispatch({
type: COUNTER_ACTION_TYPE.DECREASE,
payload: 1
});
}
const handleDouble = () => {
dispatch({
type: COUNTER_ACTION_TYPE.DOUBLE,
payload: 0
});
}
return <div>
Count: {state.count}<br/>
<button onClick={ handleIncrease }> + </button>
<button onClick={ handleDecrease }> - </button>
<button onClick={ handleDouble }> *2 </button>
</div>;
}
使用联合类型
在上面的例子中还有一个小问题,比如在handleDouble中,其实根本不需要payload,但为了满足类型定义,传入一个毫无意义的值0。为了解决这个问题,我们可以:
更新文件src/typings/index.ts:
TypeScript
export type CounterPayloadAction = {
type: string
payload: number
}
export type CounterNoPayloadAction = {
type: string
}
export type CounterAction = CounterPayloadAction | CounterNoPayloadAction
更新文件src/reducers/CountReducer.ts:
TypeScript
import { CounterState, CounterAction, COUNTER_ACTION_TYPE, CounterPayloadAction } from '../typings';
const countReducer = (state: CounterState, action: CounterAction) => {
switch(action.type) {
case COUNTER_ACTION_TYPE.INCREASE:
return {count: state.count + (action as CounterPayloadAction).payload}
case COUNTER_ACTION_TYPE.DECREASE:
return {count: state.count - (action as CounterPayloadAction).payload}
case COUNTER_ACTION_TYPE.DOUBLE:
return {count: state.count * 2}
default:
return state;
}
}
export default countReducer;
这样在组件的handlDouble函数中就相对简单,也看起来正常了:
TypeScript
const handleDouble = () => {
dispatch({
type: COUNTER_ACTION_TYPE.DOUBLE
});
}