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

и чем в моем компоненте я использую mapDispatchToProps С bindActionCreators чтобы вызвать эту функцию из моего компонента следующим образом:

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

если я делаю это так, то Typescript жалуется, когда я использую then в моем компоненте, говоря, что эта функция не является обещанием.

Я создал демо, где вы можете возиться с кодом

нажатие "загрузить с пульта дистанционного управления" иногда не удается просто увидеть, если обещание:

https://codesandbox.io/s/v818xwl670

3 ответов


проблема-это вызов bindActionCreators на mapDispatchToProps. Во время выполнения bindActionCreators в основном преобразует это (id: string) => (dispatch: Dispatch<TActions>) => Promise<string>; в этой (id: string) => Promise<string>;, но типа bindActionCreators не отражает это преобразование. Вероятно, это связано с тем, что для этого вам понадобятся условные типы, которые до недавнего времени были недоступны.

если мы посмотрим на этой пример использования из репозитория redux, мы видим, что они выполняют преобразование, указывая типы функции явно:

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;

в основном, в 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>;


спасибо @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