React函数/钩子组件中的componentDidMount等效方法是什么?

304

在React函数组件中,是否有使用hooks模拟componentDidMount的方法?

11个回答

598

针对Hooks的稳定版本(React 版本 16.8.0+)

针对 componentDidMount

useEffect(() => {
  // Your code here
}, []);

对于componentDidUpdate方法

useEffect(() => {
  // Your code here
}, [yourDependency]);

componentWillUnmount用于在React组件卸载之前执行一些清理操作。

useEffect(() => {
  // componentWillUnmount
  return () => {
     // Your code here
  }
}, [yourDependency]);
在这种情况下,您需要将依赖项传递到该数组中。假设您的状态如下所示:const [count, setCount] = useState(0);每当计数增加时,您希望重新渲染函数组件。然后,您的useEffect应该如下所示。
useEffect(() => {
  // <div>{count}</div>
}, [count]);

这样,每当您的计数更新时,您的组件将重新渲染。希望这能有所帮助。


详细解释!有没有一种方法可以模拟componentDidReceiveProps? - jeyko
1
谢谢您提供这个信息,我之前不知道 useState 中还有第二个参数。对于任何阅读此内容的人,请记住,如果将第二个参数保留为 undefined,则会在每次渲染时触发您的 effect(如果我没有弄错的话)。 - dimiguel
3
我一直在尝试使用空的依赖数组来模拟componentDidMount。问题是通常会出现警告:"React Hook useEffect has a missing dependency: <some prop>. Either include it or remove the dependency array react-hooks/exhaustive-deps"。应用建议的任一“修复”都会使其不再像componentDidMount那样运行。我做错了什么吗? - Jonas Rosenqvist
6
这个回答在建议它是 componentDidMount 的等效替代时是错误的(问题是要求一个“等效替代”),它只展示了在某些情况下可行的解决方法。一个正确的答案应该指出没有等价替代,并为所有使用情况提供推荐的解决方法。 - kca
11
这是互联网上最简洁的useEffect()文档。 - nonethewiser
显示剩余3条评论

45

在React Hooks中,没有与componentDidMount完全相等的对应方法。


根据我的经验,使用React Hooks需要一种不同的开发思维方式,一般来说,你不应该将其与类方法(如componentDidMount)进行比较。

话虽如此,你可以通过某些方式使用Hooks产生类似于componentDidMount的效果

解决方案1:

useEffect(() => {
  console.log("I have been mounted")
}, [])

解决方案2:

const num = 5

useEffect(() => {
  console.log("I will only run if my deps change: ", num)
}, [num])

解决方案 3(使用函数):

useEffect(() => {
  const someFunc = () => {
    console.log("Function being run after/on mount")
  }
  someFunc()
}, [])

解决方案4(useCallback):

const msg = "some message"

const myFunc = useCallback(() => {
  console.log(msg)
}, [msg])

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

解决方案5(创意方法):

export default function useDidMountHook(callback) {
  const didMount = useRef(null)

  useEffect(() => {
    if (callback && !didMount.current) {
      didMount.current = true
      callback()
    }
  })
}

值得注意的是,只有在其他解决方案都无法满足您的用例时,才应真正使用解决方案5。如果您决定需要解决方案5,则建议使用此预制挂钩use-did-mount
来源(更多细节):在React Hooks中使用componentDidMount

17

在函数式组件中没有componentDidMount,但是React Hooks提供了一种方法,可以通过使用useEffect hook来模拟其行为。

将空数组作为第二个参数传递给useEffect(),以仅在挂载时运行回调函数。

请阅读有关useEffect文档

function ComponentDidMount() {
  const [count, setCount] = React.useState(0);
  React.useEffect(() => {
    console.log('componentDidMount');
  }, []);

  return (
    <div>
      <p>componentDidMount: {count} times</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click Me
      </button>
    </div>
  );
}

ReactDOM.render(
  <div>
    <ComponentDidMount />
  </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>


6

useEffect() 钩子使我们能够实现componentDidMount,componentDidUpdate和componentWillUnMount功能。

useEffect()的不同语法可以实现上述每种方法。

i) componentDidMount

useEffect(() => {
  //code here
}, []);

ii) componentDidUpdate

useEffect(() => {
  //code here
}, [x,y,z]);

//where x,y,z are state variables on whose update, this method should get triggered

iii) componentDidUnmount

useEffect(() => {
  //code here
  return function() {
    //code to be run during unmount phase
  }
}, []);

你可以查看官方的React网站获取更多信息。 有关Hooks的React官方页面

