我正在开发与 MobX 商店配合使用的组件,但遇到了问题。当我转到具有不同项目 ID 的页面时,加载数据后,我会在呈现新数据之前看到以前的数据。因此,我为第一次渲染编写了此检查,这似乎对于准备要渲染的组件很有用,但在我看来,这可能是错误的代码。是这样吗?
"use client"
const Component = (props) => {
const isFirstRender = useRef(true)
if (isFirstRender.current) {
/* some action */
isFirstRender.current = false
}
return (
/* some jsx */
)
})
这可能与此处记录的场景相同当道具更改时重置所有状态
下面讨论与这个问题相关的一些要点。
现在来回答这个问题:
现在这篇文章正在寻求一种解决方案,以避免陈旧或过时的渲染,这种渲染总是在 useEffect 之前发生,这与我们在上面第 7 点中讨论的点完全相同。
可能解决方案的一些背景信息
请注意,组件将在渲染之间保留状态。这意味着通常当函数对象终止调用时,函数对象内声明的所有变量都将丢失。
然而,React 函数对象能够在渲染之间保留状态值。 React 默认的状态保留规则是,只要同一个组件在 UI 渲染树中的相同位置渲染,它就会保留状态。有关此内容的更多信息,请参阅此处保存和重置状态。 虽然默认行为适合大多数用例,因此它已成为 React 的默认行为,但我们现在所处的上下文不适合这种行为
。我们不希望 React 保留之前的 fetch。 这意味着我们需要一种方法来告诉 React 请随着 props 的变化重置状态
。请注意,即使我们成功重置状态,渲染过程和 useEffect 仍将以相同的正常顺序运行。将进行具有最新状态的初始渲染,并在渲染之后运行 useEffect。然而,我们在这里可以实现的改进是,这个初始渲染将使用我们传递到 useState 的初始值。由于这个初始值是固定值,因此我们始终可以使用它来构建两个状态值的条件渲染 - 加载状态或获取的数据。 以下两个示例代码,demo相同。
下面的第一个代码,演示了我们一直在讨论的问题。
app.js
import { useEffect, useState } from 'react';
export default function App() {
return <Page />;
}
function Page() {
const [contentId, setContentId] = useState(Math.random());
return (
<>
<Content contentId={contentId} />
<br />
<button onClick={() => setContentId(Math.random())}>
Change contentId
</button>
</>
);
}
function Content({ contentId }) {
const [mockData, setMockData] = useState(null);
useEffect(() => {
new Promise((resolve) => {
setTimeout(() => {
resolve(`some mock data 1,2,3,4.... for ${contentId}`);
}, 2000);
}).then((data) => setMockData(data));
}, [contentId]);
return <>mock data : {mockData ? mockData : 'Loading data..'}</>;
}
试运行
测试计划:点击按钮更改contentId
点击之前的用户界面
之前的数据保留时间不到2秒,这不是想要的UI。 UI 应更改以通知用户数据加载正在进行。 2 秒后,模拟数据应进入 UI。
下面的第二个代码解决了该问题。
它通过使用属性 key 解决了该问题。该属性在国家保留计划中具有重要意义。简而言之,现在发生的情况是,如果两次渲染之间的键发生变化,React 将重置状态。 App.js
import { useEffect, useState } from 'react';
export default function App() {
return <Page />;
}
function Page() {
const [contentId, setContentId] = useState(Math.random());
return (
<>
<Content contentId={contentId} key={contentId} />
<br />
<button onClick={() => setContentId(Math.random())}>
Change contentId
</button>
</>
);
}
function Content({ contentId }) {
const [mockData, setMockData] = useState(null);
useEffect(() => {
new Promise((resolve) => {
setTimeout(() => {
resolve(`some mock data 1,2,3,4.... for ${contentId}`);
}, 2000);
}).then((data) => setMockData(data));
}, [contentId]);
return <>mock data : {mockData ? mockData : 'Loading data..'}</>;
}
试运行
测试计划:点击按钮更改contentId
点击前的UI
点击后不到2秒的UI
之前的数据没有保留,而是IU显示加载状态,并在实际数据到来后立即更新。这可能是此用例中所需的 UI。
引用
当子级有条件渲染时,React 如何确定应用哪种状态?