首页 > 解决方案 > 打字稿推理问题

问题描述

我正在尝试使用 React 的 useReducer 和 useContext 构建一个通用商店,但我在推断默认状态时遇到了问题。

存储生成器函数如下:

export function generateStore<Actions extends ReducerAction, State = any>(defaultValue: State, reducer: (state: State, action: Actions) => State): {
  provider: (props: { children: ReactNode }) => ReactElement;
  dispatcher: (action: Actions) => void;
  useStore: () => State;
} {
  const store = createContext(defaultValue);
  const { Provider } = store;

  let dispatch: React.Dispatch<Actions>;

  const ProviderElm = (props: { children: ReactNode }): ReactElement => {
    const { children } = props;
    const [state, dispatcher] = useReducer(reducer, defaultValue);
    dispatch = dispatcher;
    return <Provider value={state}>{children}</Provider>;
  };

  return {
    provider: ProviderElm,
    dispatcher: (action: Actions) => dispatch && dispatch(action),
    useStore: () => useContext(store),
  };
}

初始化程序示例可能是:

const defaultState = {
  auth: {
    authenticated: false,
  },
};

type StoreActions =
  | {
      type: 'LOGIN';
      payload: {
        token: string;
      };
    }
  | {
      type: 'LOGOUT';
    };

const { dispatcher, provider, useStore } = generateStore<StoreActions>(
  defaultState,
  (state = defaultState, action) => {
    switch (action.type) {
      case 'LOGIN': {
        const { token } = action.payload;
        return {
          ...state,
          auth: {
            authenticated: true,
            token,
          },
        };
      }
      case 'LOGOUT': {
        return {
          ...state,
          auth: {
            authenticated: false,
            token: null,
          },
        };
      }

      default:
        return defaultState;
    }
  },
);

问题是State泛型的generateStore不能将自己推断为参数的类型defaultValue

它总是需要我像这样初始化它,否则智能感知将无法计算出类型: generateStore<StoreActions, typeof defaultState>

关于我如何进行这项工作以及为什么它目前无法推断类型的任何想法?

标签: reactjstypescript

解决方案


如果您希望 TypeScript 推断您的泛型类型。您不能为函数提供任何类型参数。TypeScript 不支持部分类型推断。要么全有,要么全无。通过调用generateStore<StoreActions>,您将触发编译器State = any在您的函数上使用预定义的通用参数。

我建议使用强类型状态以使其更清晰。

type State = {
  auth: {
    authenticated: boolean
  }
}

type StoreActions =
  | {
    type: 'LOGIN';
    payload: {
      token: string;
    };
  }
  | {
    type: 'LOGOUT';
  };

const defaultState: State = {
  auth: {
    authenticated: false,
  },
};

const { dispatcher, provider, useStore } = generateStore<StoreActions, State>(
  defaultState,
  (state = defaultState, action) => {
    switch (action.type) {
      case 'LOGIN': {
        const { token } = action.payload;
        return {
          ...state,
          auth: {
            authenticated: true,
            token,
          },
        };
      }
      case 'LOGOUT': {
        return {
          ...state,
          auth: {
            authenticated: false,
            token: null,
          },
        };
      }

      default:
        return defaultState;
    }
  },
);

唯一的另一种选择是创建一个包装函数,它只需要一个参数来推断(状态)并直接提供动作类型。每组操作都需要一个,但这可能是一个很好的解决方法,具体取决于它将使用多少次。

type StoreActions =
  | {
    type: 'LOGIN';
    payload: {
      token: string;
    };
  }
  | {
    type: 'LOGOUT';
  };

const defaultState = {
  auth: {
    authenticated: false,
  },
};

export function generateStoreWithStoreActions<State = any>(defaultValue: State, reducer: (state: State, action: StoreActions) => State) {
  return generateStore<StoreActions, State>(defaultValue, reducer);
}

const { dispatcher, provider, useStore } = generateStoreWithStoreActions(
  defaultState,
  (state = defaultState, action) => {
    switch (action.type) {
      case 'LOGIN': {
        const { token } = action.payload;
        return {
          ...state,
          auth: {
            authenticated: true,
            token,
          },
        };
      }
      case 'LOGOUT': {
        return {
          ...state,
          auth: {
            authenticated: false,
            token: null,
          },
        };
      }

      default:
        return defaultState;
    }
  },
);

推荐阅读