React中不常用的功能——Context
2021-03-05 本文已影响0人
依然还是或者其他
React中不常用的功能——Context
Context
React源码版本16.8
基本用法
跨层级通信 Context
React.createContext创建context对象
// 新建Context.js
import React from "react";
export const Context=React.createContext();
Context.Provider父级创建provider传递参数
<Context.Provider value={theme}>
<ContextTypePage/>
</Context.Provider>
子组件消费
- contextType
//传递一个参数的情况
import React, { Component } from 'react'
import {Context} from "../Context"
export default class ContextTypePage extends Component {
static contextType=Context;
render() {
const {themeColor}=this.context;
console.log("contextType",this.context)
return (
<div className="border">
<span>我是ContextTypePage页面</span>父级传过来的颜色是——{themeColor}
</div>
)
}}
该 API 订阅单一 context
- Consumer
import React, { Component } from 'react'
import { Context,UserContext } from '../Context'
export default class ConsumerPage extends Component {
render() {
return (
<div className="border">
我是ConsumerPage页面
<Context.Consumer>
{
theme=>{
return <div>页面父组件传过来的是——{theme.themeColor}
<UserContext.Consumer>
{
user=><p>我是传过来第二个参数——{user.name} </p>
}
</UserContext.Consumer>
</div>
}
}
</Context.Consumer>
</div>
)
}
}
- useContext
//传递两个参数情况
import React from 'react'
import {Context,UserContext}from '../Context'
export default function useContextPage(props) {
const theme=React.useContext(Context)
const user=React.useContext(UserContext)
return (
<div className="border">
<span>我是useContextPage页面</span>
父级传过来的是——{theme.themeColor}
<p>传过来的第二个参数:{user.name}</p>
</div>
)
}
useContext只可以用在函数组件中或者自定义hook,且第二个参数不会覆盖第一个
大致原理
ReactContext.js 中的createContext
// createContext 创建一个Context ,即入口函数
export function createContext<T>(
defaultValue: T,
calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
if (calculateChangedBits === undefined) {
calculateChangedBits = null;
} else {
//dev相关
}
//NOTE: 声明context
const context: ReactContext<T> = {
//Symbol 类型
$$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,
// 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,
//_contex 指向context本身
_context: context,
};
let hasWarnedAboutUsingNestedContextConsumers = false;
let hasWarnedAboutUsingConsumerProvider = false;
if (__DEV__) {
//dev相关
} else {
//Consumer 指向context本身
context.Consumer = context;
}
//dev相关
return context;
}
contextType原理
ReactFiberClassComponent.js 中的constructClassInstance
即Class 组件 构造函数初始化
function constructClassInstance(
workInProgress: Fiber,
ctor: any,
props: any,
renderExpirationTime: ExpirationTime,
): any {
let isLegacyContextConsumer = false;
let unmaskedContext = emptyContextObject;
let context = null;
const contextType = ctor.contextType;
//dev情况.....
if (typeof contextType === 'object' && contextType !== null) {
//若contextType是对象 且不为null 则将contextType赋值给this.context
//NOTE: 读取contextType,赋值给context
context = readContext((contextType: any));
} else {
unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
const contextTypes = ctor.contextTypes;
isLegacyContextConsumer =
contextTypes !== null && contextTypes !== undefined;
context = isLegacyContextConsumer
? getMaskedContext(workInProgress, unmaskedContext)
: emptyContextObject;
}
// Instantiate twice to help detect side-effects.
//dev情况.....
const instance = new ctor(props, context);
const state = (workInProgress.memoizedState =
instance.state !== null && instance.state !== undefined
? instance.state
: null);
adoptClassInstance(workInProgress, instance);
//dev情况.....
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// ReactFiberContext usually updates this cache but can't for newly-created instances.
if (isLegacyContextConsumer) {
cacheContext(workInProgress, unmaskedContext, context);
}
return instance;
}
从上可知若contextType是对象 且不为null 则将contextType赋值给this.context
Consumer原理
从构造函数可以知晓Consumer跟Provider是指向同一个context的,所以实现了跨级访问
useState原理
export function useContext<T>(
Context: ReactContext<T>,
unstable_observedBits: number | boolean | void,
) {
const dispatcher = resolveDispatcher();
//dev.....
return dispatcher.useContext(Context, unstable_observedBits);
}
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
//.....
return dispatcher;
}
在ReactFiberHooks.js中 声明了
export type Dispatcher = {
readContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
): T,
useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>],
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: (I) => S,
): [S, Dispatch<A>],
useContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
): T,
//其他声明......
//....
};
const HooksDispatcherOnMount: Dispatcher = {
readContext,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
};
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
};
在HooksDispatcherOnMount或HooksDispatcherOnUpdate中,useContext实际调用的都是readContext
ReactFiberNewContext.js中的readContext
//NOTE: Context读取
export function readContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
): T {
//dev....
//.....
if (lastContextWithAllBitsObserved === context) {
// Nothing to do. We already observe everything in this context.
} else if (observedBits === false || observedBits === 0) {
// Do not observe any updates.
} else {
let resolvedObservedBits; // Avoid deopting on observable arguments or heterogeneous types.
if (
typeof observedBits !== 'number' ||
observedBits === MAX_SIGNED_31_BIT_INT
) {
// Observe all updates.
lastContextWithAllBitsObserved = ((context: any): ReactContext<mixed>);
resolvedObservedBits = MAX_SIGNED_31_BIT_INT;
} else {
resolvedObservedBits = observedBits;
}
let contextItem = {
context: ((context: any): ReactContext<mixed>),
observedBits: resolvedObservedBits,
next: null,
};
if (lastContextDependency === null) {
invariant(
currentlyRenderingFiber !== null,
'Context can only be read while React is rendering. ' +
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
'In function components, you can read it directly in the function body, but not ' +
'inside Hooks like useReducer() or useMemo().',
);
// This is the first dependency for this component. Create a new list.
lastContextDependency = contextItem;
currentlyRenderingFiber.contextDependencies = {
first: contextItem,
expirationTime: NoWork,
};
} else {
// Append a new context item.
lastContextDependency = lastContextDependency.next = contextItem;
}
}
// 只有在React Native里边isPrimaryRenderer才会是false
return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}
readContext返回context._currentValue
总结
context实现跨级读取访问的根本性就是通过Context组件维护一个稳定对象,在对象内维护一个可变的_currentValue值,供Consumer访问