React hooks 使用注意点详解
部分参考资料:https://blog.csdn.net/lunahaijiao/article/details/125454338
useEffect
- count发生变化时执行副作用操作,可以将count作为依赖项传入数组中
- 每次count改变都会执行
- 在第一次渲染之后和每次更新之后都会执行,也可以是只有某些值发生变化之后执行,重点在于是每轮渲染结束后延迟调用( 异步执行 ),这是 useEffect 的好处,保证执行 effect 的时候,DOM 都已经更新完毕, 不会阻碍 DOM 渲染造成视觉阻塞。
副作用函数
useEffect用于处理组件的副作用。
- 第一个参数是一个回调函数,在里面进行业务逻辑代码的书写(副作用操作)
- 通常在副作用中进行ajax请求,事件的绑定与解绑,设置定时器与清除等等。
- 第二个参数是依赖项数组,指定副作用函数的触发条件。
- 如果不设置第二个参数,那么当该组件挂载和组件每渲染一次,副作用就会执行一次
- 如果数组中的依赖设置为空,那么只会在组件挂载和卸载时执行一次副作用。在 useEffect 的回调函数中,可以执行组件挂载时的操作,并在return返回的清理函数中执行组件卸载前的操作(例如:清除定时器,取消订阅)。
- 如果数组中存在依赖项,当组件挂载时和依赖项数组中的依赖发生变化,那么该副作用就会重新执行 ,在 useEffect 的回调函数中,return返回的清理函数在下一次 useEffect 执行前执行。
useLayoutEffect和 useEffect 的区别
- useLayoutEffect在组件渲染之前执行,dom还没有更新的时候
useLayoutEffect 的使用方法和 useEffect 相同,区别是他们的执行时机。
如上面所说,effect 的内容是会在渲染 DOM 之后执行,然而并非所有的操作都能被放在 effect 都延迟执行的,例如,在浏览器执行下一次绘制前,需要操作 DOM 改变页面样式,如果放在 useEffect 中执行,会出现闪屏问题。而 useLayoutEffect 是在浏览器执行绘制之前被同步执行,放在 useLayoutEffect 中就会避免这个问题。
这篇文章中可以清楚的看到上述例子的具体实现:useEffect 和 useLayoutEffect 的区别
- useLayoutEffect 也可以返回一个清理函数
- useLayoutEffect在浏览器重新绘制屏幕之前触发
useMemo
- 只要父组件的状态更新,无论有没有对子组件进行操作,子组件都会进行更新
- useMemo与memo的理念上差不多,都是判断是否满足当前的限定条件来决定是否执行callback函数,而useMemo的第二个参数是一个数组,通过这个数组来判定是否更新回调函数
- 其他State的更新引起了一个本来不需要更新的函数再次执行,
useMemo的好处:
- 可以减少不必要的循环和不必要的渲染
- 可以减少子组件的渲染次数
- 通过特地的依赖进行更新,可以避免很多不必要的开销,但要注意,有时候在配合 useState拿不到最新的值,这种情况可以考虑使用 useRef解决
- useMemo的值不一定是最新的值
- 如果依赖项没有变化函数不会执行
- 回调函数运行与否取决于依赖数组
useCallback
- useCallback与useMemo依赖项都为空时,useMemo函数不会调用,useCallback会调用,只是引用没有变
const Validate: React.FC = () => {
const [ab, setA] = useState(0)
const a = useMemo(() => {
console.log('meimo');
return ab
}, [])
const bb = useCallback(() => {
console.log('callback');
}, [])
return (
<>
<div>{ab}ab</div>
<div>{a}a</div>
<button onClick={() => setA(pre => ++pre)}>dsdffsd</button>
<button onClick={bb}>clalbal</button>
</>
);
};
export default Validate;
-
useCallback与useMemo可以说是一模一样,唯一不同的是
-
useMemo返回的是函数运行的结果,而useCallback返回的是函数
-
这个函数是父组件传递子组件的一个函数,防止做无关的刷新,其次,这个组件必须配合memo,否则不但不会提升性能,还有可能降低性能
-
函数式React.memo组件的props,React.memo会比较props的变化,所以用useCallBack缓存函数的引用,但是这个函数依然会执行,只是缓存地址
-
注意这里面的ts写法
useRef
- ref.current不能作为其它 Hooks 的依赖项,useEffect()监听不到ref.current的变化
- 可以获取当前元素的所有属性,拿到dom元素
- 缓存数据 useRef 可以拿到最新值
- useRef 能拿到最新的值但是不适合渲染,如果渲染的话,不会变化,不会引起组件的从新渲染
- 当你希望组件"记住"某些信息,但又不想让这些信息触发新的渲染时,你可以使用ref.
- 获取 DOM 元素或子组件的实例对象;
- 存储渲染周期之间共享的数据;eg: 记录组件渲染次数
相关代码
import { Button } from "antd"
import { useEffect, useRef, useState } from "react"
const Parent = () => {
useEffect(() => {
console.log('父组件别渲染了')
})
return <>
<div>paerent</div>
<Child />
</>
}
const Child = () => {
const [a, setA] = useState(0)
const reff = useRef(0)
reff.current++
useEffect(() => {
console.log('reff.current改变了')
}, [reff.current])
return <>
<div>{reff.current}</div>
<Button onClick={() => reff.current++}>dfsdf</Button>
<Button onClick={() => setA(pre => ++pre)}>usetate </Button>
</>
}
export default Parent
const MockMemo: React.FC<any> = () => {
const [count, setCount] = useState(4)
const [show, setShow] = useState(true)
const ref = useRef(6)
const inputRef = useRef<any>(null)
const add = useCallback(() => {
// setCount(count => count + 1)
// setCount(count => {
// console.log(count, 'dssdf');
// return count + 1
// })
// setCount(count + 1)
ref.current = ref.current + 4
console.log('useCallbackuseCallback', count, 'm', ref.current)
}, [show])
useEffect(() => {
console.log('ref.currentref.变化了');
}, [ref.current])
return (
<div>
<div style={{ display: 'flex', justifyContent: 'flex-start' }}>
<TestButton title="普通点击" onClick={() => { setCount(count + 1); }} />
<TestButton title="useCallback点击" onClick={add} />
<InputTest ref={inputRef}></InputTest>
</div>
<div>数字count {count}</div>
<div>数字ref{ref.current}</div>
<Button onClick={() => { setShow(!show); inputRef.current.focu() }}> 切换</Button>
</div>
)
}
const TestButton = React.memo((props: any) => {
console.log(props.title, 'meomeo')
return <Button type='primary' onClick={props.onClick} style={props.title === 'useCallback点击' ? {
marginLeft: 20
} : undefined}>{props.title}</Button>
})
const InputTest = React.forwardRef((props: any, ref: any) => {
const refinput = useRef<any>(null)
const focu = () => {
refinput.current.focus()
}
useImperativeHandle(ref, () => {
return {
focu
}
})
return <input type="text" ref={refinput} />
})