在 React 的官方文档中它提到 -
如果熟悉 React 类生命周期方法,可以将 useEffect Hook 视为 componentDidMount、componentDidUpdate 和 componentWillUnmount 的组合。
我的问题是 - 我们如何componentWillMount()
在挂钩中使用生命周期方法?
在 React 的官方文档中它提到 -
如果熟悉 React 类生命周期方法,可以将 useEffect Hook 视为 componentDidMount、componentDidUpdate 和 componentWillUnmount 的组合。
我的问题是 - 我们如何componentWillMount()
在挂钩中使用生命周期方法?
您不能在挂钩中使用任何现有的生命周期方法( 、 等componentDidMount
)componentDidUpdate
。componentWillUnmount
它们只能在类组件中使用。使用 Hooks,您只能在功能组件中使用。下面的行来自 React 文档:
如果您熟悉 React 类生命周期方法,您可以将
useEffect
Hook 视为componentDidMount
、componentDidUpdate
、 和componentWillUnmount
组合。
建议是,您可以在功能组件中从类组件中模仿这些生命周期方法。
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', () => {})
}
}, [])
const useComponentWillMount = (cb) => {
const willMount = useRef(true)
if (willMount.current) cb()
willMount.current = false
}
当存在顺序问题(例如在另一个脚本之前运行)时,这个钩子可能是一个保护程序。如果不是这种情况,请使用更符合 React hooks 范例的 useComnponentDidMount。
const useComponentDidMount = cb => useEffect(cb, []);
如果您知道您的效果应该只在开始时运行一次,请使用此解决方案。它只会在组件安装后运行一次。
类组件具有生命周期方法,这些方法被定义为组件时间线中的点。Hooks 不遵循这种范式。相反,效果应该由它们的内容构成。
function Post({postID}){
const [post, setPost] = useState({})
useEffect(()=>{
fetchPosts(postID).then(
(postObject) => setPost(postObject)
)
}, [postID])
...
}
在上面的示例中,效果处理获取帖子的内容。它不是某个时间点,而是有一个依赖于 - 的值postID
。每次postID
获得一个新值(包括初始化)时,它都会重新运行。
在类组件中, componentWillMount 被认为是遗留的(source 1,source2)。它是遗留的,因为它可能会运行不止一次,并且还有另一种选择 - 使用构造函数。这些考虑与功能组件无关。
根据 reactjs.org 的说法,将来不会支持 componentWillMount。 https://reactjs.org/docs/react-component.html#unsafe_componentwillmount
无需使用 componentWillMount。
如果你想在组件挂载之前做一些事情,只需在 constructor() 中做。
如果要进行网络请求,请不要在 componentWillMount 中进行。这是因为这样做会导致意想不到的错误。
网络请求可以在 componentDidMount 中完成。
希望能帮助到你。
更新于 08/03/2019
您要求 componentWillMount 的原因可能是因为您想在渲染之前初始化状态。
只需在 useState 中执行即可。
const helloWorld=()=>{
const [value,setValue]=useState(0) //initialize your state here
return <p>{value}</p>
}
export default helloWorld;
或者您可能想在 componentWillMount 中运行一个函数,例如,如果您的原始代码如下所示:
componentWillMount(){
console.log('componentWillMount')
}
使用钩子,您需要做的就是删除生命周期方法:
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 仍然只在组件安装时运行。
这取决于您是希望它只运行一次还是在每次渲染后运行。只要你知道自己在做什么,忘记添加数组并不危险。
我为钩子创建了一个示例。请检查一下。
https://codesandbox.io/s/kw6xj153wr
更新于 21/08/2019
我写上面的答案已经有一段时间了。我认为你需要注意一些事情。当你使用
useEffect(()=>{},[])
当 react 比较你传递给数组 [] 的值时,它用于Object.is()
比较。如果您将对象传递给它,例如
useEffect(()=>{},[{name:'Tom'}])
这与以下内容完全相同:
useEffect(()=>{})
它每次都会重新渲染,因为当Object.is()
比较一个对象时,它比较的是它的引用,而不是值本身。这与 {}==={} 返回 false 的原因相同,因为它们的引用不同。如果您仍然想比较对象本身而不是引用,您可以执行以下操作:
useEffect(()=>{},[JSON.stringify({name:'Tom'})])
2021 年 7 月 9 日更新:
关于依赖的一些更新:
一般来说,如果你使用一个函数或一个对象作为依赖,它总是会重新渲染。但是 react 已经为你提供了解决方案:useCallback 和 useMemo
useCallback 能够记住一个函数。useMemo 能够记住一个对象。
见这篇文章:
https://javascript.plainenglish.io/5-useeffect-infinite-loop-patterns-2dc9d45a253f
您可以破解 useMemo 挂钩来模仿 componentWillMount 生命周期事件。做就是了:
const Component = () => {
useMemo(() => {
// componentWillMount events
},[]);
useEffect(() => {
// componentDidMount events
return () => {
// componentWillUnmount events
}
}, []);
};
您需要在与您的状态交互的任何内容之前保留 useMemo 挂钩。这不是它的意图,但它对我所有 componentWillMount 问题都有效。
这是有效的,因为 useMemo 不需要实际返回一个值,并且您不必实际将它用作任何东西,但是因为它基于依赖项记住一个值,该依赖项只会运行一次(“[]”)并且它位于我们的组件之上当组件在其他任何东西之前安装时运行一次。
useLayoutEffect
[]
如果功能实际上类似于componentWillMount
- 它会在第一个内容到达 DOM 之前运行 - 尽管实际上有两个更新,但它们在绘制到屏幕之前是同步的,则可以使用一组空的观察者 ( ) 来完成此操作。
例如:
function MyComponent({ ...andItsProps }) {
useLayoutEffect(()=> {
console.log('I am about to render!');
},[]);
return (<div>some content</div>);
}
useState
使用初始化器/设置器的好处useEffect
是,尽管它可以计算渲染通道,但用户不会注意到对 DOM 的实际重新渲染,并且它在第一次显着渲染之前运行,这不是useEffect
. 缺点当然是您的第一次渲染略有延迟,因为在绘制到屏幕之前必须进行检查/更新。不过,这确实取决于您的用例。
我个人认为,useMemo
在某些需要做一些繁重的事情的小众情况下是可以的——只要你记住这是例外与常态。
这是我使用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的模拟。
我编写了一个自定义钩子,它将在第一次渲染之前运行一次函数。
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>
)
}
有一个很好的解决方法来实现componentDidMount
和componentWillUnmount
使用useEffect
.
根据文档,useEffect
可以返回一个“清理”函数。此函数不会在第一次useEffect
调用时调用,只会在后续调用中调用。
因此,如果我们使用useEffect
完全没有依赖关系的钩子,则只有在挂载组件时才会调用钩子,而在卸载组件时会调用“清理”函数。
useEffect(() => {
console.log('componentDidMount');
return () => {
console.log('componentWillUnmount');
};
}, []);
仅在卸载组件时调用清理返回函数。
希望这可以帮助。
componentWillMount
已弃用(1、2、3),并且建议的替换是在constructor
在功能组件的主体中只运行一次功能。这可以通过useState
、useMemo
或来实现useEffect
,具体取决于用例所需的时间。
由于代码需要在初始渲染提交到屏幕之前运行,这不合格useEffect
,因为“传递给 useEffect 的函数将在渲染提交到屏幕后运行。” 4 .
由于我们想保证代码只运行一次,这不符合useMemo
条件,因为“将来,React 可能会选择“忘记”一些以前记忆的值并在下一次渲染时重新计算它们” 5。
useState
支持惰性初始状态计算,保证在初始渲染期间只运行一次,这似乎是这项工作的好候选。
const runOnceBeforeRender = () => {};
const Component = () => {
useState(runOnceBeforeRender);
return (<></>);
}
const runOnceBeforeRender = () => {};
const useOnInitialRender = (fn) => {
useState(fn);
}
const Component = () => {
useOnInitialRender(fn);
return (<></>);
};
该runOnceBeforeRender
函数可以选择返回一个状态,该状态将在函数第一次渲染时立即可用,不会触发重新渲染。
https://reactjs.org/docs/hooks-reference.html#usememo
请记住,传递给 useMemo 的函数在渲染期间运行。不要在那里做任何你在渲染时通常不会做的事情。例如,副作用属于 useEffect,而不是 useMemo。
Ben Carp 的回答对我来说似乎只是一个有效的答案。
但是由于我们使用的是函数式方法,因此另一种方法可以从闭包和 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
已弃用并被视为 legacy。反应建议:
通常,我们建议使用 constructor() 来代替初始化状态。
现在在Hook FAQ中你会发现,函数组件的类构造函数的等价物是:
构造函数:函数组件不需要构造函数。您可以在 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; };
大多数人可能很清楚,但请记住,在函数组件的主体内部调用的函数充当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
正如反应文件中所述:
您可能会认为我们需要一个单独的效果来执行清理。但是用于添加和删除订阅的代码是如此紧密相关,以至于 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';
}
所以我们唯一需要在 hooks 中使用 componentWillUnmount 就是在 useEffect 中返回一个函数,如上所述。
所以对于 React 钩子,我认为在 return 语句可以工作之前声明你的逻辑。您应该有一个默认设置为 true 的状态。在我的例子中,我调用了状态组件WillMount。然后有条件在此状态为 true 时运行代码块(代码块包含您要在 componentWillMount 中执行的逻辑),此块中的最后一条语句应该将 componentWillMountState 重置为 false(这一步很重要,因为如果没有完成,会出现无限渲染)示例
// 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>
)*
这将确保在对象“详细信息”中有数据之前,加载三元运算符的第二部分,这反过来会触发 useEffect 方法,该方法导致在“详细信息”对象中设置从服务器接收的数据,因此呈现主要的 JSX
简单地在 React.useEffect() 中放置一个依赖数组作为第二个参数。如果任何依赖项更新,钩子将导致运行并最终更新您的组件的副作用。
我们最近遇到了这个问题,因为我们需要在组件挂载时做一些事情,即我们需要更新全局状态。
所以我创建了这个钩子,不确定这是一种多么好的方法,但到目前为止,只要我们谨慎使用它并且只用于简单的任务,它就可以工作。我可能不会将它用于网络请求和其他长时间运行的复杂任务。
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;
React 组件是一个函数,对吗?所以让moment 成为语句componentWillMount
之前的函数体。return
function componentWillMountMomentIsHere() {
console.log('component will mount')
}
function AnyComponent(){
const [greeting, setGreeting] = useState('Hello')
componentWillMountMomentIsHere()
return <h1>{greeting}</h1>
}
只需在 useEffect 中添加一个空的依赖数组,它将作为componentDidMount
.
useEffect(() => {
// Your code here
console.log("componentDidMount")
}, []);