此文章不适合没hooks经验的同学,新手可以先看这个
一、useContext
1.1 React.createContext
createContext 能够创建一个 React 的 上下文(context),然后订阅了这个上下文的组件中,可以拿到上下文中提供的数据或者其他信息(也就是value对象)。
1.2 useContext
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
🍊🍐一:
import React,{useState,createContext,useContext, memo, useMemo} from 'react';
const ParentContext = createContext();
export default function Parent(){
const [show, setShow] = useState(false)
return (
<React.Fragment>
<ParentContext.Provider value={setShow}>
<Child></Child>
</ParentContext.Provider>
<Other show={show} setShow={setShow}></Other>
</React.Fragment>
)
}
function Child(props){
// O === setShow 方法
const O = useContext(ParentContext)
console.log('Child 组件执行了');
return (
<button onClick={()=>{
O(true)
}}>Child</button>
)
}
function Other(props){
const { show, setShow} = props
return (
show ? <button onClick={()=>{setShow(false)}}>Other</button> : ''
)
}
这样只是简单的把功能实现,注意这里会有一个问题,Parent每次组件的执行都会引发子组件的渲染。这是因为父组件更新子组件也会执行。
二、useContext性能优化
2.1 为了解决以上组件的开销较大问题,可以使用memoization 来优化。
*memoization定义:如果函数组件在给定相同 props 的情况下,那么可以通过 React.memo将其包装,以此记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
🍊🍐:
import React,{useState,createContext,useContext, memo, useMemo} from 'react';
const ParentContext = createContext();
export default function Parent(){
const [show, setShow] = useState(false)
return (
<React.Fragment>
<ParentContext.Provider value={setShow}>
<Child></Child>
</ParentContext.Provider>
<Other show={show} setShow={setShow}></Other>
</React.Fragment>
)
}
const Child = memo(function Child(props){
const O = useContext(ParentContext)
console.log('Child 组件执行了');
last = O;
return (
<button onClick={()=>{
O(true)
}}>Child</button>
)
})
const Other = memo(
function Other(props){
const { show, setShow} = props
return (
show ? <button onClick={()=>{setShow(false)}}>Other</button> : ''
)
}
)
真的解决了吗?继续看👀
接着修改如下,这时候再去看控制台,是不是心里mmp了,Child又莫名重新渲染了,是为什么呢?
import React,{useState,createContext,useContext, memo, useMemo} from 'react';
const ParentContext = createContext();
export default function Parent(){
const [show, setShow] = useState(false)
return (
<React.Fragment>
<ParentContext.Provider value={{setShow}}>
<Child></Child>
</ParentContext.Provider>
<Other show={show} setShow={setShow}></Other>
</React.Fragment>
)
}
let last;
const Child = memo(function Child(props){
const {setShow} = useContext(ParentContext)
console.log('Child 组件执行了');
console.log('last === setShow', last === setShow) //
last = setShow;
return (
<button onClick={()=>{
setShow(true)
}}>Child</button>
)
})
const Other = memo(
function Other(props){
const { show, setShow} = props
return (
show ? <button onClick={()=>{setShow(false)}}>Other</button> : ''
)
}
)
官方说明:
React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState 或 useContext 的 Hook,当 context 发生变化时,它仍会重新渲染。可以理解为useContext可以击穿一切即使是memo。
大概原理:ParentContext.Provider 属性value我改变成了一个对象,当每次执行setShow方法,Parent都会重新渲染,value这个对象都会重新生成,由于React是Object.is()浅比较,即useContext(ParentContext)返回的对象每次都是不等的(可以在Child组件打印看看),所以又重新渲染Child,造成不必要的开销。
既然有问题,那怎么解决呢?接下来大招来了。🤭🤭🤭
useMemo上场:
export default function Parent(){
const [show, setShow] = useState(false)
// 只需要修改这里通过useMemo包装,保证每次返回的对象不变,deeps为[],只在首次挂载执行,所以就保证了每次value都是同一个。
const ctxValue = useMemo(() => ( {setShow }), [])
return (
<React.Fragment>
<ParentContext.Provider value={ctxValue}>
<Child></Child>
</ParentContext.Provider>
<Other show={show} setShow={setShow}></Other>
</React.Fragment>
)
}
三、 总结
useContext使用很简单,但是想用好还是有很多麻烦的,除非不关心性能。- 子组件使用memo做记忆缓存。
- 使用
useMemo包装,控制返回contextValue对象根据依赖deep改变。
如果有错误,欢迎指正,多多讨论。