如何在React钩子中使用componentWillMount() ?

在React的官方文档中提到了-

如果你熟悉React类的生命周期方法,你可以认为 将useEffect钩子作为componentDidMount, componentDidUpdate和 componentWillUnmount总和。< / p >

我的问题是——我们如何在钩子中使用componentWillMount()生命周期方法?

411171 次浏览
根据reactjs.org的说法,componentWillMount在未来将不被支持。 https://reactjs.org/docs/react-component.html#unsafe_componentwillmount < / p >

不需要使用componentWillMount。

如果你想在组件挂载之前执行一些操作,只需在构造函数()中执行即可。

如果你想做网络请求,不要在componentWillMount中做。这是因为这样做会导致意想不到的错误。

网络请求可以在componentDidMount中完成。

希望能有所帮助。


2019年8月3日更新

你请求componentWillMount的原因可能是因为你想在呈现之前初始化状态。

就在我们的地盘上做吧。

const helloWorld=()=>{
const [value,setValue]=useState(0) //initialize your state here
return <p>{value}</p>
}
export default helloWorld;

或者你想在componentWillMount中运行一个函数,例如,如果你的原始代码看起来像这样:

componentWillMount(){
console.log('componentWillMount')
}

使用hook,你所需要做的就是删除生命周期方法:

const hookComponent=()=>{
console.log('componentWillMount')
return <p>you have transfered componeWillMount from class component into hook </p>
}

我只是想对第一个关于useEffect的答案补充一些东西。

useEffect(()=>{})

useEffect运行在每次渲染上,它是componentDidUpdate, componentDidMount和ComponentWillUnmount的组合。

 useEffect(()=>{},[])
如果我们在useEffect中添加一个空数组,它只在组件挂载时运行。这是因为useEffect将比较您传递给它的数组。 所以它不一定是空数组。它可以是一个不变的数组。例如,它可以是[1,2,3]或['1,2']。useEffect仍然只在组件挂载时运行

这取决于你是想让它只运行一次,还是在每次渲染后运行。如果您忘记添加数组,只要您知道自己在做什么,这并不危险。

我为hook创建了一个示例。请查看一下。

< a href = " https://codesandbox。io / s / kw6xj153wr noreferrer“rel = > https://codesandbox.io/s/kw6xj153wr < / >


2019年8月21日更新

自从我写下上面的答案已经有一段时间了。有件事我觉得你需要注意。 当你使用

useEffect(()=>{},[])
当react比较传递给数组[]的值时,它使用Object.is()进行比较。 如果你传递一个对象给它,比如

useEffect(()=>{},[{name:'Tom'}])

这和:

useEffect(()=>{})
它每次都会重新呈现,因为当Object.is()比较一个对象时,它比较的是它的引用,而不是值本身。这和为什么{}==={}返回false是一样的,因为它们的引用不同。 如果你仍然想比较对象本身而不是引用,你可以这样做:

useEffect(()=>{},[JSON.stringify({name:'Tom'})])

2021年7月9日更新:

关于依赖关系的一些更新:

一般来说,如果你使用一个函数或一个对象作为依赖项,它总是会重新呈现。但是react已经为您提供了解决方案:useCallback和useMemo

useCallback能够记住一个函数。 useMemo能够记住一个对象。

请看这篇文章:

< a href = " https://javascript.plainenglish。io / 5-useeffect-infinite-loop-patterns-2dc9d45a253f noreferrer“rel = > https://javascript.plainenglish.io/5-useeffect-infinite-loop-patterns-2dc9d45a253f < / >

你不能在钩子中使用任何现有的生命周期方法(componentDidMountcomponentDidUpdatecomponentWillUnmount等)。它们只能在类组件中使用。而Hooks只能用于功能组件。下面这句话来自React文档:

如果你熟悉React类的生命周期方法,你可以把useEffect Hook想象成componentDidMountcomponentDidUpdatecomponentWillUnmount的组合。

建议是,可以在功能组件中从类组件中模拟这些生命周期方法。

componentDidMount中的代码在组件挂载时只运行一次。useEffect钩子等价于此行为是

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

注意这里的第二个参数(空数组)。这将只运行一次。

没有第二个参数 useEffect钩子将在组件的每次渲染时被调用,这可能是危险的。

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

componentWillUnmount用于清理(比如删除事件监听器,取消计时器等)。假设你在componentDidMount中添加了一个事件监听器,并在componentWillUnmount中删除它,如下所示。

componentDidMount() {
window.addEventListener('mousemove', () => {})
}


