React Introduction Series (3) Context
In this blog we continue to look at the Context section of the React Advanced Guide.
This will be interspersed with component composition, render prop, and some knowledge of function components.
Why use context?
Context provides a way to pass data between component trees without manually adding props for each layer of components.
In a typical React application, data is passed from top to bottom (by parent and child) through props properties, but this usage is extremely cumbersome for certain types of properties (such as locale preferences, UI themes), these properties are required by many components in the application. Context provides a way to share such values between components without explicitly passing props layer by layer through the component tree.
From my understanding, if a property needs to be shared by multiple components and passed layer by layer, we can use this method.
Why meet these two conditions?
If it is simply common to multiple sibling components, we can use the state promotion method.
If we simply pass it layer by layer, we can also use a combination of components.
When to use
Context is designed to share data that is “global” to a component tree, such as the currently authenticated user, theme, or preferred language. For example, in the following code, we manually adjust the style of a button component through a “theme” attribute:
1 | class App extends React.Component { |
Using context, we can avoid passing props through intermediate elements:
1 | //Context allows us to pass values deep into the component tree without explicitly passing them through each component. |
Note that in order to use this.context, you must first define static.
For example, ‘static’ in the above example
Use
The main use case for Context is that * many * components at different levels need to access some of the same data. Use caution as this makes components less likely to reuse.
** If you just want to avoid passing some properties layer by layer,组件组合(component composition)Sometimes a better solution than context. **
Component combination
For example, consider a Page component that passes down the user and avatarSize properties so that deeply nested Link and Avatar components can read them:
1 | <Page user={user} avatarSize={avatarSize} /> |
If in the end only the Avatar component really needs user and avatarSize, then passing these two props layer by layer is very redundant. And once the Avatar component needs more props from the top-level component, you have to add them one by one at the middle level, which will become very troublesome.
A ** no context ** solution is将 Avatar
组件自身传递下去Therefore, the intermediate component does not need to know props such as’user ‘or’avatarSize’.
1 | function Page(props) { |
With this change, only the topmost Page component needs to know how the Link and Avatar components use user and avatarSize.
This inversion of control over components reduces the number of props to pass in your application, which in many cases will make your code cleaner and give you more control over the root component. However, this doesn’t apply to every scenario: this kind of lifting the logic to a higher level in the component tree will make these high-level components more complex and force low-level components to adapt to such a form, which may not be what you want.
And your component is not limited to receiving a single subcomponent. You may pass multiple subcomponents and even encapsulate multiple separate “slots” for these subcomponents (children),正如这里的文档所列举的
1 | function Page(props) { |
This mode is enough to cover many scenarios where you need to decouple the child component from the directly related parent component. If the child component needs to communicate with the parent component before rendering, you can further use render props。
However, sometimes components at different levels in the component tree need to access the same batch of data. Context allows you to “broadcast” this data to all components in the component tree, and all components can access this data and subsequent data updates. Common scenarios for using context include managing the current locale, theme, or some cached data, which is much simpler than the alternative.
I don’t know if you will be a little confused when you see this doc, let me briefly sort it out:
render
A component with a render prop accepts a function that returns a React element and implements its rendering logic inside the component by calling this function.
1 | <DataProvider render={data => ( |
This is just a brief mention of the concept, and I will analyze the specific use of render props in a future blog.
API
React.createContext
1 | const MyContext = React.createContext(defaultValue); |
Create a Context object. When React renders a component that is subscribed to this Context object, the component will read the current context value from the nearest matching’Provider 'in the component tree.
The defaultValue parameter of the component will only take effect when there is no match to the Provider in the tree where it is located. This default value helps to test the component without wrapping it with a Provider. Note: When passing’undefined ‘to the value of the Provider, the’defaultValue’ of the consuming component will not take effect.
Context.Provider
1 | < MyContext. Provider value = {/* some value */} > |
Each Context object returns a Provider React component that allows the consuming component to subscribe to changes in the context.
Provider receives a’value 'attribute and passes it to the consumer component. A Provider can have a corresponding relationship with multiple consumer components. Multiple Providers can also be used nested, and the inner layer will overwrite the outer layer of data.
When the provider’s value changes, all consumer components inside it will be re-rendered. Neither the provider nor its internal consumer components are subject to the shouldComponentUpdate function, so consumer components can be updated if their ancestor components exit the update.
To determine the change by detecting the old and new values, the Object.is
The same algorithm.
Attention
When passing an object to
Class.contextType
1 | class MyClass extends React.Component { |
The’contextType 'property mounted on the class will be reassigned to a React.createContext()
Context object created. This property allows you to use this.context to consume the value of the most recent Context. You can access it in any lifetime, including the render function.
Note:
You only pass the
If you are using experimental
1 | class MyClass extends React.Component { |
Context.Consumer
1 | <MyContext.Consumer> |
A React component that allows you to subscribe to changes in context函数式组件You can subscribe to context in.
This method requires a函数作为子元素(function as a child)This function takes the current context value and returns a React NodeThe value passed to the function is equivalent to the value provided by the provider closest to the context in the component treeIf there is no corresponding Provider, the value parameter is equivalent to the defaultValue passed to createContext ()
Attention
Want to learn more about
Context.displayName
The context object accepts a property called’displayName 'of type string. React DevTools uses this string to determine what the context should display.
For example, the following component will be displayed as MyDisplayName in DevTools:
1 | const MyContext = React.createContext(/* some value */); |
Here we summarize the use and precautions regarding these APIs.
- There is one way to create a Context, which is to call’const MyContext = React.createContext (defaultValue); ’
- There are two ways to subscribe to Context:
- In the component you want to subscribe to ‘static contextType = MyContext;’ but this method can only subscribe to one Context
- ‘Context. Consumer’: This method requires a function as a child. This function receives the current context value and returns a React node.
- The context object accepts a property called’displayName 'of type string. React DevTools uses this string to determine what the context should display.
Example
Dynamic
A more complex solution is to use dynamic values for the theme example above:
theme-context.js
1 | export const themes = { |
A Context is declared here, the default value is theme.dark
themed-button.js
1 | import {ThemeContext} from './theme-context'; |
This declares a component called’ThemedButton’
Via ThemedButton.contextType
app.js
1 | import {ThemeContext, themes} from './theme-context'; |
First of all, ‘ThemeContext. Provider’ provides value, so its internal Toolbar uses value instead of the default value of ThemeContext
The’ThemedButton ‘directly used below is not inside any’ThemeContext. Provider’ and directly uses the latest default value.
Update in nested components
It is necessary to update the context from a component that is deeply nested in the component tree. In this scenario, you can pass a function through context to make the consumers component update the context:
theme-context.js
1 | //Make sure the default value data structure passed to createContext matches that of the invoked consumers! |
theme-toggler-button.js
1 | import {ThemeContext} from './theme-context'; |
According to our previous analysis, the children of’ThemeContext. Consumer 'are a function component. When the Context changes, it will regenerate the component.
That is to say, when our theme or toggleTheme changes, it will use the changed value to create a new component to replace the old component.
app.js
1 | import {ThemeContext, themes} from './theme-context'; |
Content is rendered in’ThemeContext. Provider ‘, and’ThemeTogglerButton’ is rendered in Content.
The value provided by ThemeContext. Provider is its own state. This state contains the theme and toggleTheme functions.
When we trigger toggleTheme in’ThemeTogglerButton ', we actually modify the outermost state.
This state will be passed as a value by’ThemeContext. Provider ‘, triggering the update of’ThemeTogglerButton’.
This method is essentially no different from state promotion. It allows child components to have a way to change the state of the parent component, but this state does not need to be passed down layer by layer.
Consume multiple
To ensure fast re-rendering of context, React needs to make the context of each consumer component a separate node in the component tree.
1 | //Theme context, default theme is "light" value |
If two or more context values are often used together, you may want to consider creating your own rendering component to provide these values.
Precautions
Because the context uses the reference identity to decide when to render, there may be some pitfalls here. When the provider’s parent component renders, it may trigger unexpected rendering in the consumers component. For example, every time the Provider re-renders, the following code will re-render all the following consumers components because the’value 'attribute is always assigned a new object:
1 | class App extends React.Component { |
To prevent this, promote the value state to the state of the parent node.
1 | class App extends React.Component { |
In this way, each time the Provider re-renders, it will not reassign value to a new object, but will always be state.value.