如何编写没有参数的类型化操作 [英] How to Write A Typed Action Without Parameters
问题描述
一个 Github 问题表明我们可以将 TypedAction.defineWithoutPayload
用于此目的,但我找不到任何相关示例说明如何这样做.
A Github issue suggests that we can use TypedAction.defineWithoutPayload
for this purpose but I couldn't find any relevant examples on how to do so.
当访问令牌存储在有效负载中时,我使用它进行登录.如果令牌存在,则用户可以访问私人页面.
I use this for login when an accessToken is being stored in the payload. If the token is present, the user gets access to private pages.
export const login = (token: string) => typedAction("LOGIN", token);
现在,在注销按钮上,我正在尝试实施一个操作,以删除负载中存储的值.在这种情况下,将没有用于分派动作的参数.那么如何编写 typedAction 呢?
Now, on the logout button, I am trying to implement an action that removes the stored value in the payload. In this case, there will be no parameters for dispatching the action. So how can I write the typedAction?
如果我使用:
export const logout = () => typedAction("LOGOUT");
我开始在我的减速器的有效负载上收到一个错误,即类型注销时不存在该属性.
I start getting an error on my payload of my reducer that property doesn't exist on type 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;
}
};
Codesandbox:https://codesandbox.io/s/keen-brook-kntkm?file=/src/store/actions/login.ts:50-118
Codesandbox: https://codesandbox.io/s/keen-brook-kntkm?file=/src/store/actions/login.ts:50-118
电子邮件:c@c.com密码:勾选
Email: c@c.com Password: check
export interface IState {
token: string | null;
}
const initialState: IState = {
token: null
};
如果我按照 IDE 的建议使用 state: typeof initialState
或 state = initialState
,则 action.payload 会出错:
Error on action.payload if I use state: typeof initialState
or state = initialState
as per IDE's suggestion:
Type 'string' is not assignable to type 'IState'.ts(2322)
如果我尝试 state: initialState
那么显然:
If I try state: initialState
then obviously:
'initialState' refers to a value, but is being used as a type here. Did you mean 'typeof initialState'?ts(2749)
``
推荐答案
定义 typedAction
函数的方式工作正常:
The way you have your typedAction
function defined works fine:
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 };
}
您遇到的问题是由于您的减速器参数中的操作被解构了:
The problem you're having is because of the destructuring of the action in your reducer parameters:
export const tokenReducer = (
state: IState["token"] = null,
{ type, payload }: AppAction
): typeof state => {
// ...
};
解构和 TypeScript 的难点之一是,一旦你这样做了,变量的类型就会变得彼此独立.将操作解构为 { payload, type }
会生成 type: 'LOGIN' |'LOGOUT'
和 payload: string |未定义
变量.即使您稍后细化 type
的值,就像在您的 switch 语句中一样,payload
仍然具有类型 string |未定义
;细化type
后,TypeScript不会自动细化case块中payload
的类型;它们的类型是完全独立的.
One of the difficulties with destructuring and TypeScript is that once you do that, the typing of the variables become independent from each other. Destructuring the action into { payload, type }
makes a type: 'LOGIN' | 'LOGOUT'
and payload: string | undefined
variable. Even if you later refine the value of type
, like in your switch statement, payload
still has the type string | undefined
; TypeScript will not automatically refine the type of payload
in the case block after type
is refined; their typings are completely independent.
因此,您可以使用的一个有点丑陋的技巧是不解构:
So a somewhat ugly hack you can use is to not destructure:
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
现在是强与特定于这些操作之一的负载类型相关联.
This works because in your switch statement, it's able to refine the action: AppAction
type into the more specific login or logout types, so action.payload
now is strongly tied to the payload type specific to one of those actions.
这是我使用的 redux 操作的替代模式,您可能会发现它更方便在我的叉子上 让你享受映射类型的强大功能,用更少的样板定义减速器.首先,您必须使用类型/有效负载映射定义一个类型,并定义一些从中派生的类型:
Here's an alternative pattern for redux actions I use that you might find more convenient at my fork that lets you enjoy the power of mapped types to define reducers with less boilerplate. First, you must define a type with the type/payload mappings and define some types deriving from that:
export type ActionPayloads = {
LOGIN: string;
LOGOUT: void;
};
export type ActionType = keyof ActionPayloads;
export type Action<T extends ActionType> = {
type: T;
payload: ActionPayloads[T];
};
现在可以根据该地图定义您的动作创建者:
Your action creators can now be defined in terms of that map:
export function typedAction<T extends ActionType>(
type: T,
payload: ActionPayloads[T]
) {
return { type, payload };
}
接下来,你可以定义一个辅助函数来创建一个强类型的reducer:
Next, you can define a helper function for creating a strongly-typed 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
演员,我还没有找到好的解决方法,但至少我们从逻辑上知道打字是从外部发出的声音).
(I haven't found a good workaround for that ugly : any
cast, but at least we know logically that the typing is sound from the outside).
现在,您可以为您的动作处理程序使用漂亮的隐式类型定义您的减速器:
Now you can define your reducers thusly with nice implicit typing for your action handlers:
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`
});
这篇关于如何编写没有参数的类型化操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!