“This is the sixth day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

React version: V17.0.3

1, the entrance

Remember that react-hooks Hooks are used in the same way that they are used in the same way. Remember that react-hooks Hooks are used in the same way that they are used in the same way.

  • Component mount phase:

    useContext = ReactCurrentDispatcher.current.useContext = HooksDispatcherOnMount.useContext = readContext;

  • Component update phase:

    useContext = ReactCurrentDispatcher.current.useContext = HooksDispatcherOnUpdate.useContext = readContext;

The function useContext ultimately executes, whether in the mount or update phase, is readContext. Next, let’s look at the implementation of readContext.

2, readContext

// packages/react-reconciler/src/ReactFiberHooks.new.js export function readContext<T>(context: ReactContext<T>): // isPrimaryRenderer is true in ReactDOM. // isPrimaryRenderer is true in ReactDOM. _currentValue Const value = isPrimaryRenderer? context._currentValue : context._currentValue2; if (lastFullyObservedContext === context) { // Nothing to do. We already observe everything in this context. } else { // Const contextItem = {context: const contextItem = {context: const contextItem = {context: ((context: any): ReactContext<mixed>), memoizedValue: value, next: null, }; If (lastContextDependency === null) {// This is the first dependency for this.create a new list. // This is the first dependency of the component, create a new context dependency list lastContextDependency = contextItem; currentlyRenderingFiber.dependencies = { lanes: NoLanes, firstContext: contextItem, }; if (enableLazyContextPropagation) { currentlyRenderingFiber.flags |= NeedsPropagation; }} else {/ / Append a new context item. / / in the back of the list to add a new context lastContextDependency = lastContextDependency. Next = contextItem; }} // readContext returns context._currentValue return value; }Copy the code

ReadContext takes _currentValue/_currentValue2 out of the context object and builds a new context item, It stores the current context object and _currentValue/_currentValue2 on the context object, connects to the next context item via the next pointer, and then builds a list of context dependencies. Mount the list to the Fiber node currently being rendered, and return _currentValue/_currentValue2 retrieved from the context object.

ReadContext receives a context object (the return value of React. CreateContext) and returns the current value of that context. So let’s look at the context object and the current value of that context.

3, createContext

The React Context property allows components to pass props across hierarchies. The Context object is created using the createContext method:

const MyContext = React.createContext(defaultValue);
Copy the code

Let’s look at the createContext implementation:

// packages/react/src/ReactContext.js 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 in ReactContext is stored as an object in the createElement attribute type: REACT_CONTEXT_TYPE, // As a workaround for supporting multiple concurrent renderers, we classified some renderers as primary and others as auxiliary. // As a workaround to support multiple concurrent renderers, We categorize // Some renderers as primary and others as secondary. // We only expect a maximum of two concurrent renderers: React Native and Fabric; React DOM (primary) and React ART (secondary) // The auxiliary renderer stores the value of its context in a separate field. // 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. // <Provider value={XXX}> <Provider value={XXX}> <Provider value={XXX}> <Provider value={XXX}> _currentValue: defaultValue; // Provider's value attribute _currentValue2: defaultValue, // Provider value attribute // Used to track how many concurrent renderers this context currently // supports within in a single renderer. Such as parallel server rendering. _threadCount: 0, // The number of concurrent renderers used to track the context // These are circular Providers: (null: any), // Provide components Consumer: (null: any), // application components}; Context. Provider = {$$typeof: {$$typeof: {$$typeof: {$$typeof: REACT_PROVIDER_TYPE, _context: context, }; let hasWarnedAboutUsingNestedContextConsumers = false; let hasWarnedAboutUsingConsumerProvider = false; let hasWarnedAboutDisplayNameOnConsumer = false; If (__DEV__) {// delete DEV part of the code} else {// the Consumber object points to the react. Context object. // Make Consumer=React.Context, // React.Context _currentValue has been assigned to the value of <Provider>, so Consumer can get the latest value immediately. } // remove the DEV section of the code return context; }Copy the code

In createContext, build a context object and assign the defaultValue passed in to the context object’s _currentValue and _currentValue2 properties, We define a _threadCount property on the context object that tracks the number of concurrent renderers of the context, a Provider component that provides a value to the Consumer component, And a Consumer component for consuming the context.

The _currentValue and _currentValue2 attributes are designed for different platforms, such as Web and mobile. Both of these attributes are assigned to the defaultValue passed in when the context object is initialized. During React updates, there will always be a stack called valueCursor, which helps keep track of the current context. Each time a component is updated, Both _currentValue and _currentValue2 will be assigned to the latest value.

Once the context object is built, the current context object is mounted to the Provider component and the Consumer component, respectively.

Finally, the context object is returned with the following data structure:

4. Precautions for use

Because the context uses the reference identity to decide when to render, there can be pitfalls that trigger accidental rendering in the consumers component when the provider’s parent rerenders. For example, every time the Provider rerenders, the following code rerenders all of the following consumers components, because the value property is always assigned to the new object:

class App extends React.Component { render() { return ( <MyContext.Provider value={{something: 'something'}}> <Toolbar /> </MyContext.Provider> ); }}Copy the code

To prevent this, store the value state in the parent node’s state:

class App extends React.Component { constructor(props) { super(props); this.state = { value: {something: 'something'}, }; } render() { return ( <MyContext.Provider value={this.state.value}> <Toolbar /> </MyContext.Provider> ); }}Copy the code