写在前面
本篇博客接着前面的 React Hooks 全解(一) 继续介绍 Ract Hooks。
1. useMemo && useCallback
React 框架是通过不断地 render 来得到不同的虚拟 DOM ,然后进行 DOM Diff 来进行页面 DOM 的选择性更新的,因此,在每次的 render 之后都会短时间内存在新旧两个虚拟 DOM 。
对于组件内包含子组件的情况,当父组件内触发 render 时,就算子组件依赖的 props 没有变化,子组件也会因为父组件的重新 render 再次 render 一遍。这样就产生了不必要的 render 。
为了解决不必要的 render ,React 提供了 React.memo()
接口来对子组件进行封装。如下:
function App(){
const [n, setN] = useState(0)
const [m, setM] = useState(0)
return (
<div>
我是父组件
n: {n}
<button onClick={()=>setN(n+1)}>n+1</button>
<button onClick={()=>setM(m+1)}>m+1</button>
<Child value={m}/> //这样当子组件依赖的 m 值没有变化时,子组件就不会重新 render
</div>
)
}
const Child = React.memo((props)=>{
useEffect(()=>{
console.log('子组件 render 了')
})
return (
<div>我是子组件,我收到来自父组件的值为:m {props.value}</div>
)
})
但是上述方式存在 bug,因为 React.memo 在判断子组件依赖的属性有没有发生改变时仅仅是做的前后值是否相等的比较,如果子组件从父组件处接收的依赖是一个对象的话,比较的就会是对象的地址,而不是对象里面的内容,因此在每次父组件重新 render 后得到的会是不同地址的对象,尽管对象里面的值没有更新,但是子组件发现地址变了也会重新 render。
为了解决这个问题,就又出来了 useMemo()
Hook,useMemo 是用于在新旧组件交替时缓存复用一个函数或者一个对象,当某个依赖重新变化时才重新生成。
useMemo Hook 接收一个无参数的返回函数(或对象)的函数。并且 useMemo 必须有个依赖,告诉其在什么时候重新计算。有点类似于 Vue 的计算属性的原理。如下:
function App(){
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const onClickChild = useMemo(()=>{
return () => {
console.log(m)
}
},[m])
return (
<div>
我是父组件
n: {n}
<button onClick={()=>setN(n+1)}>n+1</button>
<button onClick={()=>setM(m+1)}>m+1</button>
<Child value={m} onClick = {onClickChild}/>
</div>
)
}
const Child = React.memo((props)=>{
useEffect(()=>{
console.log('子组件 render 了')
})
return (
<div>
我是子组件,我收到来自父组件的值为:m {props.value}
<br/>
<button onClick={props.onClick}>click</button>
</div>
)
})
useCallback()
是 useMemo 的语法糖,因为 useMemo 是接收一个没有参数的返回函数(或对象)的函数,会有些奇怪,因此提供了 useCallback 来直接接收函数或对象。
const onClickChild = useMemo(() => {
console.log(m)
},[m])
2. useInperativeHandle
useInperativeHandel 是和 ref 相关的一个 Hook。
我们知道,ref 属性是会将当前的组件实例或 原生DOM 直接赋值给传入的 Ref 对象的 current 属性上,而且函数组件不能接收 ref 属性,因为函数组件没有实例。但是如果函数组件经过 React.forwardRef() 封装过后 可以接收 ref,一般情况下,这个 ref 是访问的经过函数组件转发过后的 原生DOM,但是,如果在函数组件内不仅仅是想让外来的 ref 指向一个 原生DOM 呢?可不可以让函数组件的 ref 像类组件中的 ref 指向实例一样拥有更多的可控性操作呢?React 就为函数组件提供了一种封装返回的 ref 指向的对象的方法,就是 useInperativeHandle Hook。
2.1 举个例子
function App(){
const myRef = useRef(null)
useEffect(()=>{
console.log(myRef.current.real)
console.log(myRef.current.getParent())
}, [])
return (
<div>
我是父组件
<Child ref={myRef}/>
</div>
)
}
const Child = forwardRef((props, ref)=>{
const childRef = useRef(null)
useImperativeHandle(ref, ()=>{
return {
real: childRef.current,
getParent(){
return childRef.current.parentNode
}
}
})
return (
<div>
我是子组件,我有一个子DOM
<button ref={childRef}>按钮</button>
</div>
)
})
3. 自定义 Hook
自定义 Hook 就是自定义一个函数,这个函数必须以 use
开头,并且,该函数里必须用到原生的 Ract Hooks,返回值一般是一个数组或一个对象,用于暴露该 Hooks 的读写接口。
自定义 Hook 通常是将函数组件中多次用到的 hook 整合到一起,尽量在函数组件中不要出现多次 hook 操作。
暂时没想到合适的例子,待补充。