通用 Typescript Type + React hook [英] Generic Typescript Type + React hook

查看:62
本文介绍了通用 Typescript Type + React hook的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下 http 钩子:

export const useHttp = (initUrl: string, initData: T) =>{const [url, setUrl] = useState(initUrl);const [状态,调度] = useReducer(fetchReducer, {isLoading: 假,错误: '',数据:初始化数据});useEffect(() => {让取消请求 = 假;const fetchData = async (cancelRequest: boolean = false) =>{如果(!url)返回;dispatch({ type: 'API_REQUEST'});尝试 {const responsePromise: AxiosPromise= axios(url);常量响应 = 等待响应承诺;如果(取消请求)返回;dispatch({ type: 'API_SUCCESS', payload: response.data });}赶上(e){console.log(出现错误", e);dispatch({ type: 'API_ERROR', payload: e.message });}};fetchData(cancelRequest);返回 () =>{取消请求 = 真;}}, [网址]);const executeFetch = (url: string) =>{设置网址(网址);};返回 { ...状态,executeFetch}};

减速器:

const fetchReducer = (state: IState, action: TAction): IState=>{开关(动作.类型){案例API_REQUEST":返回 {...状态,isLoading: 真};案例'API_SUCCESS':返回 {...状态,数据:action.payload,isLoading: 假,错误: ''};案例'API_ERROR':console.error(`触发:${API_ERROR},消息:${action.payload}`);返回 {...状态,错误:action.payload,isLoading: 假,};默认:throw Error('无效动作');}};

动作:

导出接口IApiSuccess{类型:types.ApiSuccess,有效载荷:T;}导出类型 TAction= IApiRequest |IApiSuccess<T>|IApi错误;

像这样使用:

const { data, error, isLoading, executeFetch } = useHttp('news', []);返回 (<><div className={classes.articleListHeader}><h1>文章列表</h1><small className={classes.headerSubtitle}>{data.length} 文章</small>

<ul>{data.map(article => <Article article={article}/>)}</>)

我的 TS 对我大喊大叫,因为我使用了 data 变量:Object 的类型是unknown".TS2571

我确实指定了 useHttp 的类型,即 IArtlce[].知道我错过了什么吗?

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

接口 HttpReducer;扩展 IState<T>{executeFetch: (url: string) =>空白}export const useHttp = (initUrl: string, initData: T): HttpReducer=>{

但我明白了:

Type '{ executeFetch: (url: string) =>空白;错误:字符串;isLoading:布尔值;数据:未知;}' 不可分配给类型 'HttpReducer'.

解决方案

我能够重现您的错误.您期望 useReducer 钩子能够根据初始状态的类型推断状态类型,但它只是推断 IState.

useReducer 的类型 被定义 使得泛型参数是reducer的类型.状态的类型是从具有 ReducerState 实用程序类型的 reducer 中推断出来的.它不期待通用的减速器并且不能很好地使用它.

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

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

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


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

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

但差异至关重要.当前描述了一个通用函数,而修复描述了一个函数,它只采用一种类型的 T —— 那种类型的 useHttp 钩子.这是误导,因为您同时使用 T .或许我们重命名一个更容易看出来.

我们一个通用函数:

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

我们需要该函数的特定用例:

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

当我们知道我们的reducer state类型是IState,那么我们就知道data的类型是Data.

现在调用 useHttp() 会给你一个 data 类型为 IArticle[] 的变量.

Typescript Playground 链接

I have the following http hook:

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

Reducer:

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

actions:

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

Using like this:

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

My TS yell at me because I'm using the data variable: Object is of type 'unknown'. TS2571

I did specify the type of the useHttp which is IArtlce[]. Any idea what i'm missing?

Update: I tried to add return type for my reducer:

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

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

but I get:

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

解决方案

I was able to reproduce your error. You are expecting that the useReducer hook will be able to infer the state type based on the type of the initial state, but it's just inferring IState<unknown>.

The types for useReducer are defined such that the generic argument is the type of the reducer. The type for the state is inferred from the reducer with the ReducerState utility type. It's not expecting a generic reducer and doesn't work well with it.

There is no relation between the T of the hook and the T of the reducer rather than the state. fetchReducer is a generic function which means that it can take any IState and return an IState of the same type. We can use this function to process the IState<T> of our hook, but in order to infer the type of the state we need to say that our function will only accept and return IState<T>.

You need to set the generic on you useReducer to this:

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


On the surface this looks very similar to what's being inferred right now, which is:

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

But the difference is critically important. The current describes a generic function while the fix describes a function that only takes one type of T -- that of the useHttp hook. It's misleading because you are using T for both. Perhaps it is easier to see if we rename one.

We had a generic function:

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, {

We need a specific use case of that function:

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, {

When we know that our reducer state type is IState<Data>, then we know that the type of data is Data.

Now calling useHttp<IArticle[]>() gives you a data variable with type IArticle[].

Typescript Playground Link

这篇关于通用 Typescript Type + React hook的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