Context
In this article, we talk about Context. Context can realize the transfer of data across components. Most of the time it is not necessary, but sometimes, for example, the user has set UI themes and regional preferences. If you pass down from the top layer, it will be a bit troublesome. It is better to directly use Context to realize data transfer.
Old Context API
Basic examples
Before talking about the latest API, let’s review the old Context API:
class Child extends { render() { // 4. Use here to get return <p>{}</p> } } // 3. Add contextTypes static property to the child component = { value: }; class Parent extends { state = { value: 'foo' } // 1. When state or props change, the getChildContext function will be called getChildContext() { return {value: } } render() { return ( <div> <Child /> </div> ) } } // 2. Add childContextTypes static property to the parent component = { value: };
context interrupt problem
React does not officially recommend this API. For possible problems, the React documentation provides an introduction as follows:
The problem is that if a context provided by the component changes and the shouldComponentUpdate of the intermediate parent component returns false, the descendant components using that value will not be updated. Components using context are completely out of control, so there is basically no way to update context reliably.
For this question, let's write a sample code:
// 1. Child component uses PureComponentclass Child extends { render() { return <GrandChild /> } } class GrandChild extends { render() { return <p>{}</p> } } = { theme: }; class Parent extends { state = { theme: 'red' } getChildContext() { return {theme: } } render() { return ( <div onClick={() => { ({ theme: 'blue' }) }}> <Child /> <Child /> </div> ) } } = { theme: };
In this example code, when clicking on the textred
When the text is not modifiedblue
, if we change Child toextends Component
, it can be modified normally
This shows that the intermediate componentshouldComponentUpdate
fortrue
When , the transmission of the Context will be interrupted.
PureComponent exists to reduce unnecessary rendering, but we also want Context to be passed normally. Is there any way to solve it?
Since the existence of PureComponent causes the Context to be unable to be updated, then it simply stops being updated. If the Context is not updated, can GrandChild be unable to be updated?
Solution
Of course there are methods:
// 1. Create a subscription publisher, of course you can also call it a dependency injection system, or DIclass Theme { constructor(value) { = value = [] } setValue(value) { = value (f => f()) } subscribe(f) { (f) } } class Child extends { render() { return <GrandChild /> } } class GrandChild extends { componentDidMount() { // 4. After GrandChild obtains the store, subscribe (() => ()) } // 5. GrandChild gets the required value from the store render() { return <p>{}</p> } } = { theme: }; class Parent extends { constructor(p, c) { super(p, c) // 2. We instantiate a store (think redux store) and store it in the instance property = new Theme('blue') } // 3. Pass to the GrandChild component via context getChildContext() { return {theme: } } render() { // 6. Publish via store return ( <div onClick={() => { ('red') }}> <Child /> <Child /> </div> ) } } = { theme: };
In order to manage our theme, we established a dependency injection system (DI) and passed the store down through Context. Components that use store data need to be subscribed and passed a forceUpdate function. When the store is published, each component of the theme depends on the theme to execute forceUpdate, thereby realizing the update of each dependency component without the Context being updated.
You may have found that this has a little react-redux scent.
Of course, we can also use Mobx to implement and simplify the code. For specific implementations, please refer to Michel Weststrate (the author of Mobx)How to safely use React context
New Context API
Basic examples
I believe everyone has used it more or less, so let's directly use the example code:
// 1. Create Provider and Consumerconst {Provider, Consumer} = ('dark'); class Child extends { // 3. The Consumer component receives a function as a child element. This function takes the current context value and returns a React node. render() { return ( <Consumer> {(theme) => ( <button> {theme} </button> )} </Consumer> ) } } class Parent extends { state = { theme: 'dark', }; componentDidMount() { setTimeout(() => { ({ theme: 'light' }) }, 2000) } render() { // 2. Pass the value through the Provider's value return ( <Provider value={}> <Child /> </Provider> ) } }
When the value of the Provider changes, all consumer components inside it are re-rendered.
The advantage of the new API is that the propagation from Provider to its internal consumer components (including .contextType and useContext) is not subject to the shouldComponentUpdate function, so the consumer component can be updated when its ancestor components skip updates.
Simulation implementation
So how is createContext implemented? Let's not look at the source code for now. Based on the previous experience of subscribing to publishers, we can actually write a createContext ourselves. Let's try writing one:
class Store { constructor() { = [] } publish(value) { (f => f(value)) } subscribe(f) { (f) } } function createContext(defaultValue) { const store = new Store(); // Provider class Provider extends { componentDidUpdate() { (); } componentDidMount() { (); } render() { return ; } } // Consumer class Consumer extends { constructor(props) { super(props); = { value: defaultValue }; (value => { ({ value }); }); } render() { return (); } } return { Provider, Consumer }; }
Replace the method with the createContext we wrote, and you will find that it can also run.
It is actually the same as solving the old Context API problem, except that it is a layer of encapsulation. Subscribe when the Consumer component is built, and release when the Provider is updated. This skips the limit of PureComponent and implements the update of the Consumer component.
createContext source code
Now let's take a look at the real createContext source code.Source code locationexistpackages/react/src/
, the simplified code is as follows:
import {REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; export function createContext(defaultValue) { const context = { $$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, Consumer: null, // Add these to use same hidden class in VM as ServerContext _defaultValue: null, _globalName: null, }; = { $$typeof: REACT_PROVIDER_TYPE, _context: context, }; = context; return context; }
You will find that just like the source code involved in the previous article, React's createContext just returns a data object, but it doesn't matter. The implementation process will be slowly parsed in future articles.
React Series
React's createElement source code interpretation
The difference between React elements and components
React's use of Refs and forwardRef's source code interpretation
The preheating series of React series will take you to understand React from the perspective of source code
The above is a detailed explanation of the changes in React Context and the implementation principles behind it. For more information about the changes in React Context, please pay attention to my other related articles!