【问题标题】:How to Write A Typed Action Without Parameters如何编写没有参数的类型化操作
【发布时间】:2020-07-31 04:21:40
【问题描述】:

Github 问题表明我们可以为此目的使用 TypedAction.defineWithoutPayload,但我找不到任何相关示例。

当 accessToken 存储在有效负载中时,我使用它进行登录。如果令牌存在,则用户可以访问私有页面。

export const login = (token: string) => typedAction("LOGIN", token);

现在,在注销按钮上,我正在尝试执行一个删除有效负载中存储值的操作。在这种情况下,将没有用于调度操作的参数。那么 typedAction 怎么写呢?

如果我使用:

export const logout = () => typedAction("LOGOUT");

我开始在我的减速器的有效负载上收到一个错误,该属性在类型注销时不存在。

这是我的减速器:

export const tokenReducer = (
  state: IState['token'] = null,
  { type, payload }: AppAction,
): typeof state => {
  switch (type) {
    case 'LOGIN':
      return payload;
      case 'LOGOUT':
        return null;
    default:
      return state;
  }
};

代码沙盒:https://codesandbox.io/s/keen-brook-kntkm?file=/src/store/actions/login.ts:50-118

电子邮件:c@c.com 密码:检查

编辑:

export interface IState {
    token: string | null;
  }
const initialState: IState = {
  token: null
};

如果我按照 IDE 的建议使用 state: typeof initialStatestate = initialState,则 action.payload 出错:

Type 'string' is not assignable to type 'IState'.ts(2322)

如果我尝试state: initialState那么显然:

'initialState' refers to a value, but is being used as a type here. Did you mean 'typeof initialState'?ts(2749)
``

【问题讨论】:

  • 在您的代码沙箱中,您似乎已经有 typedAction 处理任何有效负载操作;那不工作吗?您是否在执行export const logout = () => typedAction("LOGOUT"); 之类的操作时遇到编译器错误?由于动作是不变且无参数的,您甚至可以将其作为常量而不是动作创建者。
  • 实际上,该行有效,但随后我在Property 'payload' does not exist on type '{ type: "LOGOUT"; } | { type: "LOGIN"; payload: string; }'.ts(2339)@Jacob 的有效负载上的 tokenReducer 中开始收到错误
  • 将单重重载更改为export function typedAction<T extends string>(type: T): { type: T, payload: void }; 是否有效?分叉:codesandbox.io/s/vigorous-wescoff-qf4h7?file=/src/store/actions/…
  • 不,它仍然给出相同的错误,以及其他错误,如“预期 1 个参数,但得到 2 个”@Jacob
  • 如果initialStateIState 类型,为什么不使用IState 类型而不是typeof?如果您尝试获取令牌变量的类型,请使用const tokenReducerInitialState: IState['token'] = null

标签: javascript reactjs typescript redux react-redux


【解决方案1】:

您定义typedAction 函数的方式可以正常工作:

export function typedAction<T extends string>(type: T): { type: T };
export function typedAction<T extends string, P extends any>(
  type: T,
  payload: P
): { type: T; payload: P };
export function typedAction(type: string, payload?: any) {
  return { type, payload };
}

您遇到的问题是由于您的减速器参数中的动作解构:

export const tokenReducer = (
  state: IState["token"] = null,
  { type, payload }: AppAction
): typeof state => {
  // ...
};

解构和 TypeScript 的一个困难是,一旦你这样做了,变量的类型就会变得彼此独立。将动作解构为{ payload, type } 会生成type: 'LOGIN' | 'LOGOUT'payload: string | undefined 变量。即使您稍后细化type 的值,就像在您的switch 语句中一样,payload 仍然具有string | undefined 类型; TypeScript 不会在type 提炼后自动提炼payload 的类型;他们的类型是完全独立的。

所以你可以使用的一个有点丑陋的技巧是不解构:

export const tokenReducer = (
  state: IState['token'] = null,
  action: AppAction,
): typeof state => {
  switch (action.type) {
    case 'LOGIN':
      return action.payload;
    case 'LOGOUT':
      return null;
    default:
      return state;
  }
};

之所以有效,是因为在您的 switch 语句中,它能够将 action: AppAction 类型细化为更具体的登录或注销类型,因此 action.payload 现在与特定于其中一个操作的有效负载类型密切相关。

这是我使用的 redux 操作的另一种模式,您可能会发现更方便的at my fork 让您享受映射类型的强大功能,以使用更少的样板来定义化简器。首先,您必须使用类型/有效负载映射定义一个类型,并定义一些派生自该类型的类型:

export type ActionPayloads = {
  LOGIN: string;
  LOGOUT: void;
};

export type ActionType = keyof ActionPayloads;

export type Action<T extends ActionType> = {
  type: T;
  payload: ActionPayloads[T];
};

您的动作创建者现在可以根据该地图定义:

export function typedAction<T extends ActionType>(
  type: T,
  payload: ActionPayloads[T]
) {
  return { type, payload };
}

接下来,你可以定义一个辅助函数来创建一个强类型的reducer:

type ReducerMethods<State> = {
  [K in ActionType]?: (state: State, payload: ActionPayloads[K]) => State
};

type Reducer<State> = (state: State, action: AppAction) => State;

function reducer<State>(
  initialState: State,
  methods: ReducerMethods<State>
): Reducer<State> {
  return (state: State = initialState, action: AppAction) => {
    const handler: any = methods[action.type];
    return handler ? handler(state, action.payload) : state;
  };
}

(对于那个丑陋的: any 演员,我还没有找到一个好的解决方法,但至少我们从逻辑上知道打字是从外面发出的声音)。

现在,您可以为您的操作处理程序使用漂亮的隐式类型定义您的减速器:

type TokenState = string | null;

export const tokenReducer = reducer<TokenState>(null, {
  LOGIN: (state, token) => token, // `token` is implicitly typed as `string`
  LOGOUT: () => null              // TS knows that the payload is `undefined`
});

【讨论】:

  • 在你回答之前,我打算在开头添加const initialState: IState = { token: null };并将state: IState["token"] = null更改为state: initialState,但它给出了一个错误。您能提出解决办法吗?
  • 这取决于:IState 是如何定义的,错误是什么?
  • 我在qs里加了
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多