`createAction` 返回的类型不是通过 `ofType` 维护的
The type returned by `createAction` isn't maintained through `ofType`
我正在制作一个应用程序,让用户 select 从可用零食列表中选择零食。零食是从外部加载的 API.
我正在使用 redux-observable
来“侦听”操作,然后发送适当的 API 请求。这是我现有的代码,对有问题的代码行进行了评论:
// actions.ts
import { createAction } from '@reduxjs/toolkit';
import * as Type from './types';
const pfx = '[Snacks]';
export const snackSelect = createAction<Type.Snack['id']>(`${pfx} Select`);
export const snackSelectSuccess = createAction(`${pfx} Select success`);
export const snacksAvailableLoad = createAction(`${pfx} Available load`);
export const snacksAvailableLoadSuccess = createAction<Array<Type.Snack>>(`${pfx} Available load success`);
// epics.ts
import {
combineEpics,
ofType,
} from 'redux-observable';
import {
map,
switchMap,
} from 'rxjs/operators';
import * as API from '../api';
import * as Actions from './actions';
export default combineEpics(
action$ => action$.pipe(
ofType(Actions.snacksAvailableLoad),
switchMap(API.snacksGet),
map(Actions.snacksAvailableLoadSuccess),
),
action$ => action$.pipe(
ofType(Actions.snackSelect),
switchMap(({ payload }) => API.snackSelect(payload)), // Error: Property 'payload' does not exist on type 'Action<any>'
map(Actions.snackSelectSuccess),
)
);
payload
(应该是目标零食 ID)在我的史诗 switchMap
中不可用。
我可以用 switchMap((action: ReturnType<typeof Actions.snackSelect>) => API.snackSelect(action.payload))
强制它,但是 ReturnType<typeof>
看起来像代码味道 and/or 而不是很多样板文件。
是否有正确的方法来通过 ofType
保留输入?
如果我使用 filter
和 .match
则推断出正确的类型:
export default combineEpics(
action$ => action$.pipe(
filter(Actions.snacksAvailableLoad.match),
switchMap(API.snacksGet),
map(Actions.snacksAvailableLoadSuccess),
),
action$ => action$.pipe(
filter(Actions.snackSelect.match),
switchMap(({ payload }) => API.snackSelect(payload)),
map(Actions.snackSelectSuccess),
),
);
我不太熟悉 redux-observable,但看起来 ofType
函数中的过滤是基于 Action['type']
属性 的类型。看我对类型函数声明的评论:
export declare function ofType<
T extends Action, // input action type
R extends T = T, // output action type
K extends R['type'] = R['type'] // type name to filter by
>(...key: K[]): (source: Observable<T>) => Observable<R>;
第一个问题是,根据您的 TypeScript 版本,您操作的 type
属性 只是 string
而不是像 "[Snacks] Select success"
这样的字面值。当您使用 ${pfx} Select success
这样的模板创建字符串时,您需要添加 as const
以避免扩大到 string
。这在 v4.3.0+(目前处于测试阶段)中不是必需的。
添加 as const
将修复您的两个没有负载的操作。确实有有效载荷的两个仍然是 string
。这是因为 createAction
函数有两个泛型类型参数,用于负载 P
和类型 T
。您不能指定一个参数并推断出第二个参数。因此,当您为有效负载设置泛型时,您将失去推断字符串文字类型的能力,它将回退到默认值 string
.
您始终可以手动指定两者:
export const snackSelect = createAction<Type.Snack["id"], `${typeof pfx} Select`>(
`${pfx} Select` as const
);
或者创建某种辅助函数,因为看起来你在这里有一个一致的模式。
第二个问题是 ofType
仅从类型名称 K
推断操作类型 R
的能力。如果您的初始操作类型 T
是您特定操作类型的联合,那么这可能会通过提取可分配给 {type: K}
的联合成员自动发生。但是如果 T
是一般的 any
动作那么它将无法区分它。
所以你需要使用某种类型保护。 Redux-toolkit 操作带有内置类型保护功能 match
.
declare interface BaseActionCreator<P, T extends string, M = never, E = never> {
type: T;
match(action: Action<unknown>): action is PayloadAction<P, T, M, E>;
}
...显然这就是您所需要的,这也解决了第一个问题。
我正在制作一个应用程序,让用户 select 从可用零食列表中选择零食。零食是从外部加载的 API.
我正在使用 redux-observable
来“侦听”操作,然后发送适当的 API 请求。这是我现有的代码,对有问题的代码行进行了评论:
// actions.ts
import { createAction } from '@reduxjs/toolkit';
import * as Type from './types';
const pfx = '[Snacks]';
export const snackSelect = createAction<Type.Snack['id']>(`${pfx} Select`);
export const snackSelectSuccess = createAction(`${pfx} Select success`);
export const snacksAvailableLoad = createAction(`${pfx} Available load`);
export const snacksAvailableLoadSuccess = createAction<Array<Type.Snack>>(`${pfx} Available load success`);
// epics.ts
import {
combineEpics,
ofType,
} from 'redux-observable';
import {
map,
switchMap,
} from 'rxjs/operators';
import * as API from '../api';
import * as Actions from './actions';
export default combineEpics(
action$ => action$.pipe(
ofType(Actions.snacksAvailableLoad),
switchMap(API.snacksGet),
map(Actions.snacksAvailableLoadSuccess),
),
action$ => action$.pipe(
ofType(Actions.snackSelect),
switchMap(({ payload }) => API.snackSelect(payload)), // Error: Property 'payload' does not exist on type 'Action<any>'
map(Actions.snackSelectSuccess),
)
);
payload
(应该是目标零食 ID)在我的史诗 switchMap
中不可用。
我可以用 switchMap((action: ReturnType<typeof Actions.snackSelect>) => API.snackSelect(action.payload))
强制它,但是 ReturnType<typeof>
看起来像代码味道 and/or 而不是很多样板文件。
是否有正确的方法来通过 ofType
保留输入?
如果我使用 filter
和 .match
则推断出正确的类型:
export default combineEpics(
action$ => action$.pipe(
filter(Actions.snacksAvailableLoad.match),
switchMap(API.snacksGet),
map(Actions.snacksAvailableLoadSuccess),
),
action$ => action$.pipe(
filter(Actions.snackSelect.match),
switchMap(({ payload }) => API.snackSelect(payload)),
map(Actions.snackSelectSuccess),
),
);
我不太熟悉 redux-observable,但看起来 ofType
函数中的过滤是基于 Action['type']
属性 的类型。看我对类型函数声明的评论:
export declare function ofType<
T extends Action, // input action type
R extends T = T, // output action type
K extends R['type'] = R['type'] // type name to filter by
>(...key: K[]): (source: Observable<T>) => Observable<R>;
第一个问题是,根据您的 TypeScript 版本,您操作的 type
属性 只是 string
而不是像 "[Snacks] Select success"
这样的字面值。当您使用 ${pfx} Select success
这样的模板创建字符串时,您需要添加 as const
以避免扩大到 string
。这在 v4.3.0+(目前处于测试阶段)中不是必需的。
添加 as const
将修复您的两个没有负载的操作。确实有有效载荷的两个仍然是 string
。这是因为 createAction
函数有两个泛型类型参数,用于负载 P
和类型 T
。您不能指定一个参数并推断出第二个参数。因此,当您为有效负载设置泛型时,您将失去推断字符串文字类型的能力,它将回退到默认值 string
.
您始终可以手动指定两者:
export const snackSelect = createAction<Type.Snack["id"], `${typeof pfx} Select`>(
`${pfx} Select` as const
);
或者创建某种辅助函数,因为看起来你在这里有一个一致的模式。
第二个问题是 ofType
仅从类型名称 K
推断操作类型 R
的能力。如果您的初始操作类型 T
是您特定操作类型的联合,那么这可能会通过提取可分配给 {type: K}
的联合成员自动发生。但是如果 T
是一般的 any
动作那么它将无法区分它。
所以你需要使用某种类型保护。 Redux-toolkit 操作带有内置类型保护功能 match
.
declare interface BaseActionCreator<P, T extends string, M = never, E = never> {
type: T;
match(action: Action<unknown>): action is PayloadAction<P, T, M, E>;
}
...显然这就是您所需要的,这也解决了第一个问题。