在React的功能组件中,有方法通过钩子来模拟componentDidMount吗?


当前回答

虽然公认的答案是可行的,但不建议使用。当你有多个状态并且你和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(...);
}

and

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(...);
}

这只是举个例子。因此,让我们不要谈论代码的最佳实践或潜在问题。两者的逻辑相同,但后者只能按预期工作。此时,你可能会获得componentDidMount功能和useEffect,但随着应用程序的发展,你可能会面临一些问题。所以,与其在那个阶段重写,不如在早期就重写。

此外,面向对象并不是那么糟糕,如果面向过程编程就足够了,我们就永远不会有面向对象编程了。有时会很痛苦,但更好(技术上来说。暂且把个人问题放在一边)。

其他回答

componentDidMount()的等效钩子是

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

希望这对你有帮助。

虽然公认的答案是可行的,但不建议使用。当你有多个状态并且你和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(...);
}

and

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(...);
}

这只是举个例子。因此,让我们不要谈论代码的最佳实践或潜在问题。两者的逻辑相同,但后者只能按预期工作。此时,你可能会获得componentDidMount功能和useEffect,但随着应用程序的发展,你可能会面临一些问题。所以,与其在那个阶段重写,不如在早期就重写。

此外,面向对象并不是那么糟糕,如果面向过程编程就足够了,我们就永远不会有面向对象编程了。有时会很痛苦,但更好(技术上来说。暂且把个人问题放在一边)。

是的,有一种方法可以在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钩子需要始终知道这一点。这包括你的方法,因为它是数组依赖关系。 未定义会让你面临难以发现的漏洞。

正因为如此,了解这是如何工作的,以及你可以做些什么来以“反应”的方式得到你想要的东西是很重要的。


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的分派方法是静态的,这意味着无论你的组件被重新渲染多少次,它都是相同的方法。因此,如果你只想执行某件事一次,而且你想在组件挂载之后立即执行,你可以像上面的例子那样做。这是一种正确的表述方式。

来源:The Complete Guide to useEffect - By Dan Abramov

也就是说,如果你喜欢实验东西,想知道如何做到“命令wat”,你可以使用useRef()与一个计数器或布尔值来检查ref是否存储了一个定义的引用,这是一个命令的方法,如果你不熟悉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>

如果你执行它,你会看到如下内容:

在react钩子中没有完全等价的componentDidMount。


根据我的经验,react钩子在开发时需要不同的心态,一般来说,你不应该将它与componentDidMount等类方法进行比较。

尽管如此,还是有一些方法可以使用钩子来产生与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])

方案三(带功能):

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钩子中使用componentDidMount

您希望使用useEffect(),这取决于您如何使用函数,可以像componentDidMount()一样工作。

如。你可以使用一个自定义的加载状态属性,初始设置为false,并在呈现时将其切换为true,并且仅在该值发生变化时触发效果。

文档