代码之家  ›  专栏  ›  技术社区  ›  Adrian Florescu

Typescript Redux Thunk(类型)

  •  14
  • Adrian Florescu  · 技术社区  · 6 年前

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

    而不是我使用的组件 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 在我的组件中,我说该功能不是承诺。

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

    按“Load from remote”(从远程加载)有时会失败,只是为了查看承诺是否:

    https://codesandbox.io/s/v818xwl670

    4 回复  |  直到 6 年前
        1
  •  8
  •   Titian Cernicova-Dragomir    6 年前

    问题是 bindActionCreators 在里面 mapDispatchToProps 。在运行时 bindActionCreators 基本上改变了这一点 (id: string) => (dispatch: Dispatch<TActions>) => Promise<string>; 进入这个 (id: string) => Promise<string>; ,但类型 bindActionCreators 不反映此转换。这可能是因为要实现这一点,您需要直到最近才可用的条件类型。

    如果我们看 this redux repo中的示例用法,我们看到它们通过显式指定函数类型来完成转换:

    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;
    
        2
  •  2
  •   Thai Duong Tran    6 年前

    基本上,在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>;

        3
  •  1
  •   Adrian Florescu    6 年前

    谢谢您@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

        4
  •  0
  •   Guillaume Caggia    4 年前

    请尝试以下操作:

    fetchPosts: (id: string) => void;