ChatGPT解决这个技术问题 Extra ChatGPT

如何仅使用 React useEffect 调用加载函数一次

useEffect React 挂钩将在每次更改时运行传入的函数。这可以优化为仅在所需属性更改时才调用。

如果我想从 componentDidMount 调用初始化函数而不是在更改时再次调用它怎么办?假设我想加载一个实体,但加载函数不需要来自组件的任何数据。我们如何使用 useEffect 钩子来做到这一点?

class MyComponent extends React.PureComponent {
    componentDidMount() {
        loadDataOnlyOnce();
    }
    render() { ... }
}

使用钩子,这可能看起来像这样:

function MyComponent() {
    useEffect(() => {
        loadDataOnlyOnce(); // this will fire on every change :(
    }, [...???]);
    return (...);
}

T
Tholle

如果您只想在初始渲染后运行提供给 useEffect 的函数,您可以给它一个空数组作为第二个参数。

function MyComponent() {
  useEffect(() => {
    loadDataOnlyOnce();
  }, []);

  return <div> {/* ... */} </div>;
}

或者,如果有用于获取数据的参数(例如用户 ID),您可以在该数组中传递用户 ID,如果它发生更改,组件将重新获取数据。许多用例都会这样工作。
是的...有关跳过的更多信息记录在这里:reactjs.org/docs/…
这似乎是最简单的答案,但 ESLint 抱怨...查看此线程 stackoverflow.com/a/56767883/1550587 上的其他答案
只需将 loadDataOnlyOnce 传递到依赖项数组中。那样有用吗?
不,因为当 loadDataOnlyOnce 改变时(不在这个例子中,但 lint 无论如何都不会抱怨非局部变量),它会重新运行效果。解决方案是为钩子创建一个单独的函数,就像在此处的另一个答案中一样(有效地欺骗 ESLint),或者在第一次运行后设置一个带有布尔值的 useRef,如果设置了就不再运行。
E
Edan Chetrit

TL;博士

useEffect(yourCallback, []) - 仅在第一次渲染后触发回调。

详细解释

useEffect 默认在组件每次渲染后运行(从而产生效果)。

在组件中放置 useEffect 时,您告诉 React 您希望将回调作为效果运行。 React 将在渲染后和执行 DOM 更新后运行效果。

如果您只传递一个回调 - 回调将在每次渲染后运行。

如果传递第二个参数(数组),React 将在第一次渲染之后以及每次更改数组中的一个元素时运行回调。例如,在放置 useEffect(() => console.log('hello'), [someVar, someOtherVar]) 时 - 回调将在第一次渲染之后以及在 someVarsomeOtherVar 之一发生更改的任何渲染之后运行。

通过向第二个参数传递一个空数组,React 将在每次渲染后比较该数组,并且会发现没有任何变化,因此仅在第一次渲染后调用回调。


a
andromeda

使用MountEffect挂钩

在组件挂载后只运行一次函数是一种常见的模式,它证明了它自己的一个隐藏实现细节的钩子是合理的。

const useMountEffect = (fun) => useEffect(fun, [])

在任何功能组件中使用它。

function MyComponent() {
    useMountEffect(function) // function will run only once after it has mounted. 
    return <div>...</div>;
}

关于 useMountEffect 挂钩

useEffect 与第二个数组参数一起使用时,React 将在挂载(初始渲染)和数组中的值发生更改后运行回调。由于我们传递了一个空数组,它只会在挂载后运行。


我非常喜欢你的回答,因为 ESLint 规则“react-hooks/exhaustive-deps”总是会在空依赖列表上失败。例如,著名的 create-react-app 模板将强制执行该规则。
现在,当您的效果函数需要 props 中的某些内容但永远不需要再次运行时,您可以使用 useMount,即使该值在没有 linter 警告的情况下发生更改:useEffect(()=>console.log(props.val),[]) 将缺少依赖项警告,但 useMount(()=>console.log(props.val)) 不会导致警告但“确实有效”。我不确定并发模式是否会出现问题。
我不太明白... "react-hooks/exhaustive-deps" 仍然抱怨 const useMountEffect = (fun) => useEffect(fun, []) 中的空数组
谢谢!虽然我认为这指出了 "react-hooks/exhaustive-deps" 中的一个缺陷,特别是因为这是在挂载上运行事物的规范方式。这个“解决方案”在功能上将问题从组件转移到其他地方,而不是从根本上解决空部门的问题。
这不会“绕过” ESLint 规则 b/c 它仍然会指出 useEffect 具有依赖项:fun
w
wuarmin

