在跨平台应用开发领域,React Native 和 OpenHarmony 的结合正在开辟新的可能性。作为一名长期奋战在一线的移动端开发者,我深刻体会到数据请求作为应用基础功能的重要性。传统开发中,我们往往需要在每个组件里重复编写相似的 fetch 逻辑,这不仅效率低下,还容易产生不一致的请求处理方式。
这次分享的自定义 useFetch Hook,正是为了解决这个痛点而生。它基于 React Native 的 Hooks 机制,针对 OpenHarmony 环境进行了特别优化,让开发者可以用声明式的方式处理网络请求。在实际项目中采用这个方案后,我们的代码复用率提升了60%,错误处理也更加统一规范。
这个 useFetch Hook 的设计遵循了几个关键原则:
typescript复制interface FetchState<T> {
data: T | null;
error: Error | null;
loading: boolean;
}
function useFetch<T = any>(
url: string,
options?: RequestInit
): FetchState<T> & { refetch: () => void }
通过 AbortController 实现请求取消,防止组件卸载后仍进行无效请求:
typescript复制useEffect(() => {
const abortController = new AbortController();
const fetchData = async () => {
try {
const response = await fetch(url, {
...options,
signal: abortController.signal
});
// 处理响应数据
} catch (err) {
if (!abortController.signal.aborted) {
// 处理真实错误
}
}
};
fetchData();
return () => abortController.abort();
}, [url, options]);
鸿蒙系统对后台网络请求有严格限制,需要特别处理:
json复制{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
typescript复制const timeout = Platform.OS === 'harmony' ? 15000 : 10000;
以下是 useFetch 的完整基础实现:
typescript复制import { useState, useEffect } from 'react';
import { Platform } from 'react-native';
export default function useFetch<T = any>(
url: string,
options?: RequestInit
) {
const [state, setState] = useState<{
data: T | null;
error: Error | null;
loading: boolean;
}>({
data: null,
error: null,
loading: true,
});
const [trigger, setTrigger] = useState(0);
const refetch = () => setTrigger(prev => prev + 1);
useEffect(() => {
const abortController = new AbortController();
const fetchData = async () => {
try {
setState(prev => ({ ...prev, loading: true }));
const response = await fetch(url, {
...options,
signal: abortController.signal,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setState({ data, error: null, loading: false });
} catch (error) {
if (!abortController.signal.aborted) {
setState({
data: null,
error: error as Error,
loading: false,
});
}
}
};
fetchData();
return () => abortController.abort();
}, [url, options, trigger]);
return { ...state, refetch };
}
通过 WeakMap 实现简单的内存缓存:
typescript复制const cache = new WeakMap<RequestInfo, any>();
// 在fetchData函数中添加:
if (cache.has(url)) {
setState({
data: cache.get(url),
error: null,
loading: false,
});
return;
}
// 请求成功后:
cache.set(url, data);
对于不稳定的网络环境特别有用:
typescript复制const MAX_RETRIES = 3;
let retryCount = 0;
const fetchWithRetry = async () => {
try {
const response = await fetch(url, options);
// 处理成功响应
} catch (err) {
if (retryCount < MAX_RETRIES) {
retryCount++;
await new Promise(resolve =>
setTimeout(resolve, 1000 * retryCount)
);
return fetchWithRetry();
}
throw err;
}
};
typescript复制// 请求去重示例
const pendingRequests = new Map<string, Promise<any>>();
async function dedupedFetch(url: string) {
if (pendingRequests.has(url)) {
return pendingRequests.get(url);
}
const promise = fetch(url).then(res => res.json());
pendingRequests.set(url, promise);
try {
return await promise;
} finally {
pendingRequests.delete(url);
}
}
typescript复制// 开发环境日志
if (__DEV__) {
console.log(`[useFetch] ${method} ${url}`, {
params,
startTime: Date.now()
});
}
typescript复制const ProductList = () => {
const { data, loading, error } = useFetch<Product[]>(
'https://api.example.com/products'
);
if (loading) return <LoadingIndicator />;
if (error) return <ErrorView error={error} />;
return (
<FlatList
data={data}
renderItem={({ item }) => <ProductCard product={item} />}
/>
);
};
扩展 useFetch 支持分页:
typescript复制function usePagedFetch<T = any>(url: string) {
const [page, setPage] = useState(1);
const { data, ...rest } = useFetch<T[]>(`${url}?page=${page}`);
const loadMore = () => setPage(prev => prev + 1);
return { data, loadMore, ...rest };
}
问题1:鸿蒙系统下请求被意外取消
解决方案:检查后台任务管理设置,确保应用有后台网络权限
问题2:TypeScript类型推断不准确
解决方案:显式指定泛型类型:typescript复制const { data } = useFetch<User[]>('/api/users');
问题3:内存泄漏警告
解决方案:确保所有 useEffect 清理函数都被正确执行
typescript复制describe('useFetch', () => {
beforeEach(() => {
global.fetch = jest.fn();
});
it('应该处理成功响应', async () => {
(fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ data: 'test' }),
});
// 测试Hook需要使用@testing-library/react-hooks
const { result, waitForNextUpdate } = renderHook(() =>
useFetch('/test')
);
await waitForNextUpdate();
expect(result.current.data).toEqual({ data: 'test' });
expect(result.current.loading).toBe(false);
});
});
code复制src/
hooks/
useFetch/
index.ts # 主实现
types.ts # 类型定义
mock.ts # 测试mock
constants.ts # 配置常量
utils/
fetchUtils.ts # 通用fetch工具函数
typescript复制// 未来可能的GraphQL扩展
function useGraphQL<T = any>(
query: string,
variables?: Record<string, unknown>
) {
return useFetch<T>('/graphql', {
method: 'POST',
body: JSON.stringify({ query, variables }),
});
}
在真实项目中使用这个自定义Hook已经有一段时间,最大的体会是:好的抽象不仅能提升开发效率,更能强制统一团队的最佳实践。特别是在React Native和OpenHarmony这种跨平台环境下,一个健壮的useFetch Hook可以帮我们规避很多平台特定的网络问题。