Ввод пользовательского «useDispatch» с помощью `unwrapResult` из redux-toolkit

#typescript #react-redux #react-hooks #redux-toolkit

#typescript #реагировать-redux #реагирующие хуки #redux-toolkit

Вопрос:

У меня есть пользовательский хук, который переносит react-redux useDispatch метод и уведомляет меня, если он отклонен. Проблема в том, что я не могу определить разрешенный тип.

 // useAction
type AnyFn = (...s: any[]) => any;

export default <T extends AnyFn>(actionCreator: T) => {
  const dispatch = useDispatch<(action: any) => Promise<any>>(); // * Note Promise<any>

  const action = useCallback((...args: Parameters<typeof actionCreator>) => {
    return dispatch(actionCreator(...args))
      .then(result => {
        return unwrapResult(result) // @reduxjs/toolkit
      })
      .catch(err => {
        // the reason this hook is needed
        // notify error ... then
        return Promise.reject(err)
      })
    }, [dispatch])

  return [action] as const;
}
  
 // action
const fetchUsersActionCreator = createAsyncThunk(
  'fetch', (id: number) => Promise.resolve([id]) // return type IUser[]
)

// consumer
const [fetchUsers] = useAction(fetchUsersActionCreator);

useEffect(() => {
  fetchUsers()
    .then((result) => {
      console.log(result.length) // Property 'length' does not exist on type PayloadAction<IUser[]...
    })
}, [fetchUsers])

// But if I unwrap results here the result type is inferred correctly

useEffect(() => {
  fetchUsers()
    .then(unwrapResult)
    .then((result) => {
      console.log(result.length) // OK
    })
}, [fetchUsers])
  

Итак, как я мог бы ввести useAction хук, чтобы разрешенный тип выводился из возвращаемого типа unwrapResult ? Я знаю, что это где-то связано с наборами useDispatch , но я не могу указать на это…

Любая помощь приветствуется

РЕДАКТИРОВАТЬ: я указал, что действие является асинхронным.

Ответ №1:

Я бы предположил, что ваша проблема уже решилась бы сама собой, если бы вы использовали useDispatch тип, который вводится, как описано в документах:

 import { configureStore } from '@reduxjs/toolkit'
import { useDispatch } from 'react-redux'
import rootReducer from './rootReducer'

const store = configureStore({
  reducer: rootReducer
})

export type AppDispatch = typeof store.dispatch
export const useAppDispatch = () => useDispatch<AppDispatch>() // Export a hook that can be reused to resolve types
  

Если это не поможет, вы можете скопировать и использовать PayloadForActionTypesExcludingErrorActions тип из источника RTK, но имейте в виду, что это поведение немного изменится со следующим выпуском, когда этот PR будет объединен. Если вам нужно скопировать этот тип, вам, вероятно, потребуется настроить его в следующем выпуске.

Комментарии:

1. Спасибо за ответ, я пробовал это раньше, но useAppDispatch даже не похож на обещание, может быть, потому, что у меня расширенный магазин? вывод: const useAppDispatch: () => ThunkDispatch<{ router: ..., user: ... }, null, AnyAction> amp; ThunkDispatch<...> amp; Dispatch<...>

2. Сам по себе useAppDispatch не похож на обещание, но вызов useAppDispatch с помощью thunk в качестве аргумента (и только тогда!) Должен возвращать обещание.

Ответ №2:

action Функция в вашем useAction хуке — это функция, которая принимает те же аргументы, actionCreator что и и возвращает a Promise , которое преобразуется в payload свойство созданного действия.

Мне не нравятся все обратные выводы здесь, и я не знаю, почему вы возвращаете кортеж длиной 1 вместо того, чтобы возвращать функцию напрямую, но этот возвращаемый тип должен работать на основе вашего текущего кода. Вы можете удалить readonly , если вы удалите as const из своего возврата.

 const useAction = <T extends AnyFn>(
    actionCreator: T
): readonly [(...args: Parameters<typeof actionCreator>) => Promise<ReturnType<typeof actionCreator>['payload']>]
  

Ссылка на игровую площадку

Я бы написал это так:

 const useAction = <Args extends any[], Action extends PayloadAction<any, any>>(
  actionCreator: (...args: Args) => Action
): (...args: Args) => Promise<Action['payload']> => {
  const dispatch = useDispatch<(action: any) => Promise<any>>(); // * Note Promise<any>

  return useCallback(async (...args: Args) => {
    return dispatch(actionCreator(...args))
      .then(result => {
        return unwrapResult(result) // @reduxjs/toolkit
      })
      .catch(err => {
        return Promise.reject(err)
      })
  }, [dispatch])
}
  

Ссылка на игровую площадку

Комментарии:

1. Спасибо за ответ, однако разрешенный тип здесь не указан, я создал игровую площадку , и результат по-прежнему любой

2. Да, перенос возвращаемого значения в массив был вызван тем, что раньше у меня было больше элементов, но с тех пор я его удалил.

Ответ №3:

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

Спасибо @phry и @linda-paiste за ваши ответы, поскольку они указали мне правильное направление.

 import { unwrapResult, AsyncThunk } from '@reduxjs/toolkit';
import { AppDispatch } from '../store'; // type AppDispatch = typeof store.dispatch

// useAction aka useAsyncThunk
export default <Arg, Returned>(
  actionCreator: AsyncThunk<Returned, Arg, {}>
) => {
  const dispatch = useDispatch<AppDispatch>();

  return useCallback((arg: Arg) => {
    return dispatch(actionCreator(arg))
    .then(result => {
      return unwrapResult(result) // @reduxjs/toolkit
    })
    .catch(err => {
      // the reason this hook is needed
      // notify error ... then
      return Promise.reject(err)
    })
  }, [dispatch, actionCreator])
};