“useContext想必大家都使用过,但是在使用的时候有没有想过它的性能问题?那么我们在编写useContext的时候如何减少重新渲染?本章中不会涉及React源码。”
1. useContext如何使用
首先在什么场景下会使用到useContext?
背景:React是单向数据流,如果组件之间有嵌套引用关系,且嵌套比较复杂(比如:组件A引用组件B,组件B引用组件C),当在没有状态管理的情况下,只能使用props一层一层往下传递,这样会使得维护变得很复杂。
此时React自带的useContext可以解决在没有状态管理的情况下,组件A传递值给组件C,而不需要通过中间的组件B。
接下来我们就来看看如何正确的使用useContext,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import React, { createContext, useContext } from 'react' export const MyContext = createContext('默认值111') const C = () => { const context = useContext(MyContext) return ( <div> <p>我是组件C</p> ---------------------- <p>从组件A传递给组件C的值:{context}</p> {/* 使用Consumer和useContext是等价的,只是使用useContext可读性更好 */} {/* <MyContext.Consumer> {(val) => <p>从组件A传递给组件C的值:{val}</p>} </MyContext.Consumer> */} </div> ) }
const B = () => { return ( <div> <p>我是组件B</p> ----------------------- <C /> </div> ) }
const A = () => { return ( <div> <p>我是组件A</p> ------------------------ {/* 外层没有包裹Provider时,会去取默认值:默认值111,否则会去取default A*/} <MyContext.Provider value={'default A'}> <B /> </MyContext.Provider> </div> ) } export default A
|
得到结果如下所示:

总结一下使用useContext步骤:
(1)在传递数据的组件中使用createContext创建一个context,并将其导出;
(2)在传递数据的组件中使用Provider包裹着子组件,并且使用value属性来传递数据;
(3)接受数据的组件导入定义的context,可以使用useContext或者Consumer(接收的是一个函数)来接收,这个也是我们叫的消费组件。
2. useContext如何减少重新渲染
在第一节中介绍了useContext的使用之后,我们再来看看一个问题:被Provider包裹的所有子组件里,只要调用了useContext,当context发生变化时就必然会重新渲染,那么这样会造成不必要的渲染开销问题。
接着我们就来看看如何解决这样的问题:拆分context,也就是说不要将所有的state状态都放到一个context下,比如我们可以将稳定的状态放到一个context下(UserContext2
),不稳定的状态放到一个context下(UserContext1
),代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import React, { createContext, useContext, useState, useMemo, memo } from 'react' const UserContext1 = createContext('default val1') const UserContext2 = createContext('default val2')
// 子组件需要配合memo使用来缓存对应的value值 const Child1 = memo(() => { const user1 = useContext(UserContext1) console.log('user1', user1) return null })
const Child2 = memo(() => { const user2 = useContext(UserContext2) console.log('user2', user2) return null })
const App = () => { const [value1, setValue1] = useState(1) const [value2, setValue2] = useState(2) // 如果要在value中传递一个对象引用类型,不能直接写对象, // 否则会重新渲染,使用的memo优化失效,需要配合useMemo使用 const modifyVal1 = useMemo(() => ({ value1, setValue1 }), [value1]) const modifyVal2 = useMemo(() => ({ value2, setValue2 }), [value2]) return ( <div> <button onClick={() => setValue1(value1 + 1)}>change value1</button> <button onClick={() => setValue2(value2 + 1)}>change value2</button> {/* 这里修改为value={{ value2, setValue2 }}*/} <UserContext2.Provider value={modifyVal2}> {/* 这里修改为value={{ value1, setValue1 }}*/} <UserContext1.Provider value={modifyVal1}> <Child1 /> <Child2 /> </UserContext1.Provider> </UserContext2.Provider> </div> ) }
export default App
|
这样分别点击change value1和change value2按钮时,在控制台只会打印对应的修改的context,比如点击change value1按钮,那么只会在控制台打印出如下图所示内容:

点击change value2按钮,会在控制台打印如下图所示内容:

当我们没有使用useMemo/memo来优化代码时,当点击change value1按钮时,控制台会将user1和user2都输出,控制台打印内容如下所示:

同理点击再点击change value2按钮时,控制台也会同时将user1和user2都输出,得到如下图所示代码:

注意:在拆分context的时候,代码需要配合useMemo和memo的使用,这样才可以有效的避免重新渲染的问题。
好了,今天只是简单的分享了一下useContext如何减少重新渲染的方法,如果你觉得对你有帮助的话,不妨点个赞再走图片图片图片。
再次感谢这篇文章的作者
useContext & useReducer