React源码解析————ReactContext.js,ReactLazy.js
2021SC@SDUSC
2021SC@SDUSC
ReactContext源码解析
导入Context类型值
import {REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
import type {ReactContext} from 'shared/ReactTypes';
1.创建context对象: $$typeof属性规定对象的类型为REACT_CONTEXT_TYPE
2.因为在支持多个并行渲染器时,React将渲染器分类为主要和次要两个等级。并且同时只能存在两个并行渲染器:React Native(主要), Fabric(次要),React DOM(主要),React ART(次要)。次要渲染器将Context值存在不同的变量中。因此设置两个当前值变量: _currentValue: defaultValue, _currentValue2: defaultValue,
3._threadCount被用来追踪一个context某一时刻有多少同步渲染器,档次渲染同样支持,ex:平行服务渲染(parallel server rendering)
4.provider中有context属性,context中provider属性为环形定义
5.定义context的Provider属性
6.定义一些在dev环境中的警告flag
export function createContext<T>(defaultValue: T): ReactContext<T> {
// TODO: Second argument used to be an optional `calculateChangedBits`
// function. Warn to reserve for future use?
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
// 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,
// Used to track how many concurrent renderers this context currently
// supports within in a single renderer. Such as parallel server rendering.
_threadCount: 0,
// These are circular
Provider: (null: any),
Consumer: (null: any),
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
let hasWarnedAboutUsingNestedContextConsumers = false;
let hasWarnedAboutUsingConsumerProvider = false;
let hasWarnedAboutDisplayNameOnConsumer = false;
为了向前兼容,可以从consumer中溯源context的信息
const Consumer = {
$$typeof: REACT_CONTEXT_TYPE,
_context: context,
};
如果将context用作consumer,我们应该警告此类使用 1.如果将context用作consumer的时候,provider会在context.consumer.provider中,于是应该警告context.consumer.provider已经被移动到context.provider中 2.如果将context用作consumer的时候,consumerr会在context.consumer.consumer中,于是应该警告context.consumer.consumer已经被移动到context.consumer中
console.error(
'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' +
'a future major release. Did you mean to render <Context.Provider> instead?',
console.error(
'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' +
'a future major release. Did you mean to render <Context.Consumer> instead?',
);
于是在开发环境下: 使用Object.defineProperties() 方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。 在Consumer上对Provider、 _currentValue、_currentValue2、 _threadCount、 Consumer、displayName进行修改和警告处理并最终赋给context.Consumer
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,
};
// $FlowFixMe: Flow complains about not setting a value, which is intentional here
Object.defineProperties(Consumer, {
Provider: {
get() {
if (!hasWarnedAboutUsingConsumerProvider) {
hasWarnedAboutUsingConsumerProvider = true;
console.error(
'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;
},
},
_threadCount: {
get() {
return context._threadCount;
},
set(_threadCount) {
context._threadCount = _threadCount;
},
},
Consumer: {
get() {
if (!hasWarnedAboutUsingNestedContextConsumers) {
hasWarnedAboutUsingNestedContextConsumers = true;
console.error(
'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;
},
},
displayName: {
get() {
return context.displayName;
},
set(displayName) {
if (!hasWarnedAboutDisplayNameOnConsumer) {
console.warn(
'Setting `displayName` on Context.Consumer has no effect. ' +
"You should set it directly on the context with Context.displayName = '%s'.",
displayName,
);
hasWarnedAboutDisplayNameOnConsumer = true;
}
},
},
});
// $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;
}
context用法
使用Provider和Consumer生产和消费数据
import React from 'react';
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 创建1个Context对象并给定默认值,如果没有匹配到Provider,消费组件取Context默认值
export const ProductContext = React.createContext({
name: 'car',
price: 8000,
});
export const { Provider, Consumer } = ProductContext;
import React, { PureComponent } from 'react';
import MidPage from './MidPage';
import { Provider } from './ProductContext';
class ProviderPage extends PureComponent {
state = {
product: {
name: 'plane',
price: 12000,
},
};
render() {
const { product } = this.state;
return (
<div>
<h1>根组件使用Provider传值,子组件使用Consumer接收</h1>
<Provider value={product}>
<MidPage />
</Provider>
{/*不用Provider,显示Context对象defaultValue*/}
</div>
);
}
}
export default ProviderPage;
import React, { PureComponent } from 'react';
import { Consumer } from './ProductContext';
class MidPage extends PureComponent {
render() {
return (
<Consumer>
{context => {
return (
name:{context.name}
price:{context.price}
</div>
);
}}
</Consumer>
);
}
}
export default MidPage;
最后结果显示:
根组件使用Provider传值,子组件使用Consumer接收
name:plane
price:12000
ReactLazy.js
如果想要实现懒加载,我们必须要学会使用lazy,suspense等等,下面我们来解析ReactLazy.js文件
首先是定义的一些状态变量:Uninitialized(初始化),Pending(加载中),Resolved(加载成功),Rejected(加载失败) 并且对于每种状态定义了一种type
const Uninitialized = -1;
const Pending = 0;
const Resolved = 1;
const Rejected = 2;
type UninitializedPayload<T> = {
_status: -1,
_result: () => Thenable<{default: T, ...}>,
};
type PendingPayload = {
_status: 0,
_result: Wakeable,
};
type ResolvedPayload<T> = {
_status: 1,
_result: {default: T},
};
type RejectedPayload = {
_status: 2,
_result: mixed,
};
type Payload<T> =
| UninitializedPayload<T>
| PendingPayload
| ResolvedPayload<T>
| RejectedPayload;
export type LazyComponent<T, P> = {
$$typeof: Symbol | number,
_payload: P,
_init: (payload: P) => T,
};
定义初始化的函数,最初payload._result为传入的函数,之后payload._result 则用于存储懒加载异步函数执行的结果,首先判断_status,若为Uninitialized,调用传入的函数,使用then方法查看前值、终值和据因(这里的thenable有两种一种是Wakeable另一种是Thenable<+R>,onFulfill和onReject大家在学习异步的时候想必都学过这里不再赘述):
// The subset of a Thenable required by things thrown by Suspense.
// This doesn't require a value to be passed to either handler.
export interface Wakeable {
then(onFulfill: () => mixed, onReject: () => mixed): void | Wakeable;
}
// The subset of a Promise that React APIs rely on. This resolves a value.
// This doesn't require a return value neither from the handler nor the
// then function.
export interface Thenable<+R> {
then<U>(
onFulfill: (value: R) => void | Thenable<U> | U,
onReject: (error: mixed) => void | Thenable<U> | U,
): void | Thenable<U>;
}
此时函数执行结束之后moduleObject中已经有了执行的终值或者被拒绝执行后error中已经有了拒绝的原因,当然此时完全有可能依旧是初始化状态(因为有可能发生各种异常,例如成功执行后,在执行onFulfill的函数的时候抛出异常等等,此时payload的_status没有变化),所以这种情况我们将_status改为Pending,下一次再尝试加载,等待加载成功,若加载成功则返回moduleObject.default(当然在开发环境下,moduleObject为undefined或者moduleObject没有default属性都会报错),若失败返回加载失败的原因。
function lazyInitializer<T>(payload: Payload<T>): T {
if (payload._status === Uninitialized) {
const ctor = payload._result;
const thenable = ctor();
// Transition to the next state.
// This might throw either because it's missing or throws. If so, we treat it
// as still uninitialized and try again next time. Which is the same as what
// happens if the ctor or any wrappers processing the ctor throws. This might
// end up fixing it if the resolution was a concurrency bug.
thenable.then(
moduleObject => {
if (payload._status === Pending || payload._status === Uninitialized) {
// Transition to the next state.
const resolved: ResolvedPayload<T> = (payload: any);
resolved._status = Resolved;
resolved._result = moduleObject;
}
},
error => {
if (payload._status === Pending || payload._status === Uninitialized) {
// Transition to the next state.
const rejected: RejectedPayload = (payload: any);
rejected._status = Rejected;
rejected._result = error;
}
},
);
if (payload._status === Uninitialized) {
// In case, we're still uninitialized, then we're waiting for the thenable
// to resolve. Set it as pending in the meantime.
const pending: PendingPayload = (payload: any);
pending._status = Pending;
pending._result = thenable;
}
}
if (payload._status === Resolved) {
const moduleObject = payload._result;
if (__DEV__) {
if (moduleObject === undefined) {
console.error(
'lazy: Expected the result of a dynamic imp' +
'ort() call. ' +
'Instead received: %s\n\nYour code should look like: \n ' +
// Break up imports to avoid accidentally parsing them as dependencies.
'const MyComponent = lazy(() => imp' +
"ort('./MyComponent'))\n\n" +
'Did you accidentally put curly braces around the import?',
moduleObject,
);
}
}
if (__DEV__) {
if (!('default' in moduleObject)) {
console.error(
'lazy: Expected the result of a dynamic imp' +
'ort() call. ' +
'Instead received: %s\n\nYour code should look like: \n ' +
// Break up imports to avoid accidentally parsing them as dependencies.
'const MyComponent = lazy(() => imp' +
"ort('./MyComponent'))",
moduleObject,
);
}
}
return moduleObject.default;
} else {
throw payload._result;
}
}
下面是lazy函数,首先定义payload,然后调用lazyInitializer,然后返回lazyType,当然在开发环境下理所当然的有各种情况的设置,这里不再赘述
export function lazy<T>(
ctor: () => Thenable<{default: T, ...}>,
): LazyComponent<T, Payload<T>> {
const payload: Payload<T> = {
// We use these fields to store the result.
_status: -1,
_result: ctor,
};
const lazyType: LazyComponent<T, Payload<T>> = {
$$typeof: REACT_LAZY_TYPE,
_payload: payload,
_init: lazyInitializer,
};
if (__DEV__) {
// In production, this would just set it on the object.
let defaultProps;
let propTypes;
// $FlowFixMe
Object.defineProperties(lazyType, {
defaultProps: {
configurable: true,
get() {
return defaultProps;
},
set(newDefaultProps) {
console.error(
'React.lazy(...): It is not supported to assign `defaultProps` to ' +
'a lazy component import. Either specify them where the component ' +
'is defined, or create a wrapping component around it.',
);
defaultProps = newDefaultProps;
// Match production behavior more closely:
// $FlowFixMe
Object.defineProperty(lazyType, 'defaultProps', {
enumerable: true,
});
},
},
propTypes: {
configurable: true,
get() {
return propTypes;
},
set(newPropTypes) {
console.error(
'React.lazy(...): It is not supported to assign `propTypes` to ' +
'a lazy component import. Either specify them where the component ' +
'is defined, or create a wrapping component around it.',
);
propTypes = newPropTypes;
// Match production behavior more closely:
// $FlowFixMe
Object.defineProperty(lazyType, 'propTypes', {
enumerable: true,
});
},
},
});
}
return lazyType;
}
|