React Hook:useEffect
useEffect
1.useEffect是用来使函数组件也可以进行副作用操作的。那么什么是副作用呢?
useEffect(didUpdate);
In computer science, an operation, function or expression is said to have a side effect if it modifies some state variable value(s) outside its local environment, that is to say has an observable effect besides returning a value (the main effect) to the invoker of the operation.
看不懂的话,直白的说就是:
函数的副作用就是函数除了返回值外对外界环境造成的其它影响。举个例子,假如我们每次执行一个函数,该函数都会操作全局的一个变量,那么对全局变量的操作就是这个函数的副作用。而在React的世界里,我们的副作用大体可以分为两类:
一类是调用浏览器的API,例如使用addEventListener来添加事件监听函数等.
另外一类是发起获取服务器数据的请求,例如当用户组件挂载的时候去异步获取用户的信息等。
在Hook出来之前,如果我们需要在组件中进行副作用的话就需要将组件写成类组件,然后在组件的生命周期函数里面写副作用,这其实会引起很多代码设计上的问题(具体问题:面试题).
Hook出来之后,开发者就可以在函数组件中使用useEffect来定义副作用了。虽然useEffect基本可以覆盖componentDidMount, componentDidUpdate,componentWillUnmount等生命周期函数组合起来使用的所有场景,但是useEffect和生命周期函数的设计理念还是存在本质上的区别的,如果一味用生命周期函数的思考方式去理解和使用useEffect的话,可能会引发一些奇怪的问题,大家有兴趣的话,可以看看React核心开发Dan写的这篇文章:A Complete Guide to useEffect(https://overreacted.io/a-complete-guide-to-useeffect) 里面阐述了使用useEffect的一个比较正确的思考方式。
2.语法:
import {useEffect} from "react" useEffect(effect?=>clean, dependencies?) //useEffect的第一个参数effect是要执行的副作用函数,它可以是任意的用户自定义函数,用户可以在这个函数里面 操作一些浏览器的API或者和外部环境进行交互,网络请求等,这个函数会在每次组件渲染完成之后被调用 //useEffect可以有一个返回值,返回一个函数,系统在组件重新渲染之前调用它 //第二个参数dependencies来限制该副作用的执行条件
案例1:useEffect基本用法
import React, { useState, useEffect } from 'react' import ReactDOM from 'react-dom' const UserDetail = ({ userId }) => { const [userDetail, setUserDetail] = useState({}) useEffect(() => { fetch(`http://ip:port/users/${userId}`) .then(response => response.json()) .then(user => setUserDetail(userDetail)) }) return ( <div> <div>User Name: {userDetail.name}</div> </div> ) } ReactDOM.render(<UserDetail />, document.getElementById('root')) //用户详情信息的副作用会在UserDetail组件每次完成渲染后执行,所以当该组件第一次挂载的时候就会向服务器发起获取用户详情信息的请求然后更新userDetail的值,这里的第一次挂载我们可以想象成类组件的componentDidMount。 //死循环:但是组件会不断向服务端发起请求。出现这个死循环的原因是useEffect里面调用了setUserDetail,这个函数会更新userDetail的值,从而使组件重渲染,而重渲染后useEffect的effect继续被执行,进而组件再次重渲染 //为了避免重复的副作用执行,useEffect允许我们通过第二个参数dependencies来限制该副作用什么时候被执行:指明了dependencies的副作用,只有在dependencies数组里面的元素的值发生变化时才会被执行,因此如果要避免上面的代码进入死循环我们就要将userId指定为我们定义的副作用的dependencies
案例2:dependencies参数
import React, { useState, useEffect } from 'react' import ReactDOM from 'react-dom' const UserDetail = ({ userId }) => { const [userDetail, setUserDetail] = useState({}) useEffect(() => { fetch(`https://myapi/users/${userId}`) .then(response => response.json()) .then(user => setUserDetail(userDetail)) }, [userId]) return ( <div> <div>User Name: ${userDetail.name}</div> </div> ) } ReactDOM.render(<UserDetail />, document.getElementById('root'))
案例3:effect返回值函数
import React, { useEffect } from 'react' import ReactDOM from 'react-dom' const WindowScrollListener = () => { useEffect(() => { const handleWindowScroll = () => console.log('正在滚') window.addEventListener('scroll', handleWindowScroll) // 这个就是清除函数 return () => { window.removeEventListener(handleWindowScroll) } }, []) return ( <div> 滚起来 </div> ) } ReactDOM.render(<WindowScrollListener />, document.getElementById('root')) //上面的代码中在WindowScrollListener组件首次渲染完成后注册一个监听页面滚动事件的函数,并在组件下一次渲染前移除该监听函数。由于我们指定了一个空数组作为这个副作用的dependencies,所以这个副作用只会在组件首次渲染时被执行一次,而它的cleanup函数只会在组件unmount时才被执行,这就避免了频繁注册页面的监听函数从而影响页面的性能。
总结:
useEffect(() => { }) //每次组件渲染都执行
useEffect(() => { return () => { } }, []) //组件第一次渲染执行,组件移除时触发clean函数
useEffect(() => { return () => { } }, [count])//count改变才会执行,组件重新渲染前触发clean函数
useEffect(didUpdate);