如何在React渲染前运行代码

2,577 阅读4分钟

你的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中

在数据准备好之前会发生什么?

仔细检查你的代码,确保在数据没有准备好的情况下不会爆炸(如果值为空)。如果数据被初始化为或可能成为nullundefined !要特别小心。

return (
  <div>
    {user && user.name ? user.name : "Not loaded yet"}
  </div>
)

在ES2020中,有两个新的操作符可以使这段代码变得更简单:可选的链 (?.) 和nullish凝聚 (??)。

可选的链式运算符(?.)可以让你安全地访问一个可能是空的对象的属性:

return (
  <div>
    {user?.name || "Not loaded yet"}
  </div>
)

当左边是nullundefined 时,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渲染行为指南