【问题标题】:Typescript Redux Thunk (Types)Typescript Redux Thunk(类型)
【发布时间】:2018-08-31 15:43:24
【问题描述】:

我有一个 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);
        }
    );
}

由于我使用的是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 会抱怨说该功能不是承诺。

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

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

https://codesandbox.io/s/v818xwl670

【问题讨论】:

  • 当你第一次发帖时我看到了这个问题,并敲了我的头一会儿,但无法弄清楚。我相信bindActionCreators 的签名是错误的,因为在运行时,如果你传递一个返回函数的函数,结果是一个函数,该函数将使用dispatch 自动调用第二个函数,但打字不反映这个,他们只是返回一个具有相同类型的函数。但是我对 redux 的了解不够,无法肯定地说明这一点
  • 为了解决这个问题,我使用了一个类型断言,它既可以编译又具有预期的运行时行为,但感觉很hackish:bindActionCreators( { fetchPosts: fetchPosts as any as ((id: string) =&gt; Promise&lt;string&gt;) }, dispatch );

标签: reactjs typescript redux-thunk


【解决方案1】:

尝试以下方法:

fetchPosts: (id: string) => void;

【讨论】:

    【解决方案2】:

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

    我发现你提供的两个答案混合在一起。

    1:

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

    2:

    现在我可以使用该功能,但不能作为承诺。为了实现这一承诺,我可以使用@thai-duong-tran 提供的解决方案。

    const fetchPromise = new Promise(resolve => {
        this.props.fetchPosts("adidas");
    });
    

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

    【讨论】:

    • 首先,干得好。我只是在最新的 CRA 中测试了你的代码,它在 fetchPosts 调度中给出了一个错误; (TS) 类型参数 '{ type: string;加载:布尔值; }' 不可分配给“TActions”类型的参数。类型'{类型:字符串;加载:布尔值; }' 不可分配给类型 'IALoadingPosts'。属性“类型”的类型不兼容。类型 'string' 不可分配给类型 '"LOADING_POSTS"'。知道如何解决这个问题吗?
    【解决方案3】:

    问题是在mapDispatchToProps 中对bindActionCreators 的调用。在运行时bindActionCreators 基本上将这个(id: string) =&gt; (dispatch: Dispatch&lt;TActions&gt;) =&gt; Promise&lt;string&gt;; 转换成这个(id: string) =&gt; Promise&lt;string&gt;;,但是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;
    

    【讨论】:

      【解决方案4】:

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

      例如

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

      asynFunction 返回类型将被推断为Promise&lt;Foo&gt;

      您只需将 Error 作为类型中的联合类型删除即可获得正确的类型定义:

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

      【讨论】:

      • 感谢 Thai,我尝试添加您提到的类型,但它说 then 在类型调度中不存在。随意摆弄上面发布的代码框链接