我们必须停止思考组件生命周期方法(即componentDidMount)。我们必须开始思考效果。 React 效果不同于旧式的类生命周期方法。

默认情况下,效果会在每个渲染周期后运行,但可以选择退出此行为。要选择退出,您可以定义依赖关系,这意味着仅在对依赖关系之一进行更改时才会执行效果。

如果您明确定义效果没有依赖关系,则效果仅在第一个渲染周期之后运行一次。

第一种解决方案(带有 ESLint 投诉)

因此,您的示例的第一个解决方案如下:

function MyComponent() {

    const loadDataOnlyOnce = () => {
      console.log("loadDataOnlyOnce");
    };

    useEffect(() => {
        loadDataOnlyOnce(); // this will fire only on first render
    }, []);
    return (...);
}

但是 React Hooks ESLint 插件会抱怨这样的事情:

React Hook useEffect has missing dependency: loadDataOnlyOnce. Either include it or remove the dependency array

起初,这个警告似乎很烦人,但请不要忽视它。它可以帮助您更好地编码。

第二种解决方案(正确的方法,如果依赖项不依赖于组件)

如果我们将 loadDataOnlyOnce 添加到依赖数组,我们的效果将在每个渲染周期后运行,因为 loadDataOnlyOnce 的引用在每次渲染时都会发生变化,因为函数被销毁(垃圾收集)并创建了一个新函数,但这正是我们不想要的。

在渲染周期中,我们必须保持 loadDataOnlyOnce 的相同引用。

所以只需移动上面的函数定义:

const loadDataOnlyOnce = () => {
  console.log("loadDataOnlyOnce");
};

function MyComponent() {
    useEffect(() => {
        loadDataOnlyOnce(); // this will fire only on first render
    }, []);
    return (...);
}

第三种解决方案(正确的方法,如果依赖项依赖于组件)

如果效果(loadDataOnlyOnce)的依赖关系依赖于组件(需要道具或状态),则有 React 的内置 useCallback-Hook。

useCallback-Hook 的基本含义是在渲染周期内保持函数的引用相同。

function MyComponent() {
    const [state, setState] = useState("state");

    const loadDataOnlyOnce = useCallback(() => {
      console.log(`I need ${state}!!`);
    }, [state]);

    useEffect(() => {
        loadDataOnlyOnce(); // // this will fire only when loadDataOnlyOnce-reference changes
    }, [loadDataOnlyOnce]);
    return (...);
}

这个答案值得初学者 React 开发人员更多地关注。
@JusticeBringer 我想说的不仅是初学者。这不是一个简单的概念。
第四个选项。将函数内容直接放入useEffect。这也消除了“eslint”错误。感谢您顺便解释一下这个概念。
A
Anastasia

将一个空数组作为第二个参数传递给 useEffect。这有效地告诉 React,引用 docs

这告诉 React 你的效果不依赖于任何来自 props 或 state 的值,所以它永远不需要重新运行。

这是一个片段,您可以运行它来证明它有效:

函数 App() { const [user, setUser] = React.useState(null); React.useEffect(() => { fetch('https://randomuser.me/api/') .then(results => results.json()) .then(data => { setUser(data.results[0 ]); }); }, []); // 传递空数组只在挂载时运行一次。返回

{用户 ? user.name.first : '加载中...'}
; } ReactDOM.render(, document.getElementById('app'));


Y
Yasin Tazeoglu
function useOnceCall(cb, condition = true) {
  const isCalledRef = React.useRef(false);

  React.useEffect(() => {
    if (condition && !isCalledRef.current) {
      isCalledRef.current = true;
      cb();
    }
  }, [cb, condition]);
}

