[英]React Suspense within a Context
那些日子我不得不为我的反应应用程序解决一个问题,我的所有数据都以 JSON 格式显示,这些数据由使用节点 FS 的快速 API提供服务,以便读取这些 JSON 并根据您的路径返回它们重新传入。
当我尝试渲染我的组件时,真正的问题出现了,如果没有 JSON 信息,这些组件将毫无用处,所以我必须一直等到我得到这些信息。
我的第一个想法是使用XMLHttpRequest
同步调用 API ,但我在文档中看到它已被弃用。 因此,我阅读了Suspense 和 Transitions的新功能,但我不理解它以及将它们放入我的 DataContext 中。 我将与您分享我认为相关的所有代码,以便让您了解我到达的位置:
// jsonLoader.js
// Here I try to get the data with XMLHttpRequest, I replaced to sync to async
export const loadJSON = async (path) => {
const json = await readFile(path, 'application/json');
return json ? JSON.parse(json) : undefined;
};
const readFile = async (path, mimeType) =>
new Promise((resolve) => {
const xmlHttp = new XMLHttpRequest();
xmlHttp.open('GET', path, true);
if (!!mimeType && xmlHttp.overrideMimeType) xmlHttp.overrideMimeType(mimeType);
xmlHttp.send();
if (xmlHttp.status == 200 && xmlHttp.readyState == 4) resolve(xmlHttp.responseText);
else return resolve(undefined);
});
然后我在我的 DataContext 中使用该模块:
// DataContext.js
// I'm trying to serve a "useQuery" called "getData" in order to fetch the API
let data = {};
const index = (obj, path) => path.split('.').reduce((o, i) => o[i], obj);
const setter = (obj, path, value) => {
if (typeof path == 'string') return setter(obj, path.split('.'), value);
else if (path.length == 1 && value !== undefined) return (obj[path[0]] = value);
else if (path.length == 0) return obj;
else return setter(obj[path[0]], path.slice(1), value);
};
const DataContextInstance = createContext({
getData: async (...paths) => ({}),
getTranslations: () => ({}),
});
export const DataContext = ({dataLoaded, children}) => {
if (dataLoaded) data = dataLoaded;
const {lang} = useContext(UserContext);
const [prevLang, setPrevLang] = useState();
const loadData = (path) => {
if (Object.keys(data).length > 0) {
const foundData = index(data, path);
if (foundData?.then) throw foundData;
if (foundData) return data;
}
const filePath = `/data/${path || `translations/${lang}`}.json`;
const json = loadJSON(filePath).then((newData) => (data[path] = newData));
data[path] = json;
};
const getData = (...paths) => {
if (paths.every((p) => index(data, p))) return data;
paths.forEach((p) => loadData(p));
return data;
};
useEffect(() => {
if (lang === prevLang && Object.keys(data).length > 0) return;
if (Object.keys(data).length > 0) return;
loadData();
setPrevLang(lang);
}, [lang, prevLang, setPrevLang, data]);
const contextValue = useMemo(
() => ({getData, getTranslations: () => getData()}),
[data, lang]
);
return (
<DataContextInstance.Provider value={contextValue}>
<Suspense fallback={<span>Loading...</span>}>{children}</Suspense>
</DataContextInstance.Provider>
);
};
export const useDataContext = () => {
const context = useContext(DataContextInstance);
if (!context) throw new Error('Context must be used within a Provider');
return context;
};
然后,我在我的组件中使用该“getData”来获取该组件所需的数据:
// NavBar.js
// Here I use my hook to get the DataContext context and get the "getData" func
function NavBar() {
const {getData} = useDataContext();
const {pages} = getData('menu').menu;
[...]
如您所见,我在每个组件中指定了我想要的 json,以避免加载所有组件,因此我在 DataContext 中将“数据”变量作为“缓存”,因此如果加载它,我只需返回它。
我的问题是我无法完成这项工作,它进入了一个呼叫循环并且永远没有悬念(我认为)。
编辑:我设法捕获了一个错误日志:
Warning: Cannot update a component (`DataContext`) while rendering a different component (`AppBase`). To locate the bad setState() call inside `AppBase`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
JSX 结构是:
<DataContext dataLoaded={data}>
<AppBase data={data} statusCode={statusCode} />
</DataContext>
如果通过添加以下行来解决它,在添加data[path] = json
(插入承诺)之后,我必须抛出该承诺以告诉 React 它正在加载承诺:
[...]
const filePath = `/data/${path || `translations/${lang}`}.json`;
if (!path) path = lang;
const json = loadJSON(filePath).then((newData) => (data[path] = newData));
data[path] = json;
if (data[path]?.then) throw data[path];
[...]
我按照CSS Tricks的说明进行操作。
谢谢大家的帮助!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.