通用 Typescript 类型 + React hook
Generic Typescript Type + React hook
我有以下 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[]。
知道我错过了什么吗?
更新:
我试图为我的减速器添加 return 类型:
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>'.
我能够 reproduce your error。您期望 useReducer
挂钩能够根据初始状态的类型推断状态类型,但它只是推断 IState<unknown>
.
useReducer
are defined 的类型使得通用参数是减速器的类型。状态的类型是从具有 ReducerState
实用程序类型的减速器推断出来的。它不期望通用减速器并且不能很好地与它一起工作。
hook的T
和reducer的T
没有关系,不是状态。 fetchReducer
是一个通用函数,这意味着它可以接受 any IState
和 return 一个 IState
相同类型。我们可以使用这个函数来处理我们钩子的IState<T>
,但是为了推断状态的类型我们需要说我们的函数将只接受和return IState<T>
.
您需要将 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<Data>
,那么我们就知道data
的类型是Data
.
现在调用 useHttp<IArticle[]>()
会得到一个 data
类型为 IArticle[]
的变量。
我有以下 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[]。 知道我错过了什么吗?
更新: 我试图为我的减速器添加 return 类型:
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>'.
我能够 reproduce your error。您期望 useReducer
挂钩能够根据初始状态的类型推断状态类型,但它只是推断 IState<unknown>
.
useReducer
are defined 的类型使得通用参数是减速器的类型。状态的类型是从具有 ReducerState
实用程序类型的减速器推断出来的。它不期望通用减速器并且不能很好地与它一起工作。
hook的T
和reducer的T
没有关系,不是状态。 fetchReducer
是一个通用函数,这意味着它可以接受 any IState
和 return 一个 IState
相同类型。我们可以使用这个函数来处理我们钩子的IState<T>
,但是为了推断状态的类型我们需要说我们的函数将只接受和return IState<T>
.
您需要将 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<Data>
,那么我们就知道data
的类型是Data
.
现在调用 useHttp<IArticle[]>()
会得到一个 data
类型为 IArticle[]
的变量。