那些天我必须解决我的 React 应用程序的问题,我所有的数据都以 JSON 形式显示,这些数据由使用节点 FS 的 express 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;
[...]
我在 DataContext 中将“data”变量作为“缓存”,所以如果加载了它,我只需退货即可。
我的问题是我无法完成这项工作,它会陷入调用循环并且永远不会悬念(我认为)。 编辑
:我设法捕获错误日志:
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
(插入承诺)之后,我必须