React16源码: context用法与createContext源码实现
2024-01-01 15:43:19
context
1 )概述
- 在react的设计中,组件与组件之间的通信通常是
- 父组件通过 props 给子组件传递子组件需要的属性
- 父组件通过传递一些回调函数给子组件
- 让子组件在某些特定的时候,可以调用一些父组件的特性
- 这种情况,会存在一个问题
- 就是react的应用中组件和组件之间并不一定只有父子关系
- 还会存在着像父子嵌套多层之后,第一层和最下层的组件
- 他们之间是一个主孙的一个关系
- 他们中间会隔着好几层不同的组件,如果通过props进行一个传递,是不太现实的
- 还有就是中间的那几层组件,不一定是我们自己写的
- 中间的组件要去传递这个 props,其实是完全没有意义的事情
- 所以,react 提供了一个 context 的一个使用方式
- 在上级组件中,我们提供了一个 context 对象之后
- 只要是在它下面渲染的组件都可以通过 context 这个属性去获取到它提供的这部分内容
- 以此达到一个跨越多层组件传递信息的一个功能
- context 有两种实现方式
- 第一种,是通过老的 context 的 API 叫做
childContextTypes
- 老的
childContextTypes
在react17 这个大版本发布的时候被废弃 - 历史原因,用的还挺多
- 老的
- 第二种, 是通过新版提供的
createContext
这个API- 这块下面会分析下源码
- 第一种,是通过老的 context 的 API 叫做
用法示例
1 )示例演示
这个示例,演示了 新旧 两个 api 的用法
import React from 'react'
import PropTypes from 'prop-types'
const { Provider, Consumer } = React.createContext('default')
// 定义一个父组件 作为 最外层
class Parent extends React.Component {
state = {
childContext: '123',
newContext: '456',
}
// react 静态方法 api
getChildContext() {
return { value: this.state.childContext, a: 'aaaaa' }
}
render() {
return (
<>
<div>
<label>childContext:</label>
<input
type="text"
value={this.state.childContext}
onChange={e => this.setState({ childContext: e.target.value })}
/>
</div>
<div>
<label>newContext:</label>
<input
type="text"
value={this.state.newContext}
onChange={e => this.setState({ newContext: e.target.value })}
/>
</div>
{/* 基于 Provider来传递 */}
<Provider value={this.state.newContext}>{this.props.children}</Provider>
</>
)
}
}
// 定义第二个父组件 作为 中间层
class Parent2 extends React.Component {
// { value: this.state.childContext, a: 'bbbbb' }
getChildContext() {
return { a: 'bbbbb' }
}
render() {
return this.props.children
}
}
// 定义第一个子组件 内部使用 Consumer
function Child1(props, context) {
console.log(context)
return <Consumer>{value => <p>newContext: {value}</p>}</Consumer>
}
// 声明子组件需要的 props
Child1.contextTypes = {
value: PropTypes.string,
}
// 定义第二个子组件
class Child2 extends React.Component {
render() {
return (
<p>
childContext: {this.context.value} {this.context.a}
</p>
)
}
}
// Child2.contextType = Consumer
// 声明 子组件2 需要的 props
Child2.contextTypes = {
value: PropTypes.string,
a: PropTypes.string,
}
// 父组件不声明,子组件无法获取 props
Parent.childContextTypes = {
value: PropTypes.string,
a: PropTypes.string,
}
// 父组件不声明,子组件无法获取 props
Parent2.childContextTypes = {
a: PropTypes.string,
}
// 最终的组件树,不同组件的嵌套
export default () => (
<Parent>
<Parent2>
<Child1 />
<Child2 />
</Parent2>
</Parent>
)
2 )关于 childContextTypes 旧版API的说明
-
主要以 Child2 组件来说明
-
上级组件中声明这个 getChildContext 这个方法
-
然后return的这个对象, 就是作为子组件当中能够获取这个 context 的对象
-
但是有一点必须要注意,就是父组件必须要声明
childContextTypes
, 即: Parent.childContextTypesParent.childContextTypes = { value: PropTypes.string, a: PropTypes.string, }
-
上层组件是必须要声明的
-
如果不声明,它提供的这个 context ,子组件是无法获取到的
-
想要获取上层组件提供的 context,需要在子组件 Child2 当中
-
声明自己需要的 contextTypes, 例如
Child2.contextTypes = { value: PropTypes.string, a: PropTypes.string, }
-
它的内容也是跟上层组件的
childContextTypes
是一样 -
两者区别是: 有或没有子组件
-
在这个渲染的过程中,比如这个 Child2 组件,希望获取父层组件中提供的 context 里面的某几个属性
-
Chid 自己就需要去声明使用几个属性,为何这么做呢
- 在react当中,它的上层组件不一定只有一个
- 它上层组件中提供的 context 也不一定只有一个
- 它们是会有一个 merge 的一个过程的
- 所以在很多属性中 react 要知道你想要获取上层组件当中提供的context里面的哪几个属性
- 所以要通过这种方式进行一个声明
3 )关于 createContext 新版API的分析
- 通过
react.createContext
,它返回了一个对象const { Provider, Consumer } = React.createContext('default')
- 这个对象里面包含了
Provider
和Consumer
,是一个 context 提供方和 context 的订阅方 - 这两个都是组件,我们通过在 Parent 这边我们提供了Provider, 然后上面指定 value
<Provider value={this.state.newContext}>{this.props.children}</Provider>
- 这个 value 就是 context,即提供的context的属性信息
- 在它的子树下面,只需要在想要用到context的地方
- 通过这个
Consumer
组件 (它传入的是一个回调方法) - 这个方法是一个function Component,它接收它的一个value
- 并且把这个想要渲染的东西渲染出来就可以了
// 声明子组件需要的 props Child1.contextTypes = { value: PropTypes.string, }
- 所以
Provider
和Consumer
是一一对应的关系 - 在上层组件里面定义之后,子组件里面你想要在哪个地方用到这个属性
- 你再去专门用这个组件去进行一个渲染就可以了
4 )为什么要弃用老的API要改成这种新的API
- 因老的API它对于context提供方,它下层的所有组件的影响太大了
- 它会导致它下层的所有组件, 即便在没有任何更新的情况下,它每一次更新的过程当中
- 仍然要进行完整的渲染,所以对性能的损耗会非常大
createContext 源码分析
定位到 reactContext.js
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
import type {ReactContext} from 'shared/ReactTypes';
import warningWithoutStack from 'shared/warningWithoutStack';
import warning from 'shared/warning';
export function createContext<T>(
defaultValue: T,
calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
if (calculateChangedBits === undefined) {
calculateChangedBits = null;
} else {
if (__DEV__) {
warningWithoutStack(
calculateChangedBits === null ||
typeof calculateChangedBits === 'function',
'createContext: Expected the optional second argument to be a ' +
'function. Instead received: %s',
calculateChangedBits,
);
}
}
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
_calculateChangedBits: calculateChangedBits,
// As a workaround to support multiple concurrent renderers, we categorize
// some renderers as primary and others as secondary. We only expect
// there to be two concurrent renderers at most: React Native (primary) and
// Fabric (secondary); React DOM (primary) and React ART (secondary).
// Secondary renderers store their context values on separate fields.
_currentValue: defaultValue,
_currentValue2: defaultValue,
// These are circular
Provider: (null: any),
Consumer: (null: any),
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
let hasWarnedAboutUsingNestedContextConsumers = false;
let hasWarnedAboutUsingConsumerProvider = false;
if (__DEV__) {
// A separate object, but proxies back to the original context object for
// backwards compatibility. It has a different $$typeof, so we can properly
// warn for the incorrect usage of Context as a Consumer.
const Consumer = {
$$typeof: REACT_CONTEXT_TYPE,
_context: context,
_calculateChangedBits: context._calculateChangedBits,
};
// $FlowFixMe: Flow complains about not setting a value, which is intentional here
Object.defineProperties(Consumer, {
Provider: {
get() {
if (!hasWarnedAboutUsingConsumerProvider) {
hasWarnedAboutUsingConsumerProvider = true;
warning(
false,
'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' +
'a future major release. Did you mean to render <Context.Provider> instead?',
);
}
return context.Provider;
},
set(_Provider) {
context.Provider = _Provider;
},
},
_currentValue: {
get() {
return context._currentValue;
},
set(_currentValue) {
context._currentValue = _currentValue;
},
},
_currentValue2: {
get() {
return context._currentValue2;
},
set(_currentValue2) {
context._currentValue2 = _currentValue2;
},
},
Consumer: {
get() {
if (!hasWarnedAboutUsingNestedContextConsumers) {
hasWarnedAboutUsingNestedContextConsumers = true;
warning(
false,
'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' +
'a future major release. Did you mean to render <Context.Consumer> instead?',
);
}
return context.Consumer;
},
},
});
// $FlowFixMe: Flow complains about missing properties because it doesn't understand defineProperty
context.Consumer = Consumer;
} else {
context.Consumer = context;
}
if (__DEV__) {
context._currentRenderer = null;
context._currentRenderer2 = null;
}
return context;
}
- 定位到
createContext
, 它接收的是一个 defaultValue 和 calculateChangeBits方法- calculateChangeBits方法是用来计算新老context它们的一个变化的
- 在这里,它声明了一个context对象, 这个对象跟之前的 ReactElement 非常的像
- 它有一个
$$typeof
, 这里的$$typeof
跟 ReactElement 里面的$$typeof
是不一样的
- 它有一个
- 下面有这两个属性 _currentValue, _currentValue2
- 这两个属性它们的用处是一样的,使用的地方会不一样,比如说,不同平台里面会不一样
- 它的 _currentValue 是用来记录 Provider上面提供的这个 value
- 在有变化的情况下,它就会更新到这个 _currentValue 上面,这就是用来记录最新的context的值的
- 下面会有一个 Provider和 Consumer
- 然后再接下去, 有一个 context.Provider 这个对象
context.Provider = { $$typeof: REACT_PROVIDER_TYPE, _context: context, };
- 这个_context指向这个context的对象
- 忽略DEV相关的判断代码
- 在最后我们看到
context.Consumer = context
- 也就是说 Consumer是指向这个对象自己的
- 在Consumer进行渲染的时候,它要去获取这个value
- 直接从它本身上面去获取到这个 _currentValue
- 就可以拿到最新的 context 的值了,然后再调用Consumer 的回调方法把它传进去
- 就可以渲染出最新的内容
- 所以这就是它的一个基本的实现原理。
- 整个context 新的contextAPI的一个源码不是特别复杂,但是整个通信过程并不止这一点
- 在这里,主要是弄清楚 Provider 和 Consumer 的关系
- 需要注意的是
- 返回的这个 Provider 和 Consumer,它们里面都有
$$typeof
- 它们并不是用替代 ReactElement 里面的
$$typeof
- 因为它返回的这个对象是整体, 是作为 ReactElement 里面的 type 属性去存储的
- 所以跟这边的
$$typeof
是完全没有任何关系的 - 也就是说 type 里面它还有一个
$$typeof
, 表明它是一个context的 Provider,或 Consumer - 这个问题在前文也有提过
- 返回的这个 Provider 和 Consumer,它们里面都有
文章来源:https://blog.csdn.net/Tyro_java/article/details/135322592
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!