componentWillUnmount() {
window.removeEventListener('mousemove', () => {})
}

与上述代码等价的钩子如下所示

useEffect(() => {
window.addEventListener('mousemove', () => {});


// returned function will be called on component unmount
return () => {
window.removeEventListener('mousemove', () => {})
}
}, [])

https://reactjs.org/docs/hooks-reference.html#usememo

记住传递给useMemo的函数在呈现期间运行。 不要在那里做渲染时通常不会做的事情。 例如,side effects属于useEffect,而不是useMemo.

useLayoutEffect可以用一个空的观察者集([])来完成这一点,如果功能实际上类似于componentWillMount——它将在第一个内容到达DOM之前运行——尽管实际上有两次更新,但它们在绘制到屏幕之前是同步的。

例如:


function MyComponent({ ...andItsProps }) {
useLayoutEffect(()=> {
console.log('I am about to render!');
},[]);


return (<div>some content</div>);
}


与带有初始化器/setter或useEffectuseState相比,它的好处是尽管它可以计算一个渲染传递,但用户不会注意到对DOM的实际重新渲染,并且它在第一次值得注意的渲染时运行之前,而useEffect则不是这样。缺点当然是在你的第一次渲染中有轻微的延迟,因为在绘制到屏幕之前必须进行检查/更新。不过,这确实取决于您的用例。

我个人认为,useMemo是好的在一些利基情况下,你需要做一些沉重的事情-只要你记住它是例外与规范。

useComponentWillMount钩

const useComponentWillMount = (cb) => {
const willMount = useRef(true)


if (willMount.current) cb()


willMount.current = false
}

当出现顺序问题(比如在另一个脚本之前运行)时,这个钩子可以作为一个保护程序。如果不是这样,请使用useComnponentDidMount,它更符合React钩子的范例。

useComponentDidMount钩

const useComponentDidMount = cb => useEffect(cb, []);

如果你知道你的效果应该只运行一次在开始使用这个解决方案。它只会在组件挂载后运行一次。

useEffect范式

类组件具有生命周期方法,这些方法定义为组件时间轴上的点。钩子不遵循这种范式。相反,效果应该由内容构成。

function Post({postID}){
const [post, setPost] = useState({})


useEffect(()=>{
fetchPosts(postID).then(
(postObject) => setPost(postObject)
)
}, [postID])


...
}

在上面的例子中,效果处理的是获取文章的内容。它不是一个特定的时间点,而是一个它依赖的值——postID。每次postID获得一个新值(包括初始化),它将重新运行。

组件将挂载讨论

在类组件中componentWillMount被认为是遗留的(源1source2)。它是遗留的,因为它可能运行不止一次,而且还有另一种选择——使用构造函数。这些考虑因素与功能组件无关。

我写了一个自定义钩子,它将在第一次渲染之前运行一个函数。

useBeforeFirstRender.js

import { useState, useEffect } from 'react'


export default (fun) => {
const [hasRendered, setHasRendered] = useState(false)


useEffect(() => setHasRendered(true), [hasRendered])


if (!hasRendered) {
fun()
}
}

用法:

import React, { useEffect } from 'react'
import useBeforeFirstRender from '../hooks/useBeforeFirstRender'




export default () => {
useBeforeFirstRender(() => {
console.log('Do stuff here')
})


return (
<div>
My component
</div>
)
}

有一个很好的变通方法可以用useEffect实现componentDidMountcomponentWillUnmount

根据文档,useEffect可以返回一个“cleanup”函数。这个函数不会在第一次useEffect调用时被调用,只在后续调用时被调用。

因此,如果我们使用的useEffect钩子完全没有依赖关系,钩子将只在组件被挂载时被调用,而“cleanup”函数将在组件被卸载时被调用。

useEffect(() => {
console.log('componentDidMount');


return () => {
console.log('componentWillUnmount');
};
}, []);

只有在卸载组件时才调用清理返回函数调用。

希望这能有所帮助。

本·卡普的答案对我来说似乎是唯一有效的答案。

但由于我们使用的是函数式方法,另一种方法可以受益于闭包和HoC:

const InjectWillmount = function(Node, willMountCallback) {
let isCalled = true;
return function() {
if (isCalled) {
willMountCallback();
isCalled = false;
}
return Node;
};
};

然后使用它:

const YourNewComponent = InjectWillmount(<YourComponent />, () => {
console.log("your pre-mount logic here");
});

对你的原始问题的简短回答,componentWillMount如何与React Hooks一起使用:

