如何以及何时强制React组件重新渲染

21,061 阅读6分钟

React通常会自动重新渲染组件,但为了让我们真正了解如何以及何时强制React重新渲染一个组件,我们需要了解React的内部工作原理。

自从网络诞生以来,我们一直使用HTML和CSS来定义网站的视觉表现。这一点没有改变,但是,随着JavaScript的引入,新的概念使我们能够读取和更新一个页面的渲染HTML。我们称这种API为DOM

jQuery这样的库变得非常流行,因为它们在DOM API的基础上提供了一个抽象层,提供了增强的功能和兼容性,使开发者很容易建立网络应用。

但jQuery也有其局限性。它没有解决开发者在处理动态加载的内容时遇到的关键问题,比如保持JavaScript状态(变量)和DOM(HTML)的同步性。改变JavaScript中的变量并不直接影响DOM。

使用React这样的框架可以解决这个问题。React依靠JavaScript来维护应用程序的状态。这个包含页面上每个对象的JavaScript引用的主状态对象被称为虚拟DOM。虚拟DOM上的任何变化都会自动反映在DOM上,这就是React最好的魔术。

但我们如何更新虚拟DOM呢?我们通过构建组件和处理状态和道具来实现。

你可能有的下一个问题是,当一个组件没有按照预期更新时,在边缘情况下会发生什么?这有可能吗?也许吧,但这很复杂,所以我们来讨论一下。

为什么React组件没有重新渲染?

一般来说,强迫React组件重新渲染并不是最佳做法,即使React未能自动更新组件。所以,在考虑强制重新渲染之前,我们应该分析我们的代码,因为React的魔力取决于我们是好的主人。

React中不正确的更新状态

让我们建立一个简单的组件来证明组件不渲染的一个常见原因。我们将建立一个简单的应用程序,它将显示一个用户名,Juan ,在按下一个按钮后,这个名字将变为Peter

下面是一个带有完整代码的应用程序的演示。你可能注意到,在点击按钮后,什么也没有发生,尽管我们在按钮上改变了我们的状态。

function changeUserName() {
   user.name = "Peter";
   setUser(user);
 }

组件并没有改变,所以没有重新渲染的触发。这就是原因。

React通过检查其浅层平等(或引用平等)来评估状态变化,它检查state 的预览和新值是否都引用了同一个对象。在我们的例子中,我们更新了user 对象的一个属性,但我们在技术上使setUser 相同的对象引用,因此,React没有察觉到其状态的任何变化。

正如React文档中所描述的,状态应该被视为不可改变的。

那么,我们该如何解决这个问题呢?我们可以用正确的值创建一个新的对象,如下所示。

function changeUserName() {
   setUser({
     ...user,
     name: "Peter"
   });
 }

注意,我们使用JavaScript中的spread操作符来保留原始对象的属性,同时在一个新对象下更新其name 。最后的结果可以在这里观察到。

不正确地更新道具而不改变状态

虽然看起来不可能,但在没有状态变化的情况下不正确地更新道具是可能发生的,而且通常会导致bug的出现。让我们看一个例子。

在这个演示中,我建立了一个时钟,它有一个大问题:在我第一次加载屏幕后,时间没有变化。这不是一个非常有用的时钟,对吗?

让我们看一下负责计算当前时间的代码。

let myTime;

 function setMyTime() {
   myTime = new Date();
   setTimeout(() => {
     setMyTime();
   }, 1000);
 }

 setMyTime();

这段代码看起来很难看,一般来说,对于React组件来说,这不是一个很好的编码方式,但它可以工作。每一秒钟,运行时将调用函数setMyTime ,并将更新变量myTime ,然后将其传递给我们的Clock 组件进行渲染。

<Clock myTime={myTime} />

这个演示并不奏效,因为道具是状态的反映,所以道具的独立变化不会触发重新渲染。要解决这个问题,我们需要进行全面重写

请注意,我们引入了状态来管理myTimeuseEffect 来启动和清除计时器,以避免在组件重新渲染时出现错误。而且,它是有效的!

const [myTime, setMyTime] = useState(new Date());

 useEffect(() => {
   var timerID = setInterval(() => tick(), 1000);

   return () => clearInterval(timerID);
 });

 function tick() {
   setMyTime(new Date());
 }

强制React组件重新渲染

强制组件重新渲染通常是不受欢迎的,React中自动重新渲染的失败通常是由于我们代码库中的一个潜在错误。但是,如果你有一个合法的需要,强迫一个React组件重新渲染,有几种方法可以做到。

强制更新React类组件

如果你在你的代码中使用类组件,你很幸运。React提供了一个官方的API来强制重新渲染,而且实现起来很简单。

someMethod() {
   // Force a render without state change...
   this.forceUpdate();
 }

在任何用户或系统事件中,你可以调用方法this.forceUpdate() ,这将导致render() 在组件上被调用,跳过shouldComponentUpdate() ,从而迫使 React 重新评估虚拟 DOM 和 DOM 状态。

这个方法有一些注意事项。

  • React会触发子组件的正常生命周期方法,包括shouldComponentUpdate() ,所以我们只能强制当前组件被重新渲染
  • VirtualDOM仍然会用DOM来验证其状态,所以React只会在标记发生变化时更新DOM。

强制更新一个功能组件

没有官方的API来重新渲染一个函数组件,也没有一个React Hook。然而,有一些聪明的技巧可以向React发出信号,表明一个组件应该被更新。

  1. 用自己的新实例替换状态对象

假设我们想对上面的改变用户的例子进行强制刷新。我们可以做这样的事情。

someMethod() {
   // Force a render with a simulated state change
   setUser({ ...user });
 }

因为user 是一个对象,我们可以把它复制到一个新的对象,并把它设置为新的状态。同样的方法可以适用于任何其他对象或数组。

2.让一个空的状态变量触发更新

这个方法很有意思,因为它在状态中创建了一个新的对象。我们只关心它的更新函数,如下所示。

const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);

在这里,我们用useCallback 来记忆我们的forceUpdate 函数,从而使它在整个组件生命周期中保持不变,并使它安全地作为props 传递给子组件。

下面是一个如何使用它的例子。

import React from "react";

export default function App() {
 const [, updateState] = React.useState();
 const forceUpdate = React.useCallback(() => updateState({}), []);

 console.log("rendering...");

 return (
   <div className="App">
     <h1>Time to force some updates</h1>
     <button onClick={forceUpdate}>Force re-render</button>
   </div>
 );
}

现在,每次我们点击Force Re-render按钮,该组件就会重新渲染。你可以在这里访问实时演示

结论

一般来说,我们应该防止强迫React重新渲染组件。如果React不能自动重新渲染组件,很可能是你项目中的一个潜在问题阻碍了组件的正确更新。

然而,我们涵盖了一些常见的方法,如果需要,可以强制React重新渲染组件。编码愉快!

The postHow and when to force a React component to re-renderappeared first onLogRocket Blog.