我有两个自定义的 React 钩子,一个只是另一个的包装器。
第一个是基本的 fetch hook,它返回状态和结果或错误。
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
钩子时,在提供选项对象作为参数时,它会导致无限重新渲染和最大更新深度超出错误。
我发现问题出在我的 useEffect
钩子中 useFetch
的选项依赖项中,当我从依赖项数组中删除它时,获取请求运行良好。
唯一的问题是我的 linter 告诉我在依赖项数组中包含选项,因为 useEffect 依赖于它。
我现在想知道如何才能正确地做到这一点。
答案 0 :(得分:1)
问题是将对象文字作为 options
传递会导致每次钩子重新呈现时 useEffect()
的依赖项都会发生变化,这意味着,正如您所经历的,您将重复获取因为每次上次提取刚刚完成时,效果都会使用新的 options
引用再次运行。
为了打破这个循环,你需要
options
传入的 useAPICall()
options
存储在 useRef()
内的 useFetch()
中此外,由于您希望获取对 options
的变化敏感,如果您决定采用第二种方法,则每次 {{1} } 钩子渲染,只有在值改变时才使用新的引用。
以下是在 useFetch()
中实现第一种方法的方法,而无需处理 useAPICall()
:
useFetch()
下面是您如何实现第二种方法的大部分内容,单独留下 export const useAPICall = <T>(url: string, body?: any, method = 'GET') => {
const options = {
method,
body,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
'x-access-token': getCookie('token'),
},
};
const ref = useRef(options);
if (
ref.current.method !== options.method
|| ref.current.body !== options.body
|| ref.current.headers['x-access-token'] !== options.headers['x-access-token']
) {
ref.current = options;
}
return useFetch<T>(
apiEndpoint + url,
ref.current,
false
);
};
,尽管我已经留下评论,您需要在其中详细说明它的细节:
useAPICall()