类组件中存在生命周期函数,为了使函数式组件具备类似声明周期的功能,react提供了useEffect
,可以看作是componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
基础用法
定义一个函数和一个数组。函数体为组件初始化或变化时执行的代码,返回值为组件销毁前执行的代码。数组参数中放的是触发此函数的依赖项数据。
useEffect(() => {
// 相当于 componentDidMount、componentDidUpdate
console.log("code");
return () => {
// 相当于 componentWillUnmount
console.log("code");
}
}, [/*依赖项*/])
监听参数
类组件在绑定事件、解绑事件、设定定时器、查找 dom 的时候,是通过 componentDidMount、componentDidUpdate、componentWillUnmount 生命周期来实现的,而 useEffect 会在组件每次 render 之后调用,就相当于这三个生命周期函数,只不过可以通过传参来决定是否调用。
//最简单用法
import { Button } from "element-react";
import { useState, useEffect } from "react";
const Test1 = () => {
const [count, setCount] = useState(0)
const [sum, setSum] = useState(0)
useEffect(() => {
//只有方法体,相当于componentDidMount和componentDidUpdate中的代码
console.log("count", count)
})
return (
<div>
<p>count:{count}</p>
<p>sum:{sum}</p>
<Button onClick={() => setCount(count + 1)}>count+1</Button>
<Button onClick={() => setSum(sum + 1)}>sum+1</Button>
</div>
)
}
export default Test1;
从图片中可以看出,无论是count还是sum,只要发生改变useEffect就会执行。
//加返回值用法
useEffect(() => {
console.log("count", count)
return () => {
//返回的函数用于解绑事件,相当于componentWillUnmount中的代码
console.log("执行销毁....")
}
})
useEffect(() => {
console.log("count",count)
return () => {
console.log("执行销毁....")
}
}, [])
注:
1、这里报警告了,意思是缺少依赖项。如果要用空数组,useEffect中不要出现useState中的属性
2、空数组时,useEffect只会执行一次
useEffect(() => {
console.log("count", count)
}, [count])
从图片中可以看出只有count改变时,才会执行useEffect
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
useCallback返回一个函数,当把它返回的这个函数作为子组件使用时,可以避免每次父组件更新时都重新渲染这个子组件
const renderButton = useCallback(
() => (
<Button type="link">
{buttonText}
</Button>
),
[buttonText] // 当buttonText改变时才重新渲染renderButton
);
useMemo返回的的是一个值,用于避免在每次渲染时都进行高开销的计算。
const result = useMemo(() => {
for (let i = 0; i < 100000; i++) {
(num * Math.pow(2, 15)) / 9;
}
}, [num]);
看一下官方文档是怎么描述两个的。
useMemo:
传入 useMemo 的函数会在渲染期间执行。
请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo。
useEffect
默认情况下,effect 会在每轮组件渲染完成后执行。这样的话,一旦 effect 的依赖发生变化,它就会被重新创建。
不比较了越比较越乱。以下两种情况用useMemo
其它情况用useEffect
就好
情况1:
let Test = () => {
let [num, setNum] = useState(0)
let [name] = useState('aa')
let addNum = () => {
setNum(num + 1)
}
let strToUpper = (str) => {
console.log("小写转大写了")
return str.toUpperCase()
}
return (
<div style={{ marginTop: '50px' }}>
<div>{name}的大写是:{strToUpper(name)}</div>
<div>num:{num}</div>
<Button type="primary" onClick={() => addNum()}>num加一</Button>
</div>
)
}
{strToUpper(name)}
只要dom元素一改变,重新渲染页面时就会执行,这种情况浪费资源要优化
//没用,虽然name没有变,useEffect没有触发,但是只要dom元素一改变,渲染时strToUpper还会执行
useEffect(() => {
console.log("执行了")
strToUpper(name)
}, [name])
//因为页面需要一个函数,你最后需要返回一个函数回去
let strToUpper = useMemo(() => {
console.log("小写转大写了")
return () => name.toUpperCase()
}, [name])
//或者
let upper=useMemo(() => {
console.log("小写转大写了")
return name.toUpperCase()
}, [name])
<div>{name}的大写是:{upper}</div>
感觉这样写就很操蛋,如果不是影响性能的(每次渲染都花很长时间),没必要为了用而用
第二种情况,上面那种很局限,如果多个字符串要转大写呢,难道要写多个useMemo
,这种情况下可以结合自定义hook使用
let useStrToUpper = (str) => useMemo(() => {
console.log("小写转大写了")
return str.toUpperCase()
}, [str])
<div>{name}的大写是:{useStrToUpper(name)}</div>
useEffect
let Test = () => {
let [num, setNum] = useState(0)
let [sum, setSum] = useState(0)
let addNum = () => {
setNum(num + 1)
}
let addSum = () => {
setSum(sum + 1)
}
useEffect(() => {
console.log("num改变了")
})
return (
<div style={{ marginTop: '50px' }}>
<div>num:{num}</div>
<Button type="primary" onClick={() => addNum()}>num加一</Button>
<Button type="primary" onClick={() => addSum()}>sum加一</Button>
</div>
)
}
没有依赖性时,当页面加载完成后,useEffect
会执行一次。后面无论是num还是sum只要改变,useEffect都会执行。
添加依赖项
useEffect(() => {
console.log("num改变了")
},[num])
添加依赖项后,页面刚加载完成后执行一次。后面只有当num改变后,才会执行。
useMemo
还是上面的例子,将useEffect
换成useMemo
当没有依赖项时(没有依赖项写空数组)只执行一次,后续不执行。
当添加依赖项时渲染时执行一次,后续当依赖项改变后才会执行。