React+TypeScript教程之4 - useReducer


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

TypeScript
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
  });
}

文章作者: 逻思
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 逻思 !