React Context 原理理解
Posted YuLong~W
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React Context 原理理解相关的知识,希望对你有一定的参考价值。
文章目录
Context
React提供了 Context 上下文模式,为了解决 props 每层都需要传递的问题,即Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
注意:提供者一定要是消费者的某一层父级
Context 相关用法
v16.3.0后,context api 正式发布
用 createContext
创建出一个 context 上下文对象,context 对象提供两个组件,Provider
和 Consumer
作为新的提供者和消费者,这种 context 模式,更便捷的传递 context
1、React.createContext
const ThemeContext = React.createContext(null) //创建context对象
const ThemeProvider = ThemeContext.Provider //提供者
const ThemeConsumer = ThemeContext.Consumer // 订阅消费者
- createContext 接受一个参数,作为初始化 context 的内容,返回一个Context 对象
- Context 对象上的 Provider 作为提供者,Context 对象上的 Consumer 作为消费者
2、提供者:Provider
const ThemeProvider = ThemeContext.Provider //提供者
export default function ProviderDemo()
const [ contextValue , setContextValue ] = React.useState( color:'#ccc', background:'pink' )
return <div>
<ThemeProvider value= contextValue >
<Son />
</ThemeProvider>
</div>
provider 作用:
- value 属性传递 context,供给 Consumer 使用。
- value 属性改变,ThemeProvider 会让消费 Provider value 的组件重新渲染。
Provider 特性总结:
-
Provider 作为提供者传递 context ,provider中value属性改变会使所有消费context的组件重新更新。
-
Provider可以逐层传递context,下一层Provider会覆盖上一层Provider。
3、消费者:Consumer
① 类组件之contextType 方式
const ThemeContext = React.createContext(null)
// 类组件 - contextType 方式
class ConsumerDemo extends React.Component
render()
const color,background = this.context
return <div style= color,background >消费者</div>
ConsumerDemo.contextType = ThemeContext
const Son = ()=> <ConsumerDemo />
- 类组件的静态属性上的 contextType 属性,指向需要获取的 context( demo 中的 ThemeContext ),就可以方便获取到最近一层 Provider 提供的 contextValue 值
② 函数组件之 useContext 方式
const ThemeContext = React.createContext(null)
// 函数组件 - useContext方式
function ConsumerDemo()
const contextValue = React.useContext(ThemeContext)
const color,background = contextValue
return <div style= color,background >消费者</div>
const Son = ()=> <ConsumerDemo />
- useContext 接受一个参数,就是 context 对象 ,返回一个 value 值,就是最近的 provider 提供 contextValue 值
③ 订阅者之 Consumer 方式
const ThemeConsumer = ThemeContext.Consumer // 订阅消费者
function ConsumerDemo(props)
const color,background = props
return <div style= color,background >消费者</div>
const Son = () => (
<ThemeConsumer>
/* 将 context 内容转化成 props */
(contextValue)=> <ConsumerDemo ...contextValue />
</ThemeConsumer>
)
- Consumer 订阅者采取 render props 方式,接受最近一层 provider 中value 属性,作为 render props 函数的参数,可以将参数取出来,作为 props 混入
ConsumerDemo
组件,说白了就是 context 变成了 props。
总结:在 Provider 里 value 的改变,会使引用①contextType
,②useContext
消费该 context 的组件重新 render ,同样会使 ③Consumer 的 children
函数重新执行,与前两种方式不同的是 Consumer 方式,当 context 内容改变的时候,不会让引用 Consumer 的父组件重新更新。
问题1:如何阻止 Provider value 改变造成的 children 不必要的渲染?
- ① 第一种就是利用 memo,pureComponent 对子组件 props 进行浅比较处理。
const Son = React.memo(()=> <ConsumerDemo />)
- ② 第二种就是 React 本身对 React element 对象的缓存。React 每次执行 render 都会调用 createElement 形成新的 React element 对象,如果把 React element 缓存下来,下一次调和更新时候,就会跳过该 React element 对应 fiber 的更新。
<ThemeProvider value= contextValue >
React.useMemo(()=> <Son /> ,[])
</ThemeProvider>
问题2:context 与 props 和 react-redux 的对比?
context 相比较 props 解决了:
- 解决了 props 需要每一层都手动添加 props 的缺陷
- 解决了改变 value ,组件全部重新渲染的缺陷
react-redux 就是通过 Provider 模式把 redux 中的 store 注入到组件中的
Context 原理
1、Context对象
通过 createContext
创建一个 context 。
Provider
本质上是一个 element 对象 $$typeof ->REACT_PROVIDER_TYPE
Consumer
本质上也是一个 element 对象 $$typeof ->REACT_CONTEXT_TYPE
_currentValue
这个用来保存传递给 Provider 的 value 。
2、Provider 提供者
Provider 本质上是一个特殊的 React Element 对象
jsx -> element -> fiber 关系:
<Provider>
->REACT_PROVIDER_TYPE
React element ->ContextProvider fiber
在 fiber 调和阶段会进入到 beginWork
流程:
- 如果当前类型的 fiber 不需要更新,那么会
FinishedWork
中止当前节点和子节点的更新。 - 如果当前类型 fiber 需要更新,那么会调用不同类型 fiber 的处理方法。
updateContextProvider
方法
1、updateContextProvider
-
调用
pushProvider
,会获取 type 属性上的 _context 对象,将 Provider 的 value 属性,赋值给 context 的 _currentValue 属性上。 -
通过
calculateChangedBits
计算出 changedBits,当它返回的changedBits === 0
的时候,那么还会浅比较 children 是否发生变化和有没有legacy context
,不需要更新,那么会 return 停止向下调和子节点 -
否则,context改变,调用
propagateContextChange
进行更新。
问:Proider 通过什么手段传递 context value?
答:即通过挂载 context 的 _currentValue 属性
。
2、propagateContextChange
-
深度遍历所有的子代 fiber ,然后找到里面具有
dependencies
的属性。 -
对比 dependencies 中的 context 和当前 Provider 的 context 是否是同一个,如果是,如果当前 fiber 是类组件,绑定一个
forceUpdate
标识 。 -
然后会提高 fiber 的更新优先级,让 fiber 在接下来的调和过程中,处于一个高优先级待更新的状态。
-
找到当前 fiber 向上的父级链上的 fiber ,统一更新他们的优先级,使之变成高优先级待更新状态。
-
对高优先级的fiber进行
beginWork
dependencies 属性
,这个属性可以把当前的 fiber 和 context 建立起关联,是一个链表结构,将 fiber 对应的 context 存放在 dependencies 中。
注意:一个 fiber 可以有多个 context 与之对应。
问题1、什么情况下 fiber 存在 dependencies—— 即 什么情况下使用 context
- 有
contextType
静态属性指向的类组件 - 使用
useContext
hooks 的函数组件 - context 提供的
Consumer
问题2、 为什么class 组件会创建一个 ForceUpdate 的 update
context 要突破这些控制,就要做到当 value 改变,消费 context 的类组件更新,则需要通过 forceUpdate 强制更新。通过 PureComponent
和 shouldComponentUpdate
等层层阻碍,解决了类组件更新限制。
3、beginWork
更新流程
React 会从 rootFiber 开始更新,每一个更新 fiber 都会走 beginWork
流程,开始找不同,找到有没有需要更新的地方,其中一个重要的寻找指标就是 更新的优先级。
父子节点中形成了一条 root fiber -> 父 fiber -> 子 fiber 的 beginWork
链
- 第一种: 如果遇到组件,而且 更新不涉及当前组件,也不在当前组件的父子递归链上,那么就不会 render,也不会向下 beginWork 。
- 第二种: 如果遇到组件,而且 更新不涉及当前组件,但是更新组件属于当前组件的子孙后代,那么不会 render,但是会向下 beginWork ,目的很明确,找到对应的更新组件。
- 第三种: 如果遇到 其他类型的 fiber 比如 hostComponent
<div>
,那么会对比当前的更新优先级,如果低优先级,那么不需要向下 beginWork 。反之向下 beginWork。
总结流程如下:
- 如果一个组件发生更新,那么当前组件到 fiber root 上的父级链上的所有 fiber ,更新优先级都会升高,都会触发 beginwork 。
- render 不等于 beginWork,但是 render 发生,一定触发了 beginwork 。
- 一次 beginwork ,一个 fiber 下的同级兄弟 fiber 会发生对比,找到任务优先级高的 fiber 。向下 beginwork 。
Provider 原理:Provider
更新,会递归所有的子组件,只要消费了 context 的子代 fiber ,都会给一个高优先级。而且向上更新父级 fiber 链上的优先级,让所有父级 fiber 都处于一个高优先级。那么接下来高优先级的 fiber 都会 beginWork 。
多个 Provider 嵌套
如果有多个 Provider 的情况,那么后一个 contextValue 会覆盖前一个 contextValue,在开发者脑海中,要有一个定律就是:Provider
是用来传递 value,而非保存 value 。
3、Consumer 消费者
Consumer 本质上是类型为 REACT_CONTEXT_TYPE
的 element 对象。在调和阶段,会转化成 ContextConsumer
类型的 fiber 对象。
在 beginwork 中,会调用 updateContextConsumer
方法。
1、updateContextConsumer
- 首先调用
readContext
获取最新的 value 。 - 然后通过
render props
函数,传入最新的 value,得到最新的children
。 - 接下来 调和 children 。
fiber 上的 dependencies 如何和 context 建立起关联。
2、readContext
- 首先会创建一个
contextItem
。 - fiber 上会存在多个
dependencies
,它们以链表的形式联系到一起,如果不存在最后一个context dependency
, context dependencies 为空 ,那么会创建第一个 dependency 。 - 如果存在 最后一个
dependency
,那么 contextItem 会以链表形式保存,并变成最后一个 lastContextDependency 。
useContext 和 contextType
1、useContext
原理,调用 useContext 本质上调用 readContext
方法。
- 函数组件通过 readContext ,将函数组件的
dependencies
和当前 context 建立起关联,context 改变,将当前函数组件设置高优先级,促使其渲染。
2、类组件的静态属性 contextType
,本质上就是调用 readContext
方法。
- 静态属性 contextType ,在类组件实例化的时候被使用,本质上也是调用
readContext
将 context 和 fiber 上的dependencies
建立起关联。
Context 原理
- Provider 传递流程:Provider 的更新,会 深度遍历子代 fiber,消费 context 的 fiber 和父级链都会 提升更新优先级。 对于类组件的 fiber ,会 forceUpdate 处理。接下来所有消费的 fiber,都会
beginWork
。 - Context 订阅流程:
contextType
,useContext
,Consumer
会内部调用readContext
,readContext 会把 fiber 上的 dependencies 属性 和 context 对象 建立起关联。
总结
1、Context的用法
2、Context的原理
以上是关于React Context 原理理解的主要内容,如果未能解决你的问题,请参考以下文章
使用react context实现一个支持组件组合和嵌套的React Tab组件