在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props
属性在每一层传递属性,contextAPI
应用而生。
如果在你的项目中使用主题,基本是每个组件都需要;或者你在项目中使用多语言,也是每个组件都需要支持,这都是典型的可以通过
context
操作的例子
我们实现一个多个组件,共享同一个颜色的示例,通过按钮点击切换颜色,如下:
我们有五个部分,外层的 pannel
组件,header
组件,title
组件,main
组件还有 content
组件。我们在随便一层组件中执行 color
切换函数,因为 setColor
方法已经通过 context
传递进去了。样式很简单,代码如下:
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
// 创建上下文对象
const ColorContext = React.createContext()
const style = {
margin: '5px',
padding: '5px'
}
// 我们使用不同的方式创建组件
function Title() {
return (
// 函数组件使用方式,children 是一个函数
<ColorContext.Consumer>
{
(contextValue) => {
return <div style={{border: `5px solid ${contextValue.color}`}}>title</div>
}
}
</ColorContext.Consumer>
)
}
class Header extends React.Component {
// 类组件绑定静态方法,默认给实例绑定 context 属性
static contextType = ColorContext
render() {
// 也可以使用 comsumer 组件
return (
<div style={{border: `5px solid ${this.context.color}`}}>header <Title /></div>
)
}
}
function Content() {
return (
<ColorContext.Consumer>
// 内部是函数形式
{
(contextValue) => {
return (
<>
// 我们在这里控制颜色的改变
<div style={{border: `5px solid ${contextValue.color}`}}>Content</div>
<button onClick={() => {contextValue.changeColor('red')}}>red</button>
<button onClick={() => {contextValue.changeColor('green')}}>green</button>
</>
)
}
}
</ColorContext.Consumer>
)
}
class Main extends React.Component {
// 类组件获取方式
static contextType = ColorContext
render() {
return (
<div style={{border: `5px solid ${this.context.color}`}}>main <Content /></div>
)
}
}
class Panel extends React.Component {
constructor(props) {
super(props)
this.state = {
color: 'black'
}
}
changeColor = (color) => {
this.setState({color})
}
render() {
const contextValue = {
color: this.state.color,
changeColor:this.changeColor
}
return (
// 通过value向下传递
<ColorContext.Provider value={contextValue}>
<div style={{...style, width: '250px', border: `5px solid ${this.state.color}`}}>
Panel
<Header />
<Main />
</div>
</ColorContext.Provider>
)
}
}
由上面可知,Provider
组件是一个提供数据的,数据存放在 value
中。Consumer
组件和 contextType
是消费数据的。而组件我们之前也实现过,更具不同的类型, 单独使用方法处理。
定义类型:
// src/constants.js
export const REACT_PROVIDER = Symbol('react.provider')
// context 和 consumer 都是 context 类型,小伙伴们可自行打印官方的库查看
export const REACT_CONTEXT = Symbol('react.context')
React
中有个 createContext
方法:
// src/react.js
// 我们的写法效仿的是我们使用官方库打印出来的结果
function createContext() {
const context = {
$$typeof: REACT_CONTEXT,
_currentValue: undefined, // 值是绑定在 context 中的 _currentValue 属性上
}
// 这里使用了递归引用,你中有我我中有你
context.Provider = {
$$typeof: REACT_PROVIDER,
_context: context
}
context.Consumer = {
$$typeof: REACT_CONTEXT,
_context: context
}
return context
}
const React = {
...
createContext
}
对于内容渲染我们要分两种情况考虑,一个是直接渲染的情况,一个是更新渲染的情况。第一种是 createDOM
中判断处理,第二种是在 updateElement
中处理,当然还有 forceUpdate
更新方法。
// src/react-dom.js createDOM
...
if (type && type.$$typeof === REACT_PROVIDER) {
return mountProviderComponent(vdom)
} else if (type && type.$$typeof === REACT_CONTEXT) {
return mountContextComponent(vdom)
}
...
function mountProviderComponent(vdom) {
const {props, type} = vdom
const context = type._context // Provider._context
context._current = props.value // 通过value属性提供值
const renderVdom = props.chidlren
vdom.oldRenderVdom = renderVdom
return createDOM(renderVdom) // 递归处理子,这里限制了一个根子节点
}
funtion mountContextComponent(vdom) {
const {props, type} = vdom
const context = type._context// Consomer._context
const renderVdom = props.children(context._currentValue) // consumer 组件子是一个函数,这里的值上一步provider 已经赋值了,引用类型
vdom.oldRenderVdom = renderVdom
return createDOM(renderVdom)
}
类组件需要处理 contextType
静态方法
// src/react-dom.js mountClassComponent
...
if (type.contextType) {
// 添加 context 属性
classInstance.context = type.contextType._currentValue
}
...
这里可能有朋友有疑问,为什么
type
一会这样,一会这么判断。这是babel
对jsx
解析的结果,typeof type === string
, 就是我们正常的html
标签。如果是函数类型的话,可能是类组件或者函数组件。在这里type
就指代的Provider
和Consumer
对象,需要具体情况具体分析。
这里一个 ColorContext
只能处理颜色的逻辑,如果还有其他的共享逻辑怎么办呢?我们可以对 Provider
和 Consumer
进行多层嵌套,使用方法是一样的。因为子也是递归处理,再根据类型找到对应的处理函数。如果使用的组件在不同的页面,我们需要把 ColorContext
进行导出,文件中自行引入。
react
更新函数即 diff
对比,同级对比,类型一样的话在比对子,同样需要对类型进行判断
// src/react-dom. updateElement
...
if (oldVdom.type.$$typeof === REACT_PROVIDER) {
updateProviderComponent(oldVdom, newVdom)
} else if (oldVdom.type.$$typeof === RERACT_CONTEXT) {
updateContextComponent(oldVdom, newVdom)
}
...
function updateProviderComponent(oldVdom, newVdom) {
const currentDOM = findDOM(oldVdom)
const parentDOM = currentDOM.parentNode
// 下面的操作和 mount 类型,只是加了对比
const {type, props} = newVdom
const context = type._context
const renderVdom = props.children
context._currentValue = props.value
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom)
newVdom.oldRenderVdom = renderVdom
}
function updateContextComponent(oldVdom, newVdom) {
const currentDOM = findDOM(oldVdom)
const parentDOM = currentDOM.parentNdoe
const {type, props} = newVdom
const context = type._context
const renderVdom = props.children(context._currentValue)
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom)
newVdom.oldRenderVdom = renderVdom
}
点击触发 changeColor
方法,促使 render
函数重新执行,我们要在 forceUpdate
中也判断类组件的字段
// src/Component.js
forceUpdate() {
...
let oldDOM = findDOM(oldREnderVdom)
// 更新时重新获取 context, 可能value 值更新了
if (this.constructor.contextType) {
this.context = this.constructor.contextType._currentValue
}
...
}
我们自己实现的效果如下:
本节也是代码为主,但是中间穿插文字的描述,我相信大家可以理解 context
的机制和产生的原因。下一下小节我们学习下 react
中的高阶组件。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有