componentWillMount已弃用并考虑为遗留建议反应:

通常,我们建议使用构造函数()来初始化状态。

现在,在钩常见问题解答中,你会发现函数组件的类构造函数的等价物是:

构造函数:函数组件不需要构造函数。你可以在useState调用中初始化这个状态。如果计算初始状态的开销很大,您可以将一个函数传递给useState。

componentWillMount的用法示例如下所示:

const MyComp = () => {
const [state, setState] = useState(42) // set initial value directly in useState
const [state2, setState2] = useState(createInitVal) // call complex computation


return <div>{state},{state2}</div>
};


const createInitVal = () => { /* ... complex computation or other logic */ return 42; };

这是我如何使用useRef钩子模拟函数组件中的构造函数的方式:

function Component(props) {
const willMount = useRef(true);
if (willMount.current) {
console.log('This runs only once before rendering the component.');
willMount.current = false;
}


return (<h1>Meow world!</h1>);
}

下面是生命周期的例子:

function RenderLog(props) {
console.log('Render log: ' + props.children);
return (<>{props.children}</>);
}


function Component(props) {


console.log('Body');
const [count, setCount] = useState(0);
const willMount = useRef(true);


if (willMount.current) {
console.log('First time load (it runs only once)');
setCount(2);
willMount.current = false;
} else {
console.log('Repeated load');
}


useEffect(() => {
console.log('Component did mount (it runs only once)');
return () => console.log('Component will unmount');
}, []);


useEffect(() => {
console.log('Component did update');
});


useEffect(() => {
console.log('Component will receive props');
}, [count]);




return (
<>
<h1>{count}</h1>
<RenderLog>{count}</RenderLog>
</>
);
}
[Log] Body
[Log] First time load (it runs only once)
[Log] Body
[Log] Repeated load
[Log] Render log: 2
[Log] Component did mount (it runs only once)
[Log] Component did update
[Log] Component will receive props

当然Class组件没有Body步骤,由于函数和类的概念不同,不可能进行1:1模拟。

只需在useEffect中添加一个空的依赖数组,它将作为componentDidMount工作。

useEffect(() => {
// Your code here
console.log("componentDidMount")
}, []);
你可以修改useMemo钩子来模仿componentWillMount生命周期事件。 只做:< / p >
const Component = () => {
useMemo(() => {
// componentWillMount events
},[]);
useEffect(() => {
// componentDidMount events
return () => {
// componentWillUnmount events
}
}, []);
};

您需要在任何与您的状态交互之前保留useMemo钩子。这不是它的意图,但它适用于我的所有componentWillMount问题。

这是可行的,因为useMemo并不需要实际返回一个值,你也不必实际使用它作为任何东西,但由于它基于依赖关系记忆一个只会运行一次的值("[]")并且它在我们的组件之上,它在组件挂载之前运行一次。

对于大多数人来说,这可能很清楚,但请记住,在函数组件体内调用的函数充当beforeRender。这并没有回答在ComponentWillMount上运行代码的问题(在第一次渲染之前),但由于它是相关的,可能会帮助其他人,所以我把它留在这里。

const MyComponent = () => {
const [counter, setCounter] = useState(0)
  

useEffect(() => {
console.log('after render')
})


const iterate = () => {
setCounter(prevCounter => prevCounter+1)
}


const beforeRender = () => {
console.log('before render')
}


beforeRender()


return (
<div>
<div>{counter}</div>
<button onClick={iterate}>Re-render</button>
</div>
)
}


export default MyComponent

钩子中的React生命周期方法

为了简单的视觉参考,请遵循此图像

enter image description here

正如您在上图中所看到的,对于ComponentWillUnmount,您必须这样做

 useEffect(() => {
return () => {
console.log('componentWillUnmount');
};
}, []);

正如反应文档中所述:

你可能会认为我们需要一个单独的效果来执行 清理。但是添加和删除订阅的代码非常紧凑 useEffect的设计是为了将它们放在一起。如果你的效果 返回一个函数,React会在清理的时候运行它:

useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});


if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}

所以我们唯一需要在钩子中使用componentWillUnmount的是在useEffect中返回一个函数,如上所述。

考虑到

  • componentWillMount已弃用(123.),建议的替换是在constructor . c中执行代码
  • 在函数组件的return语句之前执行的代码在呈现之前隐式运行
  • 与挂载类组件大致相当的是函数组件的初始调用
  • 目标是在UI更新之前执行一些代码一次