4
虽然被接受的答案可行,但不建议使用。当您有多个状态并将其与 useEffect 一起使用时,它会警告您将其添加到依赖项数组中或根本不使用它。
它有时会导致问题,可能会给您带来不可预测的输出。因此,我建议您花一点力气将函数重写为类。更改很少,您可以将某些组件设置为类,将某些组件设置为函数。您不必只使用一种约定。
以这个为例。
function App() {
  const [appointments, setAppointments] = useState([]);
  const [aptId, setAptId] = useState(1);

  useEffect(() => {
    fetch('./data.json')
      .then(response => response.json())
      .then(result => {
        const apts = result.map(item => {
          item.aptId = aptId;
          console.log(aptId);
          setAptId(aptId + 1);
          return item;
        })
        setAppointments(apts);
      });
  }, []);

  return(...);
}

class App extends Component {
  constructor() {
    super();
    this.state = {
      appointments: [],
      aptId: 1,
    }
  }

  componentDidMount() {
    fetch('./data.json')
      .then(response => response.json())
      .then(result => {
        const apts = result.map(item => {
          item.aptId = this.state.aptId;
          this.setState({aptId: this.state.aptId + 1});
          console.log(this.state.aptId);
          return item;
        });
        this.setState({appointments: apts});
      });
  }

  render(...);
}

这仅供示例。因此,我们不要讨论最佳实践或代码的潜在问题。这两个都具有相同的逻辑,但后者只能按预期工作。您可能会在此次运行中使用useEffect获取componentDidMount功能,但随着应用程序的增长,您可能会面临一些问题。因此,与其在那个阶段重写代码,不如在早期进行操作。

此外,OOP并不那么糟糕,如果过程式编程足够好,我们就永远不会有面向对象编程。有时候会很痛苦,但从技术上来说更好(个人问题除外)。


2
我做了这个。使用钩子时遇到了问题。将其转换为类后,问题不复存在。 - Julez
2
我还没有看到过一个useEffect的“陷阱”,不能通过重构代码来解决 - 包括这个例子。使用setState的回调版本或将有问题的函数完全移出渲染周期通常可以解决问题 - 如果不行,那么你的状态可能太复杂了,需要实现自己的reducer。Hooks并非强制要求,但显然是React的未来。我建议阅读这篇优秀的关于useEffect的文章 - 当我开始遇到这些问题时,它确实帮助我理解了它。 - lawrence-witt

3

是的,有一种方法可以在React函数组件中模拟componentDidMount

免责声明:真正的问题是,您需要从“组件生命周期思维方式”转变为“useEffect思维方式”

一个React组件本质上还是一个JavaScript函数,所以如果您想要在某个其他东西之前执行某些操作,只需从上到下先执行它即可。 如果你仔细想想,函数仍然是一个函数,例如:

