打字稿Redux Thunk(类型)

时间:2018-03-22 09:16:34

标签: reactjs typescript redux-thunk

我有一个redux thunk动作,它获取一些数据然后调度一些动作(这里没有显示代码,但是你可以在下面的演示链接中找到它)

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;
    }
    );
};

而且在我的组件中,我使用mapDispatchToPropsbindActionCreators从我的组件中调用此函数,如下所示:

public fetchFunc() {
    this.props.fetchPosts("test").then(
        res => {
        console.log("Res from app", res);
        },
        err => {
        console.log("Err from app", err);
        }
    );
}

由于我使用的是打字稿,我需要在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会抱怨。

我创建了一个演示,您可以在其中摆弄代码

按“从远程加载”有时会失败,只是看看是否承诺:

https://codesandbox.io/s/v818xwl670

4 个答案:

答案 0 :(得分:7)

问题是bindActionCreators中对mapDispatchToProps的调用。在运行时bindActionCreators基本上将此(id: string) => (dispatch: Dispatch<TActions>) => Promise<string>;转换为此(id: string) => Promise<string>;,但bindActionCreators的类型并未反映此转换。这可能是因为要实现这一目标,您需要条件类型,直到最近才可用。

如果我们从redux repo中查看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;

答案 1 :(得分:2)

基本上,在Typescript中,承诺的泛型类型将仅从resolve推断出来。

例如

function asyncFunction() {
    return new Promise((resolve, reject) => {
       const a = new Foo();
       resolve(a);
    })
}

asynFunction返回类型将被推断为Promise<Foo>

您只需要在类型中删除Error作为联合类型,以获得正确的类型定义:

fetchPosts: (id: string) => (dispatch: Dispatch<TActions>) => Promise<string>;

答案 2 :(得分:1)

谢谢@Thai Duong Tran和@Titian Cernicova-Dragomir。

我发现你提供的两个答案之间存在混合。

1:

在props中,我可以说函数具有原始函数的类型,而不是重新声明所有参数类型和返回类型:fetchPosts: typeof fetchPosts(感谢@ titian-cernicova-dragomir)

2:

现在我可以使用该功能,但不能作为承诺。为了使这项工作成为一种承诺,我可以使用@ thai-duong-tran。

提供的解决方案
const fetchPromise = new Promise(resolve => {
    this.props.fetchPosts("adidas");
});

您可以在此处查看工作演示:https://codesandbox.io/s/zo15pj633

答案 3 :(得分:0)

尝试以下操作:

fetchPosts: (id: string) => void;