将 React useReducer 与打字稿一起使用时如何为状态设置通用接口
How to setup a generic interface for the state when using React useReducer with typescript
当我尝试设置状态接口时,我遇到了这个问题useReducer.I 在自定义挂钩中使用它从服务器获取数据。服务器响应的数据对象可能包含不同的属性。
useFetcher.ts
const initialState = {
data: null,
loading: false,
error: null,
};
type LIST_RESPONSE = {
page: number;
totalRows: number;
pageSize: number;
totalPages: number;
list: [];
};
interface State {
data: LIST_RESPONSE| null;
loading: boolean;
error: string | null;
}
const reducer = (state: State, action: ACTIONTYPES) => {
switch (action.type) {
case ACTIONS.FETCHING: {
return {
...state,
loading: true,
};
}
case ACTIONS.FETCH_SUCCESS: {
return {
...state,
loading: false,
data: action.data,
};
}
case ACTIONS.ERROR: {
return {
...state,
loading: false,
error: action.error,
};
}
}
};
const useFetcher = (url: string): State => {
const [state, dispatch] = useReducer(reducer, initialState);
const { data, loading, error } = state;
useEffect(() => {
dispatch({ type: ACTIONS.FETCHING });
const fetchData = async () => {
try {
const response = await httpService.get(url);
dispatch({ type: ACTIONS.FETCH_SUCCESS, data: response.data });
return;
} catch (error: unknown) {
const { message } = error as Error;
dispatch({ type: ACTIONS.ERROR, error: message });
}
};
fetchData();
}, [url]);
return { data, loading, error };
};
export default useFetcher;
组件A调用useFetcher:
const { data, loading, error } = useFetcher('productList');
这很好用,它 return 是适合 LIST_RESPONSE 类型的数据对象。但是如果我想在组件B中使用useFetcher函数,它应该是return另一种数据类型,比方说:
type CATEGORTY_RESPONSE= {
categories: [];
};
如何使接口状态中的数据类型通用?例如,当我调用 useFecher 挂钩时,我可以在 State 接口中定义数据类型,如下所示:
组件 A:
const { data, loading, error } = useFetcher<LIST_RESPONSE>('productList');
组件 B:
const { data, loading, error } = useFetcher<CATEGORTY_RESPONSE>('categories');
我试过了
interface State {
data: LIST_RESPONSE|CATEGORTY_RESPONSE| null;
loading: boolean;
error: string | null;
}
或
const useFetcher = <Type extends State>(url: string): Type =>{...}
none 其中有效。
有帮助吗?谢谢
创建通用挂钩
为了使用泛型,您需要在链中的多个点应用该泛型:
useFetcher
挂钩需要知道它的状态类型 returns。
useReducer
钩子需要知道减速器的类型。
reducer
需要知道其状态和操作的类型。
State
需要知道其 data
属性 的类型。
- 成功操作需要知道其
data
属性 的类型。
这是它的样子:
import {useEffect, useReducer, Reducer} from "react";
enum ACTIONS {
FETCHING = 'FETCHING',
FETCH_SUCCESS = 'FETCH_SUCCESS',
ERROR = 'ERROR'
}
type ACTIONTYPES<DataType> = {
type: ACTIONS.FETCHING
} | {
type: ACTIONS.ERROR;
error: string
} | {
type: ACTIONS.FETCH_SUCCESS;
data: DataType;
}
const initialState = {
data: null,
loading: false,
error: null,
};
interface State<DataType> {
data: DataType | null;
loading: boolean;
error: string | null;
}
const reducer = <DataType extends any>(state: State<DataType>, action: ACTIONTYPES<DataType>) => {
switch (action.type) {
case ACTIONS.FETCHING: {
return {
...state,
loading: true,
};
}
case ACTIONS.FETCH_SUCCESS: {
return {
...state,
loading: false,
data: action.data,
};
}
case ACTIONS.ERROR: {
return {
...state,
loading: false,
error: action.error,
};
}
default: {
return state;
}
}
};
const useFetcher = <DataType extends any>(url: string): State<DataType> => {
const [state, dispatch] = useReducer<Reducer<State<DataType>, ACTIONTYPES<DataType>>>(reducer, initialState);
const { data, loading, error } = state;
useEffect(() => {
dispatch({ type: ACTIONS.FETCHING });
const fetchData = async () => {
try {
const response = await httpService.get(url);
dispatch({ type: ACTIONS.FETCH_SUCCESS, data: response.data });
return;
} catch (error: unknown) {
const { message } = error as Error;
dispatch({ type: ACTIONS.ERROR, error: message });
}
};
fetchData();
}, [url]);
return { data, loading, error };
};
export default useFetcher;
我没有使用 Type extends State
,而是始终使用相同的通用值,它指的是我们从服务器获得的 data
的类型。这是唯一发生变化的类型。
烦人的部分是在 useReducer
上设置泛型,因为这里的泛型需要是 reducer 的类型而不是 状态。 React Reducer
类型定义为 Reducer<S, A>
,其中 S
是状态类型,A
是动作类型。所以我们最终得到 Reducer<State<DataType>, ACTIONTYPES<DataType>>
这是你的 reducer
函数的类型,但仅限于特定的 DataType
.
使用通用钩子
useFetcher
挂钩是完全通用的,API 响应数据可以是任何东西。我们不仅限于 LIST_RESPONSE
和 CATEGORTY_RESPONSE
.
例如,您可以这样做:
const { data, loading, error } = useFetcher<string>('someUrl');
并且 data
的类型将被推断为 string | null
。
定义列表响应
您当前的 LIST_RESPONSE
(和 CATEGORTY_RESPONSE
)类型有问题。您已将 list
属性 的类型声明为 []
,这是一个空数组。你需要说它是一个数组,其元素是特定类型。
如果列表项类型始终相同,那么我们可以为其定义一个类型并使用 list: ListItem[];
。如果类型因 URL 而异,那么我们可以使用另一个泛型来描述它。
type LIST_RESPONSE<ItemType> = {
page: number;
totalRows: number;
pageSize: number;
totalPages: number;
list: ItemType[];
};
然后我们可以将您的 useFetcher
挂钩用于任何类型的列表。点赞产品列表:
interface Product {
price: number;
}
const { data, loading, error } = useFetcher<LIST_RESPONSE<Product>>('productList');
if ( data ) {
// has type Product
const product = data.list[0];
// has type number
const price = product.price;
}
如果这有点令人困惑,那么您可以逐步考虑。
A ProductListResponse
是 LIST_RESPONSE
的特定类型,其中列表项的类型为 Product
:
type ProductListResponse = LIST_RESPONSE<Product>
我们的 'productList'
端点 returns data
类型为 ProductListResponse
:
const { data, loading, error } = useFetcher<ProductListResponse>('productList');
当我尝试设置状态接口时,我遇到了这个问题useReducer.I 在自定义挂钩中使用它从服务器获取数据。服务器响应的数据对象可能包含不同的属性。 useFetcher.ts
const initialState = {
data: null,
loading: false,
error: null,
};
type LIST_RESPONSE = {
page: number;
totalRows: number;
pageSize: number;
totalPages: number;
list: [];
};
interface State {
data: LIST_RESPONSE| null;
loading: boolean;
error: string | null;
}
const reducer = (state: State, action: ACTIONTYPES) => {
switch (action.type) {
case ACTIONS.FETCHING: {
return {
...state,
loading: true,
};
}
case ACTIONS.FETCH_SUCCESS: {
return {
...state,
loading: false,
data: action.data,
};
}
case ACTIONS.ERROR: {
return {
...state,
loading: false,
error: action.error,
};
}
}
};
const useFetcher = (url: string): State => {
const [state, dispatch] = useReducer(reducer, initialState);
const { data, loading, error } = state;
useEffect(() => {
dispatch({ type: ACTIONS.FETCHING });
const fetchData = async () => {
try {
const response = await httpService.get(url);
dispatch({ type: ACTIONS.FETCH_SUCCESS, data: response.data });
return;
} catch (error: unknown) {
const { message } = error as Error;
dispatch({ type: ACTIONS.ERROR, error: message });
}
};
fetchData();
}, [url]);
return { data, loading, error };
};
export default useFetcher;
组件A调用useFetcher:
const { data, loading, error } = useFetcher('productList');
这很好用,它 return 是适合 LIST_RESPONSE 类型的数据对象。但是如果我想在组件B中使用useFetcher函数,它应该是return另一种数据类型,比方说:
type CATEGORTY_RESPONSE= {
categories: [];
};
如何使接口状态中的数据类型通用?例如,当我调用 useFecher 挂钩时,我可以在 State 接口中定义数据类型,如下所示: 组件 A:
const { data, loading, error } = useFetcher<LIST_RESPONSE>('productList');
组件 B:
const { data, loading, error } = useFetcher<CATEGORTY_RESPONSE>('categories');
我试过了
interface State {
data: LIST_RESPONSE|CATEGORTY_RESPONSE| null;
loading: boolean;
error: string | null;
}
或
const useFetcher = <Type extends State>(url: string): Type =>{...}
none 其中有效。 有帮助吗?谢谢
创建通用挂钩
为了使用泛型,您需要在链中的多个点应用该泛型:
useFetcher
挂钩需要知道它的状态类型 returns。useReducer
钩子需要知道减速器的类型。reducer
需要知道其状态和操作的类型。State
需要知道其data
属性 的类型。- 成功操作需要知道其
data
属性 的类型。
这是它的样子:
import {useEffect, useReducer, Reducer} from "react";
enum ACTIONS {
FETCHING = 'FETCHING',
FETCH_SUCCESS = 'FETCH_SUCCESS',
ERROR = 'ERROR'
}
type ACTIONTYPES<DataType> = {
type: ACTIONS.FETCHING
} | {
type: ACTIONS.ERROR;
error: string
} | {
type: ACTIONS.FETCH_SUCCESS;
data: DataType;
}
const initialState = {
data: null,
loading: false,
error: null,
};
interface State<DataType> {
data: DataType | null;
loading: boolean;
error: string | null;
}
const reducer = <DataType extends any>(state: State<DataType>, action: ACTIONTYPES<DataType>) => {
switch (action.type) {
case ACTIONS.FETCHING: {
return {
...state,
loading: true,
};
}
case ACTIONS.FETCH_SUCCESS: {
return {
...state,
loading: false,
data: action.data,
};
}
case ACTIONS.ERROR: {
return {
...state,
loading: false,
error: action.error,
};
}
default: {
return state;
}
}
};
const useFetcher = <DataType extends any>(url: string): State<DataType> => {
const [state, dispatch] = useReducer<Reducer<State<DataType>, ACTIONTYPES<DataType>>>(reducer, initialState);
const { data, loading, error } = state;
useEffect(() => {
dispatch({ type: ACTIONS.FETCHING });
const fetchData = async () => {
try {
const response = await httpService.get(url);
dispatch({ type: ACTIONS.FETCH_SUCCESS, data: response.data });
return;
} catch (error: unknown) {
const { message } = error as Error;
dispatch({ type: ACTIONS.ERROR, error: message });
}
};
fetchData();
}, [url]);
return { data, loading, error };
};
export default useFetcher;
我没有使用 Type extends State
,而是始终使用相同的通用值,它指的是我们从服务器获得的 data
的类型。这是唯一发生变化的类型。
烦人的部分是在 useReducer
上设置泛型,因为这里的泛型需要是 reducer 的类型而不是 状态。 React Reducer
类型定义为 Reducer<S, A>
,其中 S
是状态类型,A
是动作类型。所以我们最终得到 Reducer<State<DataType>, ACTIONTYPES<DataType>>
这是你的 reducer
函数的类型,但仅限于特定的 DataType
.
使用通用钩子
useFetcher
挂钩是完全通用的,API 响应数据可以是任何东西。我们不仅限于 LIST_RESPONSE
和 CATEGORTY_RESPONSE
.
例如,您可以这样做:
const { data, loading, error } = useFetcher<string>('someUrl');
并且 data
的类型将被推断为 string | null
。
定义列表响应
您当前的 LIST_RESPONSE
(和 CATEGORTY_RESPONSE
)类型有问题。您已将 list
属性 的类型声明为 []
,这是一个空数组。你需要说它是一个数组,其元素是特定类型。
如果列表项类型始终相同,那么我们可以为其定义一个类型并使用 list: ListItem[];
。如果类型因 URL 而异,那么我们可以使用另一个泛型来描述它。
type LIST_RESPONSE<ItemType> = {
page: number;
totalRows: number;
pageSize: number;
totalPages: number;
list: ItemType[];
};
然后我们可以将您的 useFetcher
挂钩用于任何类型的列表。点赞产品列表:
interface Product {
price: number;
}
const { data, loading, error } = useFetcher<LIST_RESPONSE<Product>>('productList');
if ( data ) {
// has type Product
const product = data.list[0];
// has type number
const price = product.price;
}
如果这有点令人困惑,那么您可以逐步考虑。
A ProductListResponse
是 LIST_RESPONSE
的特定类型,其中列表项的类型为 Product
:
type ProductListResponse = LIST_RESPONSE<Product>
我们的 'productList'
端点 returns data
类型为 ProductListResponse
:
const { data, loading, error } = useFetcher<ProductListResponse>('productList');