const myFunction = () => console.log('a')
const mySecondFunction = () => console.log('b)

mySecondFunction()
myFunction()

/* Result:
'b'
'a'
*/

这真的很简单,不是吗?

const MyComponent = () => {
const someCleverFunction = () => {...}

someCleverFunction() /* there I can execute it BEFORE 
the first render (componentWillMount)*/

useEffect(()=> {
  someCleverFunction() /* there I can execute it AFTER the first render */ 
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be mapped here, trust me, I will leave this in blank.*/

return (
 <div>
   <h1>Hi!</h1>
 </div>
)}

在这种特定情况下是正确的。但是如果我做类似于这样的事情会发生什么:

const MyComponent = () => {
const someCleverFunction = () => {...}

someCleverFunction() /* there I can execute it BEFORE 
the first render (componentWillMount)*/

useEffect(()=> {
  someCleverFunction() /* there I can execute it AFTER the first render */ 
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be maped here, trust me, I will leave this in blank.*/
return (
 <div>
   <h1>Hi!</h1>
 </div>
)}

我们定义的这个“cleverFunction”在组件每次重新渲染时并不是相同的。 这会导致一些恶心的错误,有时还会导致组件不必要的重新渲染或无限的重新渲染循环。

真正的问题在于React函数组件是一个函数,通过useEffect钩子(等等)多次“执行自身”取决于你的状态。

简而言之,useEffect是一个专门设计用于将数据与屏幕上看到的内容同步的钩子。如果您的数据发生更改,则需要使useEffect钩子意识到这一点,始终如此。这包括您的方法,因为那就是数组依赖项。 未定义它会让您暴露于难以发现的错误中。

因此,了解这背后的工作原理以及您可以如何以“react”的方式获得所需非常重要。


const initialState = {
  count: 0,
  step: 1,
  done: false
};

function reducer(state, action) {
  const { count, step } = state;
  if (action.type === 'doSomething') {
    if(state.done === true) return state;
    return { ...state, count: state.count + state.step, state.done:true };
  } else if (action.type === 'step') {
    return { ...state, step: action.step };
  } else {
    throw new Error();
  }
}

const MyComponent = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;

useEffect(() => {
    dispatch({ type: 'doSomething' });
}, [dispatch]);

return (
 <div>
   <h1>Hi!</h1>
 </div>
)}

useReducer的dispatch方法是静态的,这意味着无论您的组件重新渲染多少次,它都将是相同的方法。因此,如果您想执行一些操作仅一次,并且希望在组件挂载后立即执行,可以像上面的示例那样做。这是一种声明性的正确方式。

来源:完整指南useEffect-作者Dan Abramov

话虽如此,如果您喜欢尝试各种事物并想知道如何“命令式地”执行它,可以使用useRef()与计数器或布尔值一起使用,以检查该引用是否存储了定义的引用。这是一种命令式的方法,如果您不熟悉react在幕后发生的情况,建议避免使用它。

这是因为useRef()是一个钩子,无论渲染的次数如何,它都会保存传递给它的参数(我之所以简单解释是因为这不是问题的重点,你可以阅读这篇精彩文章了解useRef)。因此,这是最好的方法来知道组件的第一次渲染发生的时间。

我留下一个示例,展示了同步“外部”效果(如外部函数)与“内部”组件状态的3种不同方式。

您可以在此处运行此片段以查看日志并了解这3个函数何时执行。

const { useRef, useState, useEffect, useCallback } = React

// External functions outside react component (like a data fetch)

function renderOnce(count) {
    console.log(`renderOnce: I executed ${count} times because my default state is: undefined by default!`);
}

function renderOnFirstReRender(count) {
    console.log(`renderOnUpdate: I executed just ${count} times!`);
}

function renderOnEveryUpdate(count) {
    console.log(`renderOnEveryUpdate: I executed ${count ? count + 1 : 1} times!`);
}

const MyComponent = () => {
    const [count, setCount] = useState(undefined);

    const mounted = useRef(0);

    // useCallback is used just to avoid warnings in console.log
    const renderOnEveryUpdateCallBack = useCallback(count => {
        renderOnEveryUpdate(count);
    }, []);

    if (mounted.current === 0) {
        renderOnce(count);
    }

    if (mounted.current === 1) renderOnFirstReRender(count);

    useEffect(() => {
        mounted.current = mounted.current + 1;
        renderOnEveryUpdateCallBack(count);
    }, [count, renderOnEveryUpdateCallBack]);

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={() => setCount(prevState => (prevState ? prevState + 1 : 1))}>TouchMe</button>
        </div>
    );
};

class App extends React.Component {
    render() {
        return (
            <div>
                <h1>hI!</h1>
            </div>
        );
    }
}

ReactDOM.createRoot(
    document.getElementById("root")
).render(
  <MyComponent/>
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

如果你执行它,你会看到类似这样的东西: result of the snippet


2
import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

请访问此官方文档。非常易懂,采用了最新的方式

https://reactjs.org/docs/hooks-effect.html


1

关于钩子内异步函数的信息:

为了防止竞态条件,Effect 回调是同步的。请将异步函数放在内部:

useEffect(() => {
  async function fetchData() {
    // You can await here
    const response = await MyAPI.getData(someId);
    // ...
  }
  fetchData();
}, [someId]); // Or [] if effect doesn't need props or state

0

useLayoutEffect钩子是React Hooks中替代ComponentDidMount的最佳选择。

useLayoutEffect钩子在渲染UI之前执行,而useEffect钩子在渲染UI之后执行。根据您的需求使用它。

示例代码:

import { useLayoutEffect, useEffect } from "react";

export default function App() {
  useEffect(() => {
    console.log("useEffect Statements");
  }, []);

  useLayoutEffect(() => {
    console.log("useLayoutEffect Statements");
  }, []);
  return (
    <div>
      <h1>Hello Guys</h1>
    </div>
  );
}
 

-1

你想要使用 useEffect(),它根据函数的使用方式,可以像 componentDidMount() 一样工作。

例如,你可以使用自定义的 loaded 状态属性,将其最初设置为 false,并在渲染时将其切换为 true,并仅在该值更改时触发效果。

文档


1
这个解决方案并不理想。仅仅为了确定组件是否已经挂载,使用状态值是一个糟糕的想法。此外,如果您要使用属性,引用会更好,因为它不会触发另一个重新渲染。 - Yangshun Tay

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