web-dev-qa-db-ja.com

Typescript Redux Thunk(タイプ)

いくつかのデータをフェッチしてからいくつかのアクションをディスパッチするreduxサンクアクションがあります(ここのコードには示されていませんが、以下のデモリンクで見つけることができます)

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を使用しているので、小道具でこの関数のタイプを定義する必要があります

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/v818xwl67

14
Adrian Florescu

@Thai DuongTranと@ TitianCernicova-Dragomirに感謝します。

私はあなたが提供した2つの答えの混合を見つけました。

1:

小道具では、すべての引数の型と戻り値の型を再宣言する代わりに、関数は元の関数のtypeを次のように持っていると言えます。fetchPosts: typeof fetchPosts(@ titian-cernicova-dragomirに感謝)

2:

これで、その関数を使用できますが、約束としては使用できません。それを約束として機能させるために、@ thai-duong-tranが提供するソリューションを使用できます。

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

ここで動作するデモを見ることができます: https://codesandbox.io/s/zo15pj6

1
Adrian Florescu

問題は、bindActionCreators内のmapDispatchToPropsの呼び出しです。実行時にbindActionCreatorsは基本的にこれを変換します(id: string) => (dispatch: Dispatch<TActions>) => Promise<string>;これに(id: string) => Promise<string>;ですが、bindActionCreatorsのタイプはこの変換を反映していません。これはおそらく、これを実現するために、最近まで利用できなかった条件付き型が必要になるという事実によるものです。

this reduxリポジトリのサンプル使用法を見ると、関数のタイプを明示的に指定することで変換が行われていることがわかります。

const boundAddTodoViaThunk = bindActionCreators<
  ActionCreator<AddTodoThunk>,
  ActionCreator<AddTodoAction>
>(addTodoViaThunk, dispatch)

既存の型を参照してコードで同じことを行うこともできますが、2つの型の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 ); 

これを行うための真にタイプセーフな方法を得るには、TypeScript2.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;

基本的に、TypeScriptでは、promiseのジェネリック型は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
Thai Duong Tran