【问题标题】:Generic Typescript Type + React hook通用 Typescript 类型 + React 钩子
【发布时间】:2021-03-21 08:44:57
【问题描述】:

我有以下 http 钩子:

export const useHttp = <T,>(initUrl: string, initData: T) => {
    const [url, setUrl] = useState(initUrl);
    const [state, dispatch] = useReducer(fetchReducer, {
        isLoading: false,
        error: '',
        data: initData
    });

    useEffect(() => {
        let cancelRequest = false;

        const fetchData = async (cancelRequest: boolean = false) => {
            if (!url) return;

            dispatch({ type: 'API_REQUEST'});
            try {
                const responsePromise: AxiosPromise<T> = axios(url);
                const response = await responsePromise;
                if (cancelRequest) return;
                dispatch({ type: 'API_SUCCESS', payload: response.data });
            } catch (e) {
                console.log("Got error", e);
                dispatch({ type: 'API_ERROR', payload: e.message });
            }
        };
        fetchData(cancelRequest);

        return () => {
            cancelRequest = true;
        }

    }, [url]);

    const executeFetch = (url: string) => {
        setUrl(url);
    };

    return { ...state, executeFetch}
};

减速机:

const fetchReducer = <T,>(state: IState<T>, action: TAction<T>): IState<T> => {
    switch (action.type) {
        case 'API_REQUEST':
            return {
                ...state,
                isLoading: true
            };
        case 'API_SUCCESS':
            return {
                ...state,
                data: action.payload,
                isLoading: false,
                error: ''
            };
        case 'API_ERROR':
            console.error(`Triggered: ${API_ERROR}, message: ${action.payload}`);
            return {
                ...state,
                error: action.payload,
                isLoading: false,
            };
        default:
            throw Error('Invalid action');
    }
};

行动:

export interface IApiSuccess<T> {
    type: types.ApiSuccess,
    payload: T;
}
export type TAction<T> = IApiRequest | IApiSuccess<T> | IApiError;

这样使用:

const { data, error, isLoading, executeFetch } = useHttp<IArticle[]>('news', []);

return (
        <>
            <div className={classes.articleListHeader}>
                <h1>Article List</h1>
                <small className={classes.headerSubtitle}>{data.length} Articles</small>
            </div>
            <ul>
                {data.map(article => <Article article={article}/>)}
            </ul>
        </>
    )

我的 TS 对我大喊大叫,因为我正在使用 data 变量:Object is of type 'unknown'. TS2571

我确实指定了 useHttp 的类型,即 IArtlce[]。 知道我缺少什么吗?

更新: 我试图为我的减速器添加返回类型:

interface HttpReducer<T> extends IState<T> {
    executeFetch: (url: string) => void
}

export const useHttp = <T,>(initUrl: string, initData: T): HttpReducer<T> => {

但我明白了:

Type '{ executeFetch: (url: string) => void; error: string; isLoading: boolean; data: unknown; }' is not assignable to type 'HttpReducer<T>'.

【问题讨论】:

  • 尝试将类型添加到state。或者向自定义钩子添加返回类型。
  • state 中添加类型是什么意思?我的状态参数有IState&lt;T&gt; 类型。
  • 问题中没有提到...您是否尝试添加返回类型?
  • fetchReducer 是如何输入的?
  • @RameshReddy 不确定如何为我的案例添加这种类型。用示例更新了我的问题

标签: reactjs typescript react-hooks typescript-generics use-reducer


【解决方案1】:

我可以reproduce your error。您期望useReducer 钩子能够根据初始状态的类型推断状态类型,但它只是推断IState&lt;unknown&gt;

useReducerare defined 的类型使得泛型参数是 reducer 的类型。状态的类型是从具有 ReducerState 实用程序类型的 reducer 中推断出来的。它不期望一个通用的减速器,并且不能很好地使用它。

钩子的T 和reducer 的T 没有关系,而不是状态。 fetchReducer 是一个通用函数,这意味着它可以采用 any IState 并返回相同类型的 IState。我们可以使用这个函数来处理我们的钩子的IState&lt;T&gt;,但是为了推断状态的类型,我们需要说我们的函数将接受并返回IState&lt;T&gt;

您需要将useReducer 的泛型设置为:

const [state, dispatch] = useReducer<(state: IState<T>, action: TAction<T>) => IState<T>>( ...

从表面上看,这与现在推断的非常相似,即:

const [state, dispatch] = useReducer<<T,>(state: IState<T>, action: TAction<T>) => IState<T>>(...

但差异至关重要。当前描述了一个通用函数,而修复描述了一个只采用T 类型的函数——useHttp 钩子。这具有误导性,因为您对两者都使用了T。如果我们重命名一个,也许更容易看到。

我们一个通用函数:

export const useHttp = <Data,>(initUrl: string, initData: Data) => {
  const [url, setUrl] = useState(initUrl);
  const [state, dispatch] = useReducer<<T,>(state: IState<T>, action: TAction<T>) => IState<T>>(fetchReducer, {

我们需要该功能的特定用例:

export const useHttp = <Data,>(initUrl: string, initData: Data) => {
  const [url, setUrl] = useState(initUrl);
  const [state, dispatch] = useReducer<(state: IState<Data>, action: TAction<Data>) => IState<Data>>(fetchReducer, {

当我们知道我们的reducer状态类型是IState&lt;Data&gt;,那么我们就知道data的类型是Data

现在调用useHttp&lt;IArticle[]&gt;() 会为您提供data 类型为IArticle[] 的变量。

Typescript Playground Link

【讨论】:

    猜你喜欢
    • 2022-12-05
    • 1970-01-01
    • 2017-06-24
    • 2019-05-10
    • 2020-04-08
    • 2021-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多