前言:
在react的class组件中,我们通过生命周期的钩子函数来完成一些副作用操作(发送网络请求,手动变更 DOM,记录日志等)。那么切换到函数组件,没有了this指向的生命周期钩子函数我们又要如何处理这些副作用操作呢?
React-hooks 为我们提供了 useEffect 钩子函数,来让我更好的处理副作用。
我们可以把 useEffect 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
一、如何使用useEffect
1.什么是副作用操作 ?
在 React 组件中有两种常见副作用操作:需要清除的和不需要清除的。
副作用:在组件中做的事,已经不算组件的核心逻辑了,那它就是副作用:发送网络请求,手动变更 DOM,记录日志等…
2. 在class中处理副作用(不需要清除的)
// 需求:组件第一次加载或更新后 手动操作更新 document.title
class App extends React.Component{
constructor(props){
super(props)
this.state = {
count:0
}
}
render(){
return (
<div>
<p>You Clicked:{this.state.count}</p>
<button onClick={()=>this.setState({count:this.state.count + 1})}>Click</button>
</div>
)
}
// 在两个生命周期钩子函数中,我们需要在两个生命周期函数中编写重复的代码。
componentDidMount(){
console.log(document.title); // React App
document.title = `Click ${this.state.count} times`
console.log(document.title); // Click 0 times
}
componentDidUpdate(){
console.log("componentDidUpdate");
document.title = `Click ${this.state.count} times`
}
}
3.在函数组件中使用useEffect处理副作用(不需要清除的)
function App() {
const [state, setState] = useState({
count: 0
})
// useEffect直接声明
// 每次渲染都会执行
// 直接定义在函数组件中,可以直接访问state和props
// 其内部函数的运行,不会阻塞浏览器视图的更新
useEffect(()=>{
document.title = `Click ${state.count} times`
console.log(document.title);
})
return (
<div>
<p>You Clicked:{state.count}</p>
<button onClick={() => setState({ count: state.count + 1 })}>Click</button>
</div>
)
}
4.为什么要使用useEffect ?
在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性。
二、useEffect的参数
useEffect可以传入两个参数:
1.第一个参数是 函数:内部装着处理副作用的逻辑
2.第二个参数是 依赖项数组:
(1)如果 不传 第二个参数,则 useEffect 每一次渲染都会执行
(2)如果 传入一个空数组 ,则 useEffect 只会执行一次
(3)如果 传入的数组中有依赖项,则依赖项的值改变了,useEffect 才会执行
function App() {
const [state, setState] = useState({
count: 0
})
// 传入空数组,useEffect只会执行一次
useEffect(()=>{
document.title = `Click ${state.count} times`
console.log(document.title); // 执行一次后就不会更新
}, [])
return (
<div>
<p>You Clicked:{state.count}</p>
<button onClick={() => setState({ count: state.count + 1 })}>Click</button>
</div>
)
}
function App() {
const [state, setState] = useState({
count: 0
})
const [name,setName] = useState('')
const [age,setAge] = useState(18)
// 添加依赖项后,只有依赖项数组内的值变化了才会触发useEffect
// 现在只有state变化,useEffect才会执行,name和age变化不会触发useEffect
useEffect(()=>{
console.log("useEffect");
document.title = `Click ${state.count} times`
console.log(document.title);
},[state])
return (
<div>
<p>You Clicked:{state.count}</p>
<p>You Name:{name}</p>
<p>You Age:{age}</p>
<button onClick={() => setState({ count: state.count + 1 })}>Click</button>
<button onClick={() => setName(`name ${state.count}`)}>Click Name</button>
<button onClick={() => setAge(age + 1)}>Click Age</button>
</div>
)
}
三、处理需要清除的副作用
没有清除副作用时:
function App(){
const [width,setWidth] = useState(document.body.clientWidth)
const resizeWidth = ()=>{
setWidth(document.body.clientWidth)
}
const removeComponent = useCallback(()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
},[])
useEffect(()=>{
window.addEventListener('resize',resizeWidth,false)
})
return (
<div>
<p>body client width:{width}</p>
<button onClick={removeComponent}>Remove Component</button>
</div>
)
}
点击移除之后
由此可见,移除之后,addEventListener仍然在监听width的变化,系统资源没有得到释放,所以我们要清除副作用
消除副作用我们可以在传入的函数中,再return一个回调函数消除副作用
function App(){
const [width,setWidth] = useState(document.body.clientWidth)
const resizeWidth = ()=>{
setWidth(document.body.clientWidth)
console.log(width);
}
const removeComponent = useCallback(()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
},[])
useEffect(()=>{
window.addEventListener('resize',resizeWidth,false)
// 在return中返回一个回调函数,清除监听器,消除副作用
return ()=>{
console.log("removeEventListener");
window.removeEventListener('resize',resizeWidth,false)
}
})
return (
<div>
<p>body client width:{width}</p>
<button onClick={removeComponent}>Remove Component</button>
</div>
)
}
消除副作用之后,就不会再触发监听事件了
为什么要在 effect 中返回一个函数?
返回一个函数可以让我们消除副作用
这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。
这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除。这样资源得到释放,提高了项目的性能。