【发布时间】:2021-04-23 04:26:36
【问题描述】:
我有两个自定义反应钩子,一个只是另一个的包装器。
第一个是基本的 fetch 钩子,它返回状态和结果或错误。
interface State<T> {
status: 'idle' | 'pending' | 'error' | 'success';
data?: T;
error?: string;
}
interface Cache<T> {
[url: string]: T;
}
type Action<T> =
| { type: 'request' }
| { type: 'success'; payload: T }
| { type: 'failure'; payload: string };
export function useFetch<T = unknown>(
url: string,
options?: RequestInit,
cached = false
): State<T> {
const cache = useRef<Cache<T>>({});
const cancelRequest = useRef(false);
const initialState: State<T> = {
status: 'idle',
error: undefined,
data: undefined,
};
const fetchReducer = (state: State<T>, action: Action<T>): State<T> => {
switch (action.type) {
case 'request':
return { ...initialState, status: 'pending' };
case 'success':
return { ...initialState, status: 'success', data: action.payload };
case 'failure':
return { ...initialState, status: 'error', error: action.payload };
default:
return state;
}
};
const [state, dispatch] = useReducer(fetchReducer, initialState);
useEffect(() => {
const fetchData = async () => {
dispatch({ type: 'request' });
if (cache.current[url] && cached) {
dispatch({ type: 'success', payload: cache.current[url] });
} else {
try {
const res = await fetch(url, options);
const json = await res.json();
cache.current[url] = json;
if (cancelRequest.current) return;
if (res.status !== 200) {
dispatch({ type: 'failure', payload: json.error });
} else {
dispatch({ type: 'success', payload: json });
}
} catch (error) {
if (cancelRequest.current) return;
dispatch({ type: 'failure', payload: error.message });
}
}
};
fetchData();
return () => {
cancelRequest.current = true;
};
}, [url, options, cached]);
return state;
}
export default useFetch;
第二个钩子是一个包装器,它为useFetch 钩子提供一些获取选项,例如包含cookie 的标头。这样我就不必一遍又一遍地编写相同的代码来针对我的 API 执行一些请求。我称之为 useAPICall
export const useAPICall = <T>(url: string, body?: any, method = 'GET') => {
return useFetch<T>(
apiEndpoint + url,
{
method,
body,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
'x-access-token': getCookie('token'),
},
},
false
);
};
getCookie 从文档中返回名为 token 的 cookie。
这就是我使用useAPICall 钩子的方式:
const { status, data, error } = useAPICall<Request>('/example/route');
我的问题是,每当我想使用 useAPICall 或 useFetch 钩子时,同时提供选项对象作为参数,它会导致无限重新渲染和超出最大更新深度错误。
我发现问题出在我的useFetch 钩子中useEffect 的选项依赖中,当我从依赖数组中删除它时,获取请求运行良好。
唯一的问题是我的 linter 告诉我在依赖数组中包含选项,因为 useEffect 依赖它。
我现在想知道如何正确地做到这一点。
【问题讨论】:
-
是否支持更改选项触发新请求?
-
@PatrickRoberts 是的,因为我想使用 useAPICall 挂钩在标头中提供令牌,而其他一些路由需要动态正文数据和发布请求。
-
这就是“你想支持为请求提供选项吗?”的答案。问题是您是否关心
useFetch()钩子的特定用法是否对在调用它的组件的生命周期内更改的选项敏感。 -
@PatrickRoberts 哦,很抱歉我没有正确阅读,我想可能存在一些用例,我更改组件的状态以触发具有不同主体的新获取请求。
-
好吧,支持这个是可能的,只是不违反钩子规则就更难做对了。我很快就会得到答复。
标签: reactjs typescript react-hooks fetch use-effect