并使用它。

useOnceCall(() => {
  console.log('called');
})

或者

useOnceCall(()=>{
  console.log('Fetched Data');
}, isFetched);

谢谢!拯救了我的一天。非常适合调用函数一次,但仅在需要加载某些状态之后。
这是比接受的答案更好的解决方案
e
ecoologic

我喜欢定义一个 mount 函数,它以与 useMount 相同的方式欺骗 EsLint,而且我发现它更加不言自明。

const mount = () => {
  console.log('mounted')
  // ...

  const unmount = () => {
    console.log('unmounted')
    // ...
  }
  return unmount
}
useEffect(mount, [])


S
Shuhad zaman

将依赖项数组留空。希望这将帮助您更好地理解。

   useEffect(() => {
      doSomething()
    }, []) 

空依赖数组仅在挂载上运行一次

useEffect(() => {
  doSomething(value)
}, [value])  

value 作为依赖项传递。如果自上次以来依赖项发生了变化,则效果将再次运行。

useEffect(() => {
  doSomething(value)
})  

没有依赖。每次渲染后都会调用它。


g
glinda93

这是我对亚辛答案的看法。

import {useEffect, useRef} from 'react';

const useOnceEffect = (effect: () => void) => {
  const initialRef = useRef(true);

  useEffect(() => {
    if (!initialRef.current) {
      return;
    }
    initialRef.current = false;
    effect();
  }, [effect]);
};

export default useOnceEffect;

用法:

useOnceEffect(
  useCallback(() => {
    nonHookFunc(deps1, deps2);
  }, [deps1, deps2])
);

j
jb.darkcharm

这并不能完全回答您的问题,但可能具有相同的预期效果,即仅在第一次渲染后运行一次函数。非常类似于 componentDidMount 函数。这使用 useState 而不是 useEffect 来避免依赖 lint 错误。您只需将一个自执行匿名函数作为第一个参数传递给 useState。顺便说一句,我不确定为什么 React 不简单地提供一个这样做的钩子。

import React, { useState } from "react"

const Component = () => {

  useState((() => {
    console.log('componentDidMountHook...')
  }))

  return (
    <div className='component'>Component</div>
  )
}

export default Component

d
drent

如果您使用具有 [] 依赖项的 useEffect,请确保您的组件未包装到 <React.StrictMode />


为什么?当它被包裹在里面时会发生什么?
@DávidMolnár 它会调用两次
很高兴在这方面指出严格模式的麻烦,尽管值得注意的是它运行两次的原因是因为它模拟了组件的安装和卸载,它应该是有弹性的......你不能有一个组件即“只能安装一次”。
G
Gnopor

我在使用 React 18 时遇到了这个问题。我是这样处理的:

import { useEffect, useRef } from "react";

export default function Component() {
    const isRunned = useRef(false);

    useEffect(() => {
        !isRunned.current &&
            {
                /* CODE THAT SHOULD RUN ONCE */
            };

        return () => {
            isRunned.current = true;
        };
    }, []);

    return <div> content </div>;
}

Check here how they explain why useEffect is called more than once.


您能否添加更多细节,为什么这是 React 18 的解决方案?
抱歉,我的意思是我在使用 React 18 时遇到了这个问题。
G
Greggory Wiley

即使用户按下后退按钮导航到页面,window.onpageshow 也可以工作,这与传递一个空数组作为使用效果挂钩的第二个参数不同,后者在通过后退按钮返回页面时不会触发(因此并非在每个初始页面加载的形式)。

 useEffect(() => {    
     window.onpageshow = async function() {
      setSomeState(false)
      let results = await AsyncFunction() 
       console.log(results, 'Fires on on first load, 
        refresh, or coming to the page via the back button')
    };
 };

V
Vignesh

如果您只是在渲染后调用 useEffect 中的函数,则添加一个空数组作为 useEffect 的第二个参数

useEffect=(()=>{
   functionName(firstName,lastName);
},[firstName,lastName])

https://i.stack.imgur.com/jbUES.png

https://i.stack.imgur.com/SE8Nt.png