解决办法是

在函数组件的主体中只运行一次函数。这可以通过useStateuseMemouseEffect来实现,具体取决于用例所需的时间。

由于代码需要在初始渲染提交到屏幕之前运行,这将取消useEffect的资格,因为“传递给useEffect的函数将在渲染提交到屏幕之后运行。“4

由于我们想要保证代码只运行一次,这就使useMemo不符合条件,因为“在未来,React可能会选择“忘记”一些先前记住的值,并在下一次渲染时重新计算它们”。5

useState支持惰性初始状态计算,保证在初始渲染期间只运行一次,这似乎是一个很好的工作候选人。

useState的示例:

const runOnceBeforeRender = () => {};


const Component = () => {
useState(runOnceBeforeRender);


return (<></>);
}

作为自定义钩子:

const runOnceBeforeRender = () => {};


const useOnInitialRender = (fn) => {
useState(fn);
}


const Component = () => {
useOnInitialRender(runOnceBeforeRender);


return (<></>);
};

runOnceBeforeRender函数可以选择返回一个在函数第一次呈现时立即可用的状态,不会触发重新呈现。

一个(可能不必要的)NPM包:useOnce钩

所以对于React钩子,我认为在return语句之前声明你的逻辑是可以工作的。您应该有一个默认设置为true的状态。在我的例子中,我调用了状态组件willmount。然后在此状态为true时运行一个条件代码块(该代码块包含您希望在componentWillMount中执行的逻辑),此块中的最后一条语句应该将componentWillMountState重置为false(这一步很重要,因为如果不执行此步骤,将会发生无限渲染) 示例< / p >
// do your imports here


const App = () =>  {
useEffect(() => {
console.log('component did mount')
}, [])
const [count, setCount] = useState(0);
const [componentWillMount, setComponentWillMount] = useState(true);
if (componentWillMount) {
console.log('component will mount')
// the logic you want in the componentWillMount lifecycle
setComponentWillMount(false)
}
  

return (
<div>
<div>
<button onClick={() => setCount(count + 1)}>
press me
</button>
<p>
{count}
</p>
      

</div>
</div>
)
}

这可能不是componentWillMount方法的确切替代方法,但这里有一个方法可以用来实现同样的目标,但要使用useEffect:

首先初始化检索数据的对象为空值,并定义useEffect方法:

const [details, setDetails] = useState("")


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


const retrieveData = () => {
getData()                  // get data from the server
.then(response => {
console.log(response.data);
setDetails(response.data)
})
.catch(e => {
console.log(e);
})
}

在JSX中,我们返回一个三元操作符

*return(
<div>
{
details ? (
<div class="">
<p>add Your Jsx Here</p>
</div>
): (
<div>
<h4>Content is still Loading.....</h4>
</div>
)
}
</div>
)*

这将确保在对象'details'中有数据之前,terenary操作符的第二部分会被加载,该操作符会触发useEffect方法,该方法导致在'details'对象中设置从服务器接收到的数据,从而呈现主JSX

简单地在React.useEffect()中放置一个依赖数组作为第二个参数。如果任何依赖项更新,钩子将导致运行并最终更新组件的副作用。

React组件是一个函数?因此,让componentWillMount moment作为return语句之前的函数体。

function componentWillMountMomentIsHere() {
console.log('component will mount')
}




function AnyComponent(){
const [greeting, setGreeting] = useState('Hello')


componentWillMountMomentIsHere()


  

return <h1>{greeting}</h1>
}

我们最近遇到了这个问题,因为我们需要在组件挂载时做一些事情,也就是我们需要更新全局状态。

所以我创建了这个钩子,不确定这个方法有多好,但到目前为止,只要我们少用它,只用于简单的任务,它就能工作。我可能不会将它用于网络请求和其他长时间运行的复杂任务。

import { useRef } from 'react';


function useComponentWillMount(callback: () => void) {
const hasMounted = useRef(false);


if (!hasMounted.current) {
(() => {
hasMounted.current = true;
callback();
})();
}


console.log(hasMounted.current);
return null;
}


export default useComponentWillMount;

componentWillMount已弃用(正如在其他评论中提到的),原因是我认为它很容易被一个简单的HOC处理。

const withComponentWillMount = (WrappedComponent, handler) => {
return (props) => {
return handler(props) ? <WrappedComponent {...props} /> : null;
}
}

我通常在我的项目中实现这个解决方案。使用此HOC,如果处理程序返回false,则组件内部不运行任何内容,包括钩子。