在React组件中检测首次渲染的正确方法是什么?

18

我有一个场景,需要检测组件的首次渲染。这里我建立了一个小例子。请问正确的方法是什么?

为什么大多数人建议使用ref而不是普通状态?

https://codesandbox.io/s/condescending-burnell-0ex3x?file=/src/App.js

import React, { useState, useRef, useEffect } from "react";
import "./styles.css";

export default function App() {
  const firstRender = useDetectFirstRender();
  const [random, setRandom] = useState("123");
  useEffect(() => {
    if (firstRender) {
      console.log("first");
    } else {
      console.log("second");
    }
  }, [random]);
  return (
    <div className="App">
      <h1>Random Number is {random}</h1>
      <button onClick={() => setRandom(Math.random())}>Change Name</button>
    </div>
  );
}

//Approach 1
// export function useDetectFirstRender() {
//   const firstRender = useRef(true);

//   useEffect(() => {
//     firstRender.current = false;
//   }, []);

//   return firstRender.current;
// }

//Approach 2
export function useDetectFirstRender() {
  const [firstRender, setFirstRender] = useState(true);

  useEffect(() => {
    setFirstRender(false);
  }, []);

  return firstRender;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>


1
这是一个糟糕的代码气味。虽然组件不应该是无状态的(与redux reducer不同),但是组件不应该关心状态刷新的顺序,例如:在现有应用程序中,组件可能会被销毁并重新创建,或者应用程序可能会重新父级化组件,然后完全清除其Redux状态而不重新创建组件。 - Dai
2
refs被使用是因为它们在更新时不会触发额外的渲染,而不像state。此外@Dai,有很多情况下第一次渲染是有用的。即使你是对的,这并不能完全消除所有合法的用途。 - azium
1
let firstRender = true 声明在 App 函数外面,就在它的定义之前。然后在 useEffect 中使用 if (firstRender) { firstRender = false; //Do something}... - farvilain
1
@farvilain 这只能在模块加载一次,而不是每次组件加载一次。如果用户通过导航离开并返回重新安装组件,则无法正常工作。 - azium
1
@azium 是的,但似乎它是一个“App”,永远不会被卸载/重新挂载... 当然,这只是一个丑陋而快速的解决方案。 - farvilain
显示剩余4条评论
5个回答

21
你可以为此创建一个可重用的自定义钩子,基于useRef
function useFirstRender() {
  const ref = useRef(true);
  const firstRender = ref.current;
  ref.current = false;
  return firstRender;
}

出现错误:React Hook "useRef" 被调用在函数 "isFirstRender" 中,该函数既不是 React 函数组件也不是自定义的 React Hook 函数。 - Ryker
4
@Ryker,你的函数名称必须以“use”开头。否则,React将不知道它应该是一个钩子。 - Piotr Siupa

12

你可以使用useMemo或useCallback hook来检测并保存它。但是这里最好使用useMemo,因为它可以防止重复渲染。

const firstRender = useMemo(
    () =>console.log('first Render'),
    []
  );

这里只会渲染一次,将数值保存在第一个渲染中,所以您可以在需要的任何地方使用它。


11
const firstRender = useRef(true);

useEffect(() => {
  if (firstRender.current) {
    firstRender.current = false;
    return;
  }
  doSomething();
});

6

当使用StrictMode时,同样需要进行一些useEffect()重置/清理return () => { isFirstRender.current = true };)。否则,isFirstRender.current将不会真正反映您想要的内容,导致意外问题,尽管使用了[]依赖项,该依赖项应该仅在第一次渲染(组件挂载时)运行。

来自如何处理开发中Effect触发两次?

React有意在开发中重新挂载组件以查找像最后一个示例中的错误。 正确的问题不是“如何运行一次Effect”,而是“如何修复我的Effect,使其在重新挂载后工作”。

const isFirstRender = useRef(true);

useEffect(() => {

  if (isFirstRender.current) {
    // Do something on first render...
  }

  isFirstRender.current = false;

  // ---> StrictMode: The following is REQUIRED to reset/cleanup:
  return () => { isFirstRender.current = true };

}, []); // ---> The `[]` is required, it won't work with `[myDependency]` etc.

1

useEffect钩子函数有第二个参数。这个第二个参数是一个变量数组,组件会检查这些变量是否发生了改变,以确保重新渲染。然而,如果这个数组为空,那么这个钩子函数只会在初始渲染时被调用一次。这与之前发布的useMemo()技巧类似。

useEffect(() => doSomethingOnce(), [])
                                   ^^

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接