preface
Context is translated as Context, and in the programming world, this is a concept you often encounter, including React.
Context is classified as Advanced in the React API, but it is not recommended for stable versions of the App.
The vast majority of applications do not need to use content.
If you want your application to be stable, don’t use context. It is an experimental API and it is likely to break in future releases of React.
However, that doesn’t mean we don’t need to focus on the Context. In fact, many good React components use the Context to perform their functions. For example, the
Today I want to talk to you a little bit about the Context that I’ve come to recognize in development, and how I’ve used it for component development.
Note: All apps mentioned in this article refer to Web apps.
I met the React Context
The official definition of Context
The React document website does not define a Context. It describes the Context used and how to use it.
The official website describes the Context scenario as follows:
In Some Cases, you want to pass data through the component tree without having to pass the props down manuallys at every level. you can do this directly in React with the powerful “context” API.
Simply put, when you don’t want to pass props or state layer by layer in the component tree, you can use Context to pass component data across layers.
Pass data using props or state, and the data flows from the top.
Using Context, you can pass data across components.
How to use Context
For Context to come into play, there are two components, the Context producer, which is usually a parent, and the Consumer of a Context, which is usually one or more child nodes. So the use of Context is based on the producer-consumer pattern.
The parent component, the Context producer, declares the properties of the Context object provided to the child component through a static property childContextTypes and implements an instance getChildContext method, Return a plain object representing the Context.
import React from 'react' import PropTypes from 'prop-types' class MiddleComponent extends React.Component { render () { Return <ChildComponent />}} Class ParentComponent extends react.ponent {// Declares the Context object property static childContextTypes = {propA: proptypes.string, methodA: proptypes.func} // getChildContext () {return {propA: proptypes.string, methodA: proptypes.func} 'propA', methodA: () => 'methodA'} render () {return <MiddleComponent />}Copy the code
For consumers of the Context, access the Context provided by the parent component in the following way.
import React from 'react' import PropTypes from 'prop-types' class ChildComponent extends React.Component { // Static contextTypes = {propA: PropTypes.string } render () { const { propA, methodA } = this.context console.log(`context.propA = ${propA}`) // context.propA = propA console.log(`context.methodA = ${methodA}`) // context.methodA = undefined return ... }} Copy the codeCopy the code
Child components need to be declared with a static contextTypes property to access the properties of the parent Context object. Otherwise, the object is undefined, even if the property name is correctly written.
For Stateless Component, you can access the parent’s Context in the following way
import React from 'react' import PropTypes from 'prop-types' const ChildComponent = (props, context) => { const { propA } = context console.log(`context.propA = ${propA}`) // context.propA = propA return ... } ChildComponent. ContextProps = {propA: PropTypes string} copy codeCopy the code
In subsequent releases React tweaked the Context API to make the producer-consumer model more explicit.
import React from 'react'; import ReactDOM from 'react-dom'; const ThemeContext = React.createContext({ background: 'red', color: 'white' }); Copy the codeCopy the code
Create a Context object using the static method react.createcontext (). This Context object contains two components,
class App extends React.Component { render () { return ( <ThemeContext.Provider value={{background: 'green', color: 'white'}}> <Header /> </ThemeContext.Provider> ); }} Copy the codeCopy the code
The value of
class Header extends React.Component { render () { return ( <Title>Hello React Context API</Title> ); } } class Title extends React.Component { render () { return ( <ThemeContext.Consumer> {context => ( <h1 style={{background: context.background, color: context.color}}> {this.props.children} </h1> )} </ThemeContext.Consumer> ); }} Copy the codeCopy the code
The children of
Context’s new API is much more React style.
A couple of places you can get the Context directly
In fact, in addition to the instance context property (this.context), the React component has several places to directly access the context provided by the parent component. For example, constructor:
constructor(props, context)
Such as the life cycle:
componentWillReceiveProps(nextProps, nextContext)
shouldComponentUpdate(nextProps, nextState, nextContext)
componetWillUpdate(nextProps, nextState, nextContext)
For function-oriented stateless components, the component Context can be accessed directly through the parameters of the function.
const StatelessComponent = (props, context) => ( ...... ) Copy the codeCopy the code
This is the basis for Context, but more detailed guidance can be found here
My understanding of Context
OK, after the basics, let’s talk about my understanding of the React Context.
Think of Context as a component scope
React App developers know that a React App is essentially a tree of React components. Each React component is equivalent to a node in the tree. In addition to the root node of the App, each node has a parent component chain.
For example, the parent chain of
The tree of connected component nodes actually forms a Context tree. Each node’s Context comes from the Context object provided by getChildContext(), which is a combination of all component nodes in the parent chain.
Have know JS scope chain concept developer should all know, JS code block during execution, will create a corresponding scope chain, the scope chain recording the runtime JS code block during execution can access the activities of the object, including variables and functions, JS program through the scope chain access to the code block variables and functions of the internal or external.
Using the JS chain of scopes as an analogy, the React component provides the Context as a scope for child components to access, and the properties of the Context can be thought of as live objects in the scope. Since a component’s Context is composed of the Context objects returned by all components in the parent chain via getChildContext(), a component can access the properties of the Context provided by all components in the parent chain.
So, I borrowed the JS scope chain idea and used the Context as the scope of the component.
Focus on the controllability and scope of Context
However, the concept of Context as a component scope is different from the common concept of scope (in terms of the programming languages I personally work with so far). We need to focus on the controllability and scope of Context.
It’s common, natural, and even insensitive to use a scope or Context in your development. However, using a Context in React is not that easy. The parent component that provides the Context needs to be “declared” by childContextTypes, and the child component that uses the parent’s Context property needs to be “claimed” by contextTypes. Therefore, I consider the React Context to be a “privileged” component scope **.
What are the benefits of this “with permissions” approach? From my personal understanding, the first is to keep the framework API consistent and use the declarative coding style as propTypes does. In addition, you can ensure some degree of controllability and scope of the Context provided by the component.
React App components are a tree structure that extends layer by layer. Parent and child components are linearly dependent on each other. Arbitrary use of Context can actually break this dependency, resulting in unnecessary additional dependencies between components, reducing component reusability, and potentially affecting App maintainability.
As you can see, the
In my opinion, exposing data or apis through Context is not an elegant practice, although react-Redux does this. Therefore, a mechanism, or constraint, is needed to reduce unnecessary impact.
The constraints of the static properties childContextTypes and contextTypes ensure that only the component itself or any other child component associated with the component can access the Context’s properties, whether data or functions. Because only the component itself or related child components can know which Context properties it can access, as opposed to other components that are not related to the component, internal or external, because it is not clear what Context properties are “declared” by childContextTypes of each parent component in the parent chain. Therefore, there is no way to “apply” the associated attribute via contextTypes. Giving a component’s scoped Context “permission” ensures that the Context is controlled and scoped to some extent.
When developing components, we should always be aware of this and not use the Context arbitrarily.
You don’t have to use Context first
As the React advanced API, React does not recommend using Context as a priority. My understanding is:
Context
It is still in the experimental stage and may be subject to major changes in future releases. In fact, this has already happened, so it is not recommended to use in the App in order to avoid major impact and trouble in future upgradesContext
.- Although not recommended for use in apps
Context
However, for components, as the influence range is smaller than App, it can still be considered if high cohesion can be achieved without destroying the dependency of component treeContext
. - For data communication between components or state management, this is preferred
props
orstate
Solution, and then consider using other third-party mature library solution, the above method is not the best choice, then consider usingContext
. Context
The update needs to passsetState()
Trigger, but it’s not reliable.Context
Cross-component access is supported, however, if the intermediate child component does not respond to updates by some means, such asshouldComponentUpdate()
returnfalse
Then there is no guaranteeContext
The update must be accessible for useContext
Child component of. As a result,Context
Reliability needs attention. However, the update issue is resolved in the new VERSION of the API.
In short, it’s okay to use a Context as long as you make sure it’s controllable, and even more so if used properly, Context can actually be a powerful experience for React component development.
Use Context as the medium for sharing data
Context can be used to communicate data across components. And I think of it as, like, a bridge, as a medium for data sharing. There are two types of data sharing: app-level and component-level.
- App-level data sharing
The Context object provided by the App root component can be thought of as the global scope at the App level. Therefore, we use the Context object provided by the App root component to create some global data at the App level. For example, see React-redux. Here is the core implementation of the
export function createProvider(storeKey = 'store', subKey) { const subscriptionKey = subKey || `${storeKey}Subscription` class Provider extends Component { getChildContext() { return { [storeKey]: this[storeKey], [subscriptionKey]: null } } constructor(props, context) { super(props, context) this[storeKey] = props.store; } render() { return Children.only(this.props.children) } } // ...... Provider.propTypes = { store: storeShape.isRequired, children: PropTypes.element.isRequired, } Provider.childContextTypes = { [storeKey]: storeShape.isRequired, [subscriptionKey]: SubscriptionShape,} return Provider} export default createProvider() copies the codeCopy the code
When the root component of an App is wrapped with the
- Component-level data sharing
If the functionality of a component cannot be accomplished by the component itself and requires additional child components, you can use Context to build a component composed of multiple child components. For example, react-router.
The < router /> of the React-Router cannot operate and manage routes independently, because navigation links and jumps are usually separated. Therefore, the
and
To better understand the above, let’s extract the source code of
// Router.js /** * The public API for putting history on context. */ class Router extends React.Component { static propTypes = { history: PropTypes.object.isRequired, children: PropTypes.node }; static contextTypes = { router: PropTypes.object }; static childContextTypes = { router: PropTypes.object.isRequired }; getChildContext() { return { router: { ... this.context.router, history: this.props.history, route: { location: this.props.history.location, match: this.state.match } } }; } / /... componentWillMount() { const { children, history } = this.props; / /... this.unlisten = history.listen(() => { this.setState({ match: this.computeMatch(history.location.pathname) }); }); } / /... } Duplicate codeCopy the code
Although there is other logic in the source code, the essence of
// Link.js /** * The public API for rendering a history-aware <a>. */ class Link extends React.Component { // ...... static contextTypes = { router: PropTypes.shape({ history: PropTypes.shape({ push: PropTypes.func.isRequired, replace: PropTypes.func.isRequired, createHref: PropTypes.func.isRequired }).isRequired }).isRequired }; handleClick = event => { if (this.props.onClick) this.props.onClick(event); if ( ! event.defaultPrevented && event.button === 0 && ! this.props.target && ! isModifiedEvent(event) ) { event.preventDefault(); // Use the Router instance provided by the <Router /> component const {history} = this.context. Router; const { replace, to } = this.props; if (replace) { history.replace(to); } else { history.push(to); }}}; render() { const { replace, to, innerRef, ... props } = this.props; / /... const { history } = this.context.router; const location = typeof to === "string" ? createLocation(to, null, null, history.location) : to; const href = history.createHref(location); return ( <a {... props} onClick={this.handleClick} href={href} ref={innerRef} /> ); }} Copy the codeCopy the code
The core of
isto render the tag, intercept the click event of the tag, and then route history through the shared Router of
// Route.js /** * The public API for matching a single path and rendering. */ class Route extends React.Component { // . state = { match: this.computeMatch(this.props, this.context.router) }; // Computes the matching path, returns a matching object, Otherwise return null computeMatch({computedMatch, location, path, strict, exact, sensitive}, router ) { if (computedMatch) return computedMatch; / /... const { route } = router; const pathname = (location || route.location).pathname; return matchPath(pathname, { path, strict, exact, sensitive }, route.match); } / /... render() { const { match } = this.state; const { children, component, render } = this.props; const { history, route, staticContext } = this.context.router; const location = this.props.location || route.location; const props = { match, location, history, staticContext }; if (component) return match ? React.createElement(component, props) : null; if (render) return match ? render(props) : null; if (typeof children === "function") return children(props); if (children && ! isEmptyChildren(children)) return React.Children.only(children); return null; }} Copy the codeCopy the code
The react-router is built around a < router /> Context.
Develop components using Context
Previously, we developed a simple component with Context, slot distribution components. This chapter discusses how to use Context for component development using this slot distribution experience.
Slot distribution component
Let’s start with what a slot distribution component is, a concept that was first introduced in Vuejs. Slot distribution is a technique of inserting the content of a parent component into a child component template through composition of components, called Slot in Vuejs.
To give you a more intuitive understanding of this concept, I carried a Demo of slot distribution from Vuejs.
For the supplied slot component
<div> <h2> I am the title of the subcomponent </h2> <slot> and only show the </slot> </div> copy code if there is nothing to distributeCopy the code
For the parent component, the template is as follows:
<div> <h1> I'm the title of the parent component </h1> <my-component> <p> Here's some initial content </p> <p> Here's more initial content </p> </my-component> </div> Copy the codeCopy the code
Final render result:
< div > < h1 > my father is the title of the component < / h1 > < div > < h2 > I am a child component of title < / h2 > < p > this is some of the initial content < / p > < p > this is more of the initial content < / p > < / div > < / div > duplicate codeCopy the code
You can see that the
Vuejs also supports named slots.
For example, a layout component
<div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot Name ="footer"></slot> </footer> </div> Copy codeCopy the code
And in the parent component template:
<app-layout> <h1 slot="header"> Here may be a page title </ H1 > < P > a paragraph of the main content. </p> <p> Another paragraph. </p> <p slot="footer"> Here is some contact information </p> </app-layout> copy codeCopy the code
Final render result:
<div class="container"> <header> <h1> Here might be a paragraph of the main content of a page header </h1> </header> <main> <p>. </p> <p> Another paragraph. < / p > < / main > < footer > < p > here are some contact information < / p > < footer > < / div > duplicate codeCopy the code
The advantage of slot distribution is that it allows components to be abstractable into templates. The component itself only cares about the template structure, leaving the details to the parent component without breaking the syntax that HTML uses to describe the DOM structure. I think it’s a good technology, but React isn’t very friendly to support it. Therefore, I developed a set of slot distribution components based on React by referring to Vuejs’ slot distribution component. The React component can also be templated.
For the
class AppLayout extends React.Component { static displayName = 'AppLayout' render () { return ( <div class="container"> <header> <Slot name="header"></Slot> </header> <main> <Slot></Slot> </main> <footer> <Slot name="footer"></Slot> </footer> </div>)}} Copy the codeCopy the code
For outer use, it can be written like this:
<AppLayout> <AddOn slot="header"> <h1> Here may be a page title </h1> </AddOn> <AddOn> <p> a paragraph of the main content. </p> <p> Another paragraph. </p> </AddOn> <AddOn slot="footer"> <p> Here is some contact information </p> </AddOn> </AppLayout> Copy codeCopy the code
Component implementation ideas
Based on the previous thoughts, let’s organize the implementation ideas.
As you can see, the Slot distribution component relies on two child components — the Slot component
Obviously, there is a problem here. The
For the
class AppLayout extends React.Component { static displayName = 'AppLayout' render () { return ( <div class="container"> <header> <Slot name="header"></Slot> </header> <main> <Slot></Slot> </main> <footer> <Slot name="footer"></Slot> </footer> </div>)}} Copy the codeCopy the code
For outer use, it is written like this:
<AppLayout> <AddOn slot="header"> <h1> Here may be a page title </h1> </AddOn> <AddOn> <p> a paragraph of the main content. </p> <p> Another paragraph. </p> </AddOn> <AddOn slot="footer"> <p> Here is some contact information </p> </AddOn> </AppLayout> Copy codeCopy the code
Both
The
Implement slot distribution components according to the idea
Since
For
class AppLayout extends React.Component { static childContextTypes = { requestAddOnRenderer: Proptypes.func} // used to cache the contents of each <AddOn /> addOnRenderers = {} // provides an interface to getChildContext () {const via Context requestAddOnRenderer = (name) => { if (! this.addOnRenderers[name]) { return undefined } return () => ( this.addOnRenderers[name] ) } return { requestAddOnRenderer } } render () { const { children, ... RestProps} = this. Props if (children) {// Cache <AddOn /> in k-v const arr = react.children. ToArray (children) const nameChecked = [] this.addOnRenderers = {} arr.forEach(item => { const itemType = item.type if (item.type.displayName === 'AddOn') { const slotName = item.props.slot || '? If (namechecked.findIndex (item => item === stubName)! == -1) { throw new Error(`Slot(${slotName}) has been occupied`) } this.addOnRenderers[stubName] = item.props.children nameChecked.push(stubName) } }) } return ( <div class="container"> <header> <Slot name="header"></Slot> </header> <main> < Slot > < / Slot > < / main > < footer > < Slot name = "footer" > < / Slot > < footer > < / div >)}} to copy codeCopy the code
The implementation of
// props, context const Slot = ({ name, children }, { requestAddOnRenderer }) => { const addOnRenderer = requestAddOnRenderer(name) return (addOnRenderer && addOnRenderer()) || children || null } Slot.displayName = 'Slot' Slot.contextTypes = { requestAddOnRenderer: Proptypes.func} slot. PropTypes = {name: proptypes.string} slot. defaultProps = {name: '? Default '} copies the codeCopy the code
const AddOn = () => null AddOn.propTypes = { slot: PropTypes.string } AddOn.defaultTypes = { slot: '? Default '} AddOn. DisplayName = 'AddOn' copy codeCopy the code
Can be made more universal
I named this component SlotProvider
function getDisplayName (component) { return component.displayName || component.name || 'component' } const slotProviderHoC = (WrappedComponent) => { return class extends React.Component { static displayName = `SlotProvider(${getDisplayName(WrappedComponent)})` static childContextTypes = { requestAddOnRenderer: Proptypes.func} // used to cache the contents of each <AddOn /> addOnRenderers = {} // provides an interface to getChildContext () {const via Context requestAddOnRenderer = (name) => { if (! this.addOnRenderers[name]) { return undefined } return () => ( this.addOnRenderers[name] ) } return { requestAddOnRenderer } } render () { const { children, ... RestProps} = this. Props if (children) {// Cache <AddOn /> in k-v const arr = react.children. ToArray (children) const nameChecked = [] this.addOnRenderers = {} arr.forEach(item => { const itemType = item.type if (item.type.displayName === 'AddOn') { const slotName = item.props.slot || '? If (namechecked.findIndex (item => item === stubName)! == -1) { throw new Error(`Slot(${slotName}) has been occupied`) } this.addOnRenderers[stubName] = item.props.children nameChecked.push(stubName) } }) } return (<WrappedComponent {... RestProps} />)}}} export const SlotProvider = slotProviderHoC Copy codeCopy the code
React uses higher-level components to transform the original
import { SlotProvider } from './SlotProvider.js' class AppLayout extends React.Component { static displayName = 'AppLayout' render () { return ( <div class="container"> <header> <Slot name="header"></Slot> </header> <main> <Slot></Slot> </main> <footer> <Slot name="footer"></Slot> </footer> </div> ) } } export default SlotProvider(AppLayout) Copy the codeCopy the code
As you can see from the above experience, when designing and developing a component,
- Components may require a root component and children to work together to accomplish component functionality. For example, the slot distribution component actually needs
SlotProvider
with<Slot />
and<AddOn />
When used together,SlotProvider
As the root component, while<Slot />
and<AddOn />
They are all child components. - The position of the child components relative to the root component or between the child components is uncertain. for
SlotProvider
In terms of<Slot />
The position of theta is indeterminate, it will be at the point of beingSlotProvider
Any location in the template of the component wrapped by this higher-order component, while for<Slot />
and<AddOn />
Their immediate location is also uncertain. One is inSlotProvider
Packaging the components inside, another isSlotProvider
thechildren
. - Subcomponents need to rely on some global API or data between them, such as
<Slot />
The actual rendered content comes fromSlotProvider
The collected<AddOn />
The content of the.
This is where we need an intermediary to share the data. It is more elegant to use the Context directly than to introduce additional third-party modules such as Redux.
Try the new version of the Context API
Revamp the previous slot distribution component using a new version of the Context API.
// SlotProvider.js function getDisplayName (component) { return component.displayName || component.name || 'component' } export const SlotContext = React.createContext({ requestAddOnRenderer: () => {} }) const slotProviderHoC = (WrappedComponent) => { return class extends React.Component { static displayName = 'SlotProvider(${getDisplayName(WrappedComponent)})' // Used to cache each <AddOn /> content addOnRenderers = {} requestAddOnRenderer = (name) => { if (! this.addOnRenderers[name]) { return undefined } return () => ( this.addOnRenderers[name] ) } render () { const { children, ... RestProps} = this. Props if (children) {// Cache <AddOn /> in k-v const arr = react.children. ToArray (children) const nameChecked = [] this.addOnRenderers = {} arr.forEach(item => { const itemType = item.type if (item.type.displayName === 'AddOn') { const slotName = item.props.slot || '? If (namechecked.findIndex (item => item === stubName)! == -1) { throw new Error(`Slot(${slotName}) has been occupied`) } this.addOnRenderers[stubName] = item.props.children nameChecked.push(stubName) } }) } return ( <SlotContext.Provider value={ requestAddOnRenderer: this.requestAddOnRenderer }> <WrappedComponent {... RestProps} /> </ SlotContext.provider >)}}} export const SlotProvider = slotProviderHoC Copy codeCopy the code
The previous childContextTypes and getChildContext() have been removed. Other than local adjustments, the overall core remains the same.
// Slot.js import { SlotContext } from './SlotProvider.js' const Slot = ({ name, children }) => { return ( <SlotContext.Consumer> {(context) => { const addOnRenderer = requestAddOnRenderer(name) return (addOnRenderer && addOnRenderer()) || children || null }} </SlotContext.Consumer> ) } Slot.displayName = 'Slot' Slot.proptypes = {name: proptypes.string} slot.defaultprops = {name: '? Default '} Copies the codeCopy the code
Since the Context was previously used in a producer-consumer fashion, and the components themselves are relatively simple, the transformation using the new API makes little difference.
conclusion
- Compared with the
props
andstate
, the ReactContext
Component communication can be achieved across hierarchies. - The use of the Context API is based on the producer-consumer pattern. Producer side, via component static properties
childContextTypes
Declare, and then pass the instance methodgetChildContext()
createContext
Object. On the consumer side, through component static propertiescontextTypes
It’s for the applicationContext
Property, and then through the instancecontext
accessContext
Properties. - use
Context
It requires a bit of thought and is not recommended for use in appsContext
, but if the development process of components can ensure the cohesion of components, controllable and maintainable, does not destroy the dependency of the component tree, the impact range is small, it can be consideredContext
Fix some problems. - through
Context
Exposing API may bring convenience to solve some problems to some extent, but I personally think it is not a good practice and should be cautious. - The old version
Context
The update of the dependencysetState()
Is unreliable, but this problem is fixed in the new VERSION of the API. - Can put the
Context
Think of it as the scope of a component, but with concernContext
Before use, analyze whether it is really necessary to use and avoid some side effects caused by overuse. - Can put the
Context
Use as a medium for App – or component-level data sharing. - Design and develop a component, if the component needs to be associated with multiple components, use
Context
Maybe it could be more elegant.
From: juejin. Cn/post / 684490…
What happens when you manipulate a primitive array in a WEBGL JS loop? React ShoudComponentUpdate React ShoudComponentUpdate Stop rendering vue why don’t you use 11 methods for deep copy shallow copy Vue vue $nextTick Deep parsing Deep Revealing Promise microtask registration and execution Process How does the V8 engine execute a section of JS code? Http3.0 2.0 1.0 compare macro tasks and micro tasks in detail