想在你的React组件渲染之前运行一些代码吗?有几种方法可以做到这一点,我们将在这里谈论它们。
但是,我必须警告你。在渲染前运行代码通常表明你在违背React的工作原理。
TL;DR - 没有Before Render,只有After
认为 "我想在我的组件渲染前获取数据 "是非常合理的。这是有逻辑的。但不是React的工作方式。
事情是这样的。
React不会等待渲染,永远不会
React很乐意在后台启动一个异步数据获取,但随后它将立即进行渲染--无论数据是否已经加载。(你几乎可以肯定,它还没有加载)。
没有办法让它等待。
不过,一切并没有失去。有一个简单的解决办法。
渲染异步数据的组件需要准备渲染一个空状态,至少一次。
想想你的应用程序在数据准备好之前应该是什么样子。也许它是空的,也许它是一个加载旋转器,或者是一些花哨的骨架状态。
为了适应React的工作方式,在第一次渲染之后,在useEffect块中启动你的数据获取。
只要确保初始化状态的类型与它最终会成为的类型相同就可以了。
渲染前初始化状态
初始化状态实际上是在第一次渲染之前运行的,而不初始化状态是问题的一个常见来源。
当组件试图在数据准备好之前进行渲染时,这将导致像Cannot read property 'map' of undefined' 这样的错误。
如果你有一个类似于useState() 的调用,但在括号之间什么都没有,这就是未初始化(会是undefined )。
经验法则是以类似的方式初始化:如果状态将持有一个字符串,就用字符串初始化。如果它是一个数字,就用数字初始化。以此类推。
初始化数组
如果你期望从服务器得到一个数组,就用一个空数组来初始化:
const [items, setItems] = useState([]);
初始化对象
如果你期待一个对象,就用一个对象初始化,或者用空对象。
const [user, setUser] = useState(null);
懒散地初始化状态
如果你的初始化代码需要做一些繁重的工作,比如映射/过滤/减少一个数组,你可以把初始化包在一个函数中,它只会运行一次:
const [products, setProducts] = useState(() => {
return hugeListOfProducts.filter(isOnSale);
})
但这并不是一个获取数据或做任何异步工作的好地方。把异步操作放在useEffect中。
在数据准备好之前会发生什么?
仔细检查你的代码,确保在数据没有准备好的情况下不会爆炸(如果值为空)。如果数据被初始化为或可能成为null 或undefined !要特别小心。
return (
<div>
{user && user.name ? user.name : "Not loaded yet"}
</div>
)
在ES2020中,有两个新的操作符可以使这段代码变得更简单:可选的链 (?.) 和nullish凝聚 (??)。
可选的链式运算符(?.)可以让你安全地访问一个可能是空的对象的属性:
return (
<div>
{user?.name || "Not loaded yet"}
</div>
)
当左边是null 或undefined 时,nullish coalescing 操作符 (??) 会返回右边。在通常使用|| 的情况下,它是很有用的,比如这个:
return (
<div>
{user?.commentCount || "Not loaded yet"}
</div>
)
这个例子有一个错误--当commentCount 为0时,它将显示 "尚未加载"。使用?? 运算符而不是|| ,它将正确工作。
return (
<div>
{user?.commentCount ?? "Not loaded yet"}
</div>
)
?? 和OR 运算符的工作原理一样,只是它不认为 , 或 是错误的。|| 0 '' false
在父节点的渲染前获取数据
如果你绝对需要在一个组件渲染之前运行一些代码,那么解决方案就是完全避免渲染该组件,直到你准备好。
这意味着有条件地在父类中渲染它,看起来就像这样。更多细节请见评论。
function Child({ items }) {
// Problem:
// This will error if `items` is null/undefined
return (
<>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</>
);
}
function Parent() {
// Uninitialized state will cause Child to error out
const [items, setItems] = useState();
// Data does't start loading
// until *after* Parent is mounted
useEffect(() => {
fetch('/data')
.then(res => res.json())
.then(data => setItems(data));
}, []);
// Solution:
// don't render Child until `items` is ready!
return (
<div>
{items && <Child items={items}/>}
</div>
);
}
就是这样!
我希望这能帮助你澄清一些关于如何在安装React组件之前做事情的困惑。请记住。没有之前,只有之后。
如果想深入了解React是如何渲染和重新渲染的,请看Mark Erikson的React渲染行为指南。