Typescript Redux Thunk(类型)
Typescript Redux Thunk (Types)
我有一个 redux thunk 操作,它获取一些数据然后分派一些操作(此处的代码中未显示,但您可以在下面的演示 link 中找到它)
export const fetchPosts = (id: string) => (dispatch: Dispatch<TActions>) => {
return fetch('http://example.com').then(
response => {
return response.json().then(json => {
return "Success message";
});
},
err => {
throw err;
}
);
};
而不是在我的组件中,我使用 mapDispatchToProps
和 bindActionCreators
从我的组件中调用此函数,如下所示:
public fetchFunc() {
this.props.fetchPosts("test").then(
res => {
console.log("Res from app", res);
},
err => {
console.log("Err from app", err);
}
);
}
由于我使用的是typescript,所以我需要在Props中定义这个函数的类型
interface IProps {
name?: string;
posts: IPost[];
loading: boolean;
fetchPosts: (id: string) => Promise<string | Error>;
}
如果我像上面那样做,Typescript 会抱怨我应该这样做:
fetchPosts: (id: string) => (dispatch: Dispatch<TActions>) => Promise<string | Error>;
如果我这样做,当我在我的组件中使用 then
时,Typescript 会抱怨说该功能不是一个承诺。
我创建了一个演示,您可以在其中 fiddle 使用代码
按"Load from remote"有时会失败,只是为了看看是否承诺:
基本上,在 Typescript 中,promise 的泛型类型只能从 resolve
中推断出来。
例如
function asyncFunction() {
return new Promise((resolve, reject) => {
const a = new Foo();
resolve(a);
})
}
asynFunction
return 类型将被推断为 Promise<Foo>
您只需要在您的类型中删除 Error
作为联合类型以获得正确的类型定义:
fetchPosts: (id: string) => (dispatch: Dispatch<TActions>) => Promise<string>;
问题出在 mapDispatchToProps
中对 bindActionCreators
的调用。在运行时 bindActionCreators
基本上将此 (id: string) => (dispatch: Dispatch<TActions>) => Promise<string>;
转换为此 (id: string) => Promise<string>;
,但 bindActionCreators
的类型并未反映此转换。这可能是因为要实现这一点,您需要直到最近才可用的条件类型。
如果我们查看 redux 存储库中的 this 示例用法,我们会发现它们通过明确指定函数的类型来完成转换:
const boundAddTodoViaThunk = bindActionCreators<
ActionCreator<AddTodoThunk>,
ActionCreator<AddTodoAction>
>(addTodoViaThunk, dispatch)
我们可以在您的代码中执行相同的操作,引用现有类型,但这会损害类型安全,因为没有检查两种类型中的 fetchPosts
是否会被正确键入:
const mapDispatchToProps = (dispatch: Dispatch<TActions>): Partial<IProps> =>
bindActionCreators<{ fetchPosts: typeof fetchPosts }, Pick<IProps, 'fetchPosts'>>(
{
fetchPosts
},
dispatch
);
或者我们可以使用类型断言,因为上述方法并不能真正提供任何安全性:
const mapDispatchToProps2 = (dispatch: Dispatch<TActions>) =>
bindActionCreators({
fetchPosts: fetchPosts as any as ((id: string) => Promise<string>)
}, dispatch );
为了以真正类型安全的方式做到这一点,我们需要使用 typescript 2.8 和带有辅助函数的条件类型。我们可以按应有的方式输入 bindActionCreators
,并自动为生成的创建者推断出正确的类型:
function mybindActionCreators<M extends ActionCreatorsMapObject>(map: M, dispatch: Dispatch<TActions>) {
return bindActionCreators<M, { [P in keyof M] : RemoveDispatch<M[P]> }>(map, dispatch);
}
const mapDispatchToProps = (dispatch: Dispatch<TActions>) =>
mybindActionCreators(
{
fetchPosts
},
dispatch
);
// Helpers
type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;
type RemoveDispatch<T extends Function> =
T extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E, f: infer F, g: infer G, h: infer H, i: infer I, j: infer J) => (dispatch: Dispatch<any>) => infer R ? (
IsValidArg<J> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => R :
IsValidArg<I> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => R :
IsValidArg<H> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => R :
IsValidArg<G> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => R :
IsValidArg<F> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F) => R :
IsValidArg<E> extends true ? (a: A, b: B, c: C, d: D, e: E) => R :
IsValidArg<D> extends true ? (a: A, b: B, c: C, d: D) => R :
IsValidArg<C> extends true ? (a: A, b: B, c: C) => R :
IsValidArg<B> extends true ? (a: A, b: B) => R :
IsValidArg<A> extends true ? (a: A) => R :
() => R
) : T;
谢谢@Thai Duong Tran 和@Titian Cernicova-Dragomir。
我发现您提供的两个答案不一致。
1:
在道具中,我可以说函数具有原始函数的类型,而不是重新声明所有参数类型和 return 类型:fetchPosts: typeof fetchPosts
(感谢@titian-cernicova-dragomir)
2:
现在我可以使用那个功能了,但不是一个承诺。为了实现这一承诺,我可以使用@thai-duong-tran.
提供的解决方案
const fetchPromise = new Promise(resolve => {
this.props.fetchPosts("adidas");
});
您可以在此处查看工作演示:https://codesandbox.io/s/zo15pj633
尝试以下操作:
fetchPosts: (id: string) => void;
我有一个 redux thunk 操作,它获取一些数据然后分派一些操作(此处的代码中未显示,但您可以在下面的演示 link 中找到它)
export const fetchPosts = (id: string) => (dispatch: Dispatch<TActions>) => {
return fetch('http://example.com').then(
response => {
return response.json().then(json => {
return "Success message";
});
},
err => {
throw err;
}
);
};
而不是在我的组件中,我使用 mapDispatchToProps
和 bindActionCreators
从我的组件中调用此函数,如下所示:
public fetchFunc() {
this.props.fetchPosts("test").then(
res => {
console.log("Res from app", res);
},
err => {
console.log("Err from app", err);
}
);
}
由于我使用的是typescript,所以我需要在Props中定义这个函数的类型
interface IProps {
name?: string;
posts: IPost[];
loading: boolean;
fetchPosts: (id: string) => Promise<string | Error>;
}
如果我像上面那样做,Typescript 会抱怨我应该这样做:
fetchPosts: (id: string) => (dispatch: Dispatch<TActions>) => Promise<string | Error>;
如果我这样做,当我在我的组件中使用 then
时,Typescript 会抱怨说该功能不是一个承诺。
我创建了一个演示,您可以在其中 fiddle 使用代码
按"Load from remote"有时会失败,只是为了看看是否承诺:
基本上,在 Typescript 中,promise 的泛型类型只能从 resolve
中推断出来。
例如
function asyncFunction() {
return new Promise((resolve, reject) => {
const a = new Foo();
resolve(a);
})
}
asynFunction
return 类型将被推断为 Promise<Foo>
您只需要在您的类型中删除 Error
作为联合类型以获得正确的类型定义:
fetchPosts: (id: string) => (dispatch: Dispatch<TActions>) => Promise<string>;
问题出在 mapDispatchToProps
中对 bindActionCreators
的调用。在运行时 bindActionCreators
基本上将此 (id: string) => (dispatch: Dispatch<TActions>) => Promise<string>;
转换为此 (id: string) => Promise<string>;
,但 bindActionCreators
的类型并未反映此转换。这可能是因为要实现这一点,您需要直到最近才可用的条件类型。
如果我们查看 redux 存储库中的 this 示例用法,我们会发现它们通过明确指定函数的类型来完成转换:
const boundAddTodoViaThunk = bindActionCreators<
ActionCreator<AddTodoThunk>,
ActionCreator<AddTodoAction>
>(addTodoViaThunk, dispatch)
我们可以在您的代码中执行相同的操作,引用现有类型,但这会损害类型安全,因为没有检查两种类型中的 fetchPosts
是否会被正确键入:
const mapDispatchToProps = (dispatch: Dispatch<TActions>): Partial<IProps> =>
bindActionCreators<{ fetchPosts: typeof fetchPosts }, Pick<IProps, 'fetchPosts'>>(
{
fetchPosts
},
dispatch
);
或者我们可以使用类型断言,因为上述方法并不能真正提供任何安全性:
const mapDispatchToProps2 = (dispatch: Dispatch<TActions>) =>
bindActionCreators({
fetchPosts: fetchPosts as any as ((id: string) => Promise<string>)
}, dispatch );
为了以真正类型安全的方式做到这一点,我们需要使用 typescript 2.8 和带有辅助函数的条件类型。我们可以按应有的方式输入 bindActionCreators
,并自动为生成的创建者推断出正确的类型:
function mybindActionCreators<M extends ActionCreatorsMapObject>(map: M, dispatch: Dispatch<TActions>) {
return bindActionCreators<M, { [P in keyof M] : RemoveDispatch<M[P]> }>(map, dispatch);
}
const mapDispatchToProps = (dispatch: Dispatch<TActions>) =>
mybindActionCreators(
{
fetchPosts
},
dispatch
);
// Helpers
type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;
type RemoveDispatch<T extends Function> =
T extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E, f: infer F, g: infer G, h: infer H, i: infer I, j: infer J) => (dispatch: Dispatch<any>) => infer R ? (
IsValidArg<J> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => R :
IsValidArg<I> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => R :
IsValidArg<H> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => R :
IsValidArg<G> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => R :
IsValidArg<F> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F) => R :
IsValidArg<E> extends true ? (a: A, b: B, c: C, d: D, e: E) => R :
IsValidArg<D> extends true ? (a: A, b: B, c: C, d: D) => R :
IsValidArg<C> extends true ? (a: A, b: B, c: C) => R :
IsValidArg<B> extends true ? (a: A, b: B) => R :
IsValidArg<A> extends true ? (a: A) => R :
() => R
) : T;
谢谢@Thai Duong Tran 和@Titian Cernicova-Dragomir。
我发现您提供的两个答案不一致。
1:
在道具中,我可以说函数具有原始函数的类型,而不是重新声明所有参数类型和 return 类型:fetchPosts: typeof fetchPosts
(感谢@titian-cernicova-dragomir)
2:
现在我可以使用那个功能了,但不是一个承诺。为了实现这一承诺,我可以使用@thai-duong-tran.
提供的解决方案const fetchPromise = new Promise(resolve => {
this.props.fetchPosts("adidas");
});
您可以在此处查看工作演示:https://codesandbox.io/s/zo15pj633
尝试以下操作:
fetchPosts: (id: string) => void;