Типы машинописных редуксов (типы)

У меня есть действие reducex 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);
        }
    );
}

Поскольку я использую машинопись, мне нужно определить тип этой функции в реквизитах

interface IProps {
    name?: string;
    posts: IPost[];
    loading: boolean;
    fetchPosts: (id: string) => Promise<string | Error>;
}

Если я сделаю это, как описано выше, TypScript будет жаловаться на то, что я должен это сделать:

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

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

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

Нажатие "Load from remote" иногда может быть просто для того, чтобы убедиться, что обещание:

https://codesandbox.io/s/v818xwl670

Ответ 1

Спасибо @Thai Duong Tran и @Титиан Черникова-Драгомир.

Я нашел смесь между двумя предоставленными вами ответами.

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

Ответ 2

Проблема заключается в вызове 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 ); 

Для действительно безопасного типа для этого нам нужно использовать машинописные тексты 2.8 и условные типы со вспомогательной функцией. Мы можем bindActionCreators так, как он должен, и автоматически выводить правильный тип для 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;

Ответ 3

В принципе, в Typcript, общий тип обещания будет выведен только из resolve.

Например

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

Возвращаемый тип asynFunction будет выведен как Promise<Foo>

Вам просто нужно удалить Error в качестве типа union в вашем типе, чтобы получить правильное определение типа:

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