React Context API初始化:异步数据获取最佳实践
2025-01-23 14:39:17
从API初始化上下文:解决方案探讨
在React应用开发中,我们经常使用Context来跨组件共享数据。一个常见需求是从外部API获取数据,然后用这些数据初始化Context。直接在模块顶级作用域中使用 async/await
和 fetch
,如提供的示例代码,会产生错误。 JavaScript模块加载是同步的,它无法等待异步操作完成。我们需要找到正确的方法,来解决这一难题。
方案一:使用useEffect
和状态管理
这种方式较为直接,在 UsuariosProvider
组件内部,使用 useState
管理数据, useEffect
副作用钩子负责从API获取数据,并在数据获取后更新状态。
原理: useEffect
可以在组件渲染之后执行副作用操作。通过将 []
作为 useEffect
的第二个参数传入,它只会在组件挂载时执行一次。我们在此获取数据,并将获取的数据设置到状态中。Context Provider 始终从状态变量 usuarios
读取数据,实现了API数据的初始化。
代码示例:
import React, { createContext, useState, useEffect } from "react";
import { Usuario } from "../typings/types";
interface UsuariosContextType {
usuarios: Usuario[]
setUsuario?: React.Dispatch<React.SetStateAction<Usuario[]>>;
}
export const UsuariosContext = createContext<UsuariosContextType>({ usuarios: [] })
export const UsuariosProvider = ({ children }: { children: React.ReactNode }) => {
const [usuarios, setUsuario] = useState<Usuario[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(`http://localhost:8080/usuarios`, { cache: 'no-store' });
if(!response.ok){
throw new Error(`HTTP Error: ${response.status}`);
}
const data = await response.json();
setUsuario(data);
} catch (error){
console.error("Fetch Error:",error)
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading){
return <div> Loading ... </div>
}
return (
<UsuariosContext.Provider value={{ usuarios, setUsuario }}>
{children}
</UsuariosContext.Provider >
)
}
步骤:
- 定义
loading
状态变量,控制加载时的渲染行为。 - 使用
useEffect
获取 API 数据。 - 在成功获取数据后,更新
usuarios
状态。 - 加入错误处理机制,增加应用的鲁棒性。
- 在组件返回之前增加对
loading
状态的检查,如果loading
为真则显示加载提示。
注意事项:
添加合适的加载状态(如上代码)非常重要。 否则,Context 在初始化时没有数据,使用该 Context 的组件在第一次渲染时会访问到空数据。
方案二: 使用异步初始化 Context Provider
将 Context Provider 设置为一个异步函数,在该异步函数内完成 API 的请求操作。当 API 数据加载完成后再返回组件。 这种方式利用了 React 组件异步渲染的能力。
原理: 利用异步函数完成初始化数据获取,之后再进行组件的渲染,通过判断是否有初始化完成的变量来进行有条件渲染。
代码示例:
import React, { createContext, useState, useEffect, FC} from "react";
import { Usuario } from "../typings/types";
interface UsuariosContextType {
usuarios: Usuario[]
setUsuario?: React.Dispatch<React.SetStateAction<Usuario[]>>;
}
export const UsuariosContext = createContext<UsuariosContextType>({ usuarios: [] });
interface UsuariosProviderProps {
children: React.ReactNode
}
export const UsuariosProvider: FC<UsuariosProviderProps> = ({ children })=>{
const [usuarios, setUsuarios] = useState<Usuario[]>([]);
const [isInit,setIsInit] = useState<boolean>(false);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch("http://localhost:8080/usuarios", { cache: 'no-store' });
if(!response.ok){
throw new Error(`Http error: ${response.status}`);
}
const data = await response.json();
setUsuarios(data);
setIsInit(true);
} catch (error){
console.error("fetch Error: ",error);
setIsInit(true)
}
};
fetchData();
}, [])
if (!isInit) {
return <div>Initializing ...</div>;
}
return (
<UsuariosContext.Provider value={{ usuarios, setUsuario: setUsuarios}}>
{children}
</UsuariosContext.Provider>
);
}
步骤:
- 添加
isInit
状态值来判断组件初始化状态。 - 将
fetch
请求放在一个useEffect
函数中。 - 使用
isInit
作为是否显示加载动画的依据,没有初始化成功不显示页面内容。 - 捕获和记录可能出现的错误信息。
注意事项:
需要仔细控制加载状态,避免不必要的空白渲染或报错,对数据进行严格的校验,增强应用的稳定性。
方案三: 引入缓存机制
在某些场景下,可以考虑引入缓存机制。第一次请求数据后,可以将数据存储在浏览器的 localStorage
中,后续初始化时,先读取缓存数据。 当缓存数据不存在或者过期的时候,才去重新请求数据。
原理: 减少对API的请求,提升应用加载速度,同时还能在API服务暂时不可用的时候,应用可以读取缓存。
代码示例:
import React, { createContext, useState, useEffect } from "react";
import { Usuario } from "../typings/types";
interface UsuariosContextType {
usuarios: Usuario[]
setUsuario?: React.Dispatch<React.SetStateAction<Usuario[]>>;
}
export const UsuariosContext = createContext<UsuariosContextType>({ usuarios: [] })
const CACHE_KEY = "usuarios_cache";
export const UsuariosProvider = ({ children }: { children: React.ReactNode }) => {
const [usuarios, setUsuarios] = useState<Usuario[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
const cachedData = localStorage.getItem(CACHE_KEY);
if(cachedData){
try{
setUsuarios(JSON.parse(cachedData));
setLoading(false)
return;
} catch(e){
//handle data parser error
}
}
try {
const response = await fetch("http://localhost:8080/usuarios", { cache: 'no-store' });
if (!response.ok){
throw new Error(`Http error: ${response.status}`);
}
const data = await response.json();
setUsuarios(data);
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
setLoading(false);
} catch (error) {
console.error("Fetch error:", error);
}
finally{
setLoading(false);
}
};
fetchData()
},[])
if (loading){
return <div> Loading ... </div>
}
return (
<UsuariosContext.Provider value={{ usuarios, setUsuario: setUsuarios}}>
{children}
</UsuariosContext.Provider>
);
}
步骤:
- 定义
CACHE_KEY
用于指定存储的key值。 - 获取本地
localStorage
存储的缓存数据。 - 如果
localStorage
存在数据,使用存储的缓存数据初始化状态值并设置loading
为false - 否则, 发起fetch请求, 获取数据并且把数据更新到状态值,同时把最新的数据缓存到
localStorage
。
注意事项: 缓存策略需要考虑数据过期和同步问题。 对于更新频繁的数据,不能无限期缓存。 应该考虑数据更新机制,防止应用中出现“陈旧”数据。