根据文件:

componentDidUpdate()在更新发生后立即调用。初始呈现时不调用此方法。

我们可以使用新的useEffect()钩子来模拟componentDidUpdate(),但似乎每次渲染后都会运行useEffect(),甚至是第一次渲染。我如何让它在初始渲染时不运行?

正如你在下面的例子中看到的,componentDidUpdateFunction在初始渲染期间被打印,但是componentDidUpdateClass在初始渲染期间没有被打印。

function ComponentDidUpdateFunction() { const [count, setCount] = React.useState(0); React.useEffect(() => { console.log("componentDidUpdateFunction"); }); return ( <div> <p>componentDidUpdateFunction: {count} times</p> <button onClick={() => { setCount(count + 1); }} > Click Me </button> </div> ); } class ComponentDidUpdateClass extends React.Component { constructor(props) { super(props); this.state = { count: 0, }; } componentDidUpdate() { console.log("componentDidUpdateClass"); } render() { return ( <div> <p>componentDidUpdateClass: {this.state.count} times</p> <button onClick={() => { this.setState({ count: this.state.count + 1 }); }} > Click Me </button> </div> ); } } ReactDOM.render( <div> <ComponentDidUpdateFunction /> <ComponentDidUpdateClass /> </div>, document.querySelector("#app") ); <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script> <div id="app"></div>


当前回答

如果你想跳过第一次渲染,你可以创建一个状态“firstRenderDone”,并在useEffect中使用空依赖列表将其设置为true(就像didMount一样)。然后,在你的另一个useEffect中,你可以检查第一个渲染是否已经在做某事之前完成。

const [firstRenderDone, setFirstRenderDone] = useState(false);

//useEffect with empty dependecy list (that works like a componentDidMount)
useEffect(() => {
  setFirstRenderDone(true);
}, []);

// your other useEffect (that works as componetDidUpdate)
useEffect(() => {
  if(firstRenderDone){
    console.log("componentDidUpdateFunction");
  }
}, [firstRenderDone]);

其他回答

我做了一个简单的useFirstRender钩子来处理像聚焦表单输入这样的情况:

import { useRef, useEffect } from 'react';

export function useFirstRender() {
  const firstRender = useRef(true);

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

  return firstRender.current;
}

它开始时为true,然后在useEffect中切换为false,只运行一次,再也不会运行。

在你的组件中使用它:

const firstRender = useFirstRender();
const phoneNumberRef = useRef(null);

useEffect(() => {
  if (firstRender || errors.phoneNumber) {
    phoneNumberRef.current.focus();
  }
}, [firstRender, errors.phoneNumber]);

对于您的情况,只需使用if (!firstRender) {....

前面所有的都很好,但是这可以通过一种更简单的方式实现,考虑到useEffect中的操作可以被“跳过”,放置一个基本上不是第一次运行的if条件(或任何其他条件),并且仍然具有依赖。

例如,我有一个案例:

从API加载数据但我的标题必须是“Loading”直到日期不存在,所以我有一个数组,开始时为空,并显示文本“显示” 使用来自这些API的不同信息来呈现组件。 用户可以一个一个删除这些信息,甚至所有使tour数组再次为空,作为开始,但这一次API获取已经完成 一旦旅游列表是空的删除,然后显示另一个标题。

所以我的“解决方案”是创建另一个useState来创建一个布尔值,该值仅在数据获取后更改,使useEffect中的另一个条件为真,以便运行另一个也依赖于巡回长度的函数。

useEffect(() => {
  if (isTitle) {
    changeTitle(newTitle)
  }else{
    isSetTitle(true)
  }
}, [tours])

这是我的App.js

import React, { useState, useEffect } from 'react'
import Loading from './Loading'
import Tours from './Tours'

const url = 'API url'

let newTours

function App() {
  const [loading, setLoading ] = useState(true)
  const [tours, setTours] = useState([])
  const [isTitle, isSetTitle] = useState(false)
  const [title, setTitle] = useState("Our Tours")

  const newTitle = "Tours are empty"

  const removeTours = (id) => {
    newTours = tours.filter(tour => ( tour.id !== id))

    return setTours(newTours)
  }

  const changeTitle = (title) =>{
    if(tours.length === 0 && loading === false){
      setTitle(title)
    }
  }

const fetchTours = async () => {
  setLoading(true)

  try {
    const response = await fetch(url)
    const tours = await response.json()
    setLoading(false)
    setTours(tours)
  }catch(error) {
    setLoading(false)
    console.log(error)
  }  
}


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

useEffect(() => {
  if (isTitle) {
    changeTitle(newTitle)
  }else{
    isSetTitle(true)
  }
}, [tours])


if(loading){
  return (
    <main>
      <Loading />
    </main>
  )  
}else{
  return ( 

    <main>
      <Tours tours={tours} title={title} changeTitle={changeTitle}           
removeTours={removeTours} />
    </main>
  )  
 }
}



export default App

@ravi,你的没有调用传入的卸载函数。下面是一个更完整的版本:

/** * Identical to React.useEffect, except that it never runs on mount. This is * the equivalent of the componentDidUpdate lifecycle function. * * @param {function:function} effect - A useEffect effect. * @param {array} [dependencies] - useEffect dependency list. */ export const useEffectExceptOnMount = (effect, dependencies) => { const mounted = React.useRef(false); React.useEffect(() => { if (mounted.current) { const unmount = effect(); return () => unmount && unmount(); } else { mounted.current = true; } }, dependencies); // Reset on unmount for the next mount. React.useEffect(() => { return () => mounted.current = false; }, []); };

@MehdiDehghani,你的解决方案工作得非常好,你必须做的一件事是卸载,重置didMount。当前值为false。当你试图在其他地方使用这个自定义钩子时,你不会得到缓存值。

import React, { useEffect, useRef } from 'react';

const useDidMountEffect = (func, deps) => {
    const didMount = useRef(false);

    useEffect(() => {
        let unmount;
        if (didMount.current) unmount = func();
        else didMount.current = true;

        return () => {
            didMount.current = false;
            unmount && unmount();
        }
    }, deps);
}

export default useDidMountEffect;
function useEffectAfterMount(effect, deps) {
  const isMounted = useRef(false);

  useEffect(() => {
    if (isMounted.current) return effect();
    else isMounted.current = true;
  }, deps);

  // reset on unmount; in React 18, components can mount again
  useEffect(() => {
    isMounted.current = false;
  });
}

我们需要返回从effect()返回的内容,因为它可能是一个清理函数。但我们不需要确定它是否是。只需传递它,让useEffect来计算。

在这篇文章的早期版本中,我说过重置ref (isMounted.)Current = false)没有必要。但在React 18中是这样的,因为组件可以以以前的状态重新挂载(感谢@Whatabrain)。