A preface

React design patterns React design patterns React design patterns React design patterns React design patterns React design patterns React design patterns React

Basically, for every design pattern, I would rack my brains to come up with two demos, hoping that you in front of the screen would give me a thumbs up to encourage me to continue to create front-end hard text.

As usual, we begin today’s reading with a question:

  • 1 What are the common design patterns for React?
  • 2 The combination mode is powerful and applies to different scenarios.
  • 3 What should developers be aware of using render props?
  • 4 application scenarios of Hoc mode.
  • 5 Provider mode Implements state management and state delivery.
  • 6 How to use the inheritance pattern, and what are its advantages and disadvantages?

I’m sure all these problems will be solved by reading this article.

First of all, why study design patterns? The reasons are summarized in the following aspects.

  • Complex functions, logic reuse problems.

First of all, React is flexible, which means that React projects can apply multiple design patterns. But these design patterns do do something:

Scene 1:

In a project, there is a global state, called theme (theme), so many UI components need this theme, and this theme can be toggled, just like github toggled dark mode, so how to gracefully implement this feature?

This scenario would have been easy if we had used the React Provider mode. We stored the global theme through the context and passed the theme through the Provider. If we needed the theme, we would have consumed the context. Whenever the theme changes, the component consuming the context is updated again, achieving the purpose of switching the theme.

Scene 2:

Form design scenarios also need React design mode to some extent. First, the overall verification of Form state requires outer Form binding event control, scheduling of Form state delivery, and verification functions. The inner layer also needs the FormItem to collect data for each form control to make the control controlled. This Form and FormItem approach is implemented through the composite pattern.

  • 2. Develop design ability and programming ability

Smart use of the React design pattern can cultivate the design ability of developers, such as HOC design, common component design, custom hooks design, some open source libraries are implemented through the flexibility and excellent design pattern of React.

Example 1:

For example, in the React state management tool, whether react-Redux or Mobx-React, on the one hand, you want to pass the state and dispatch functions to the component, and on the other hand, you want to subscribe to the state changes to force the business component to update. It takes one or more HOC to do it. Thus, React-Redux provides connect, mobx-React provides Inject, Observer and other excellent hoc. As a result, learning the React design pattern can help developers from writing common components to developing open source projects.

Today I’m going to focus on five design patterns for React:

  • Portfolio model
  • Render props model
  • Hoc mode
  • Provider pattern
  • Class component inheritance

Dual mode

1 introduction

The combination mode is suitable for some container component scenarios. The outer component wraps the inner component, which is called slot slot in Vue. The outer component can easily obtain the props state of the inner component and control the rendering of the inner component. Let me start with the simplest combination pattern example 🌰.

<Tabs onChange={ (type) = > console.log(type)  } >
    <TabItem name="react"  label="react" >React</TabItem>
    <TabItem name="vue" label="vue" >Vue</TabItem>
    <TabItem name="angular" label="angular"  >Angular</TabItem>
</Tabs>
Copy the code

The above combination of Tabs and TabItem forms the TAB switching function. Then the division of Tabs and TabItem is as follows:

  • Tabs are responsible for displaying and controlling the corresponding TabItem. Bind toggle TAB callback method onChange. When a TAB is switched, a callback is performed.
  • TabItem displays TAB items and passes information about props to Tabs.

Intuitively, we can see that Tabs and TabItem are not related, but they are invisibly related. This is the essence of the composite pattern, where the components of the composite pattern are comfortable for the user because most of the work is done while the composite component is being developed. So writing nested components for composite patterns is a great way to exercise your React component encapsulation capabilities.

Let’s take a look at how the composition pattern is implemented internally.

2. Principle revealed

The realization of the actual combination mode is not as complex as imagined, mainly divided into outer layer and inner layer two parts, of course, there may be multi-layer combination nesting, but all changes are the same, the principle is the same. First let’s look at a simple composition structure:

<Groups>
    <Item  name=React Advanced Practice Guide />
</Groups>
Copy the code

thenGroupsBe able toItemWhat are we going to do?

Item form in Groups

JSX compiles the React element, and the Item can be accessed through the Groups props. Children function.

function Groups (props){
    console.log( props.children  ) // Groups element
    console.log( props.children.props ) // {name: 'React Advanced Practice guide '}
    return  props.children
}
Copy the code

But this is for a single node; in reality, the outer container may have multiple child components.

<Groups>
    <Item  name=React Advanced Practice Guide />
    <Item name="Nodejs Deep Learning Manual" />
</Groups>
Copy the code

In this case, props. Children is an array structure, and if you want to access each of the props, you need to traverse the props. Children through React.

function Groups (props){
    console.log( props.children  ) // Groups element
    React.Children.forEach(props.children,item= >{
        console.log( item.props )  // Print props in sequence
    })
    return  props.children
}
Copy the code

Implicitly mixed with props

This is the essence of the composite pattern. It is possible to mix other props into the children using the React. CloneElement, so that the children can use the specific props provided by the container’s parent. Let’s look at the implementation:

function Item (props){
    console.log(props) // {name: "React Advanced Practice Guide ", author: "alien"}
    return <div>Name: {props. The name}</div>
}

function Groups (props){
    const newChilren = React.cloneElement(props.children,{ author:'alien' })
    return  newChilren
}
Copy the code
  • withReact.cloneElementCreate a new element and mix in the other props -> author property. The second parameter of the React. CloneElement will merge with the previous props.

In this case, Groups only has a single node. Some students will ask if they can add a new attribute to the original children. Something like this:

props.children.props.author = 'alien'
Copy the code
  • The React and props are protected, but we cannot extend the props. So to embed props implicitly, you have to passcloneElementTo implement.

Control the rendering

The composite mode can take the inner component by children and control its rendering based on the state of the inner component. For example, the following situation:

export default() = > {return <Groups>
    <Item  isShow name=React Advanced Practice Guide />
    <Item  isShow={false} name="Nodejs Deep Learning Manual" />
    <div>hello,world</div>
    { null }
</Groups>
}
Copy the code
  • In the case above, combine modes and render onlyisShow = trueThe Item component of. What about the outer components?

The actual handling of this is simple, again by traversing children and then selecting the children to render by comparing props. Let’s take a look at how to control:

function Item (props){
    return <div>Name: {props. The name}</div>
}
/* Groups component */
function Groups (props){
    const newChildren = []
    React.Children.forEach(props.children,(item) = >{
        const { type ,props } = item || {}
        if(isValidElement(item) && type === Item && props.isShow  ){
            newChildren.push(item)
        }
    })
    return  newChildren
}
Copy the code
  • throughnewChildrenStore the React Element that meets the requirements and passChildren.forEachThrough the children.
  • throughisValidElementExclude non-element nodes;typePoint to theItemFunction memory, excluding non-item elements; Get isShow property, show only isShow = trueItem, the final effect meets the requirements.

Inner and outer layer communication

The combination mode can easily realize the scene of communication between inner and outer layers. The principle is to pass the callback function to the inner layer through the outer component, and the inner layer realizes the communication relationship between the two layers by calling callback.

function Item (props){
    return <div>Name: {props. The name}<button onClick={()= >props.callback('let us learn React! > click ')}</button>
    </div>
}

function Groups (props){
    const handleCallback = (val) = >  console.log('Children content:',val )
    return <div>
        {React.cloneElement( props.children , { callback:handleCallback } )}
    </div>
}
Copy the code
  • GroupsItemComponent implicitly passes in callback functionscallback, will be passed as the new props.
  • ItemYou can do this by callingcallbackGroupsTransmit information. The communication between inner and outer layers is realized.

Complex combination scenarios

Composition mode also has a scenario where the composition takes place again in the outer container, so that the components are wrapped layer by layer and strengthened over and over again. Here’s an example:

function Item (props){
    return <div>Name: {props. The name}<br/>Author: {props. The author}<br/>{props. Mes}<br/>
    </div>
}
/* Second layer combination -> mix in the mes attribute */
function Wrap(props){
    return React.cloneElement( props.children,{ mes:'let us learn React! '})}/* Add the author attribute */
function Groups (props){
    return <Wrap>
        {React.cloneElement( props.children, { author:'alien' } )}
    </Wrap>
}

export default() = > {return <Groups>
    <Item name=React Advanced Practice Guide />
</Groups>
}
Copy the code
  • inGroupsThrough the componentWrapAnd then you combine them. After two combinations, let’sauthormesInterfuse into props.

This combination pattern reinforces the original component layer by layer, and the outer component doesn’t care so much about what the inner layer is doing. You only need to deal with children, and the inner children can also use the state, methods, and so on from the outer container components in addition to the props of the business layer.

3. Attention to detail

There are also many details worth noting in the combined mode. The first one that should come to mind is the type validation for children. Because of the combined mode, the property state of the outer container component for children is unknown. If a child is mounted directly in an uncertain state, an error will occur. So it’s very important to verify the legitimacy of children.

Validation of the children

For example, the form is essentially the render props form.

<Groups>
   {() = >  <Item  isShow name=React Advanced Practice Guide />}
</<Groups>
Copy the code

This is the case if Groups is mounted directly with children.

function Groups (props){
    return props.children
}
Copy the code

Functions are not valid as a React child. Let’s look at Groups:

function Groups (props){
    return  React.isValidElement(props.children)
     ? props.children
     : typeof props.children === 'function' ?
       props.children() : null
}
Copy the code
  • First check whether children are react. element. If so, render directly. If not, then check whether children are functionsnullThat’s it.

Bind static properties

There is also the question of how the outer and inner components are identified. For example, the following scenario:

<Groups>
   <Item  isShow name=React Advanced Practice Guide />
   <Text />
<Groups>
Copy the code

As follows, there are two components inside Groups, one is Item, the other is Text, but only Item is useful, so how to prove Item component? We need to bind static properties to the component function or class, and we can use displayName to identify the component.

So just do this:

function Item(){... } Item.displayName ='Item'
Copy the code

The corresponding Item component can be found in Groups, excluding the Text component. You can use the type property on children to find the corresponding function or class, and then you can use the displayName property on Type to find the corresponding Item component. Essentially, displayName is used for debugging, so remember the combination, You can just use the static properties of the child component. Of course you can do the same with memory space.

Specific reference methods:

function Groups (props){
    const newChildren = []
    React.Children.forEach(props.children,(item) = >{
        const { type ,props } = item || {}
        if(isValidElement(item) && type.displayName === 'Item' ){
            newChildren.push(item)
        }
    })
    return  newChildren
}
Copy the code

Find the Item using the displayName property.

4 practice demo

Next, let’s simply implement the TAB and tabItem switching function we started with.

TAB to realize

const Tab = ({ children ,onChange }) = > {
    const activeIndex = useRef(null)
    const [,forceUpdate] = useState({})
    /* Provide for TAB to use */
    const tabList = []
    /* Components to render */
    let renderChildren = null
    React.Children.forEach(children,(item) = >{
        /* Verify that it is a 
      
        component */
      
        if(React.isValidElement(item) && item.type.displayName === 'tabItem') {const { props } = item
            const { name, label } = props
            const tabItem = {
                name,
                label,
                active: name === activeIndex.current,
                component: item
            }
            if(name === activeIndex.current) renderChildren = item
            tabList.push(tabItem)
        }
    })
    /* First load, or prop Chuldren changes */
    if(! renderChildren && tabList.length >0) {const fisrtChildren = tabList[0]
        renderChildren = fisrtChildren.component
        activeIndex.current = fisrtChildren.component.props.name
        fisrtChildren.active = true
    }

    /* Switch TAB */
    const changeTab=(name) = >{
        activeIndex.current = name
        forceUpdate({})
        onChange && onChange(name)
    }

    return <div>
        <div className="header"   >
            {
                tabList.map((tab,index) => (
                    <div className="header_item" key={index}  onClick={()= > changeTab(tab.name)} >
                        <div className={'text'}  >{tab.label}</div>
                        {tab.active && <div className="active_bored" ></div>}
                    </div>))}</div>
        <div>{renderChildren}</div>
    </div>
}

Tab.displayName = 'tab' 
Copy the code

The TabI wrote is responsible for the main functions of the whole Tab switch, including TabItem filtering, state collection, and control of the display of corresponding sub-components.

  • First of all byChildren.forEachFind the ones that fit the billTabItem. collectTabItemProps, forming the menu structure.
  • Find the correspondingchildrenRender correctly for children.
  • Provides methods to change tabschangeTab.
  • DisplayName tagTabComponents. The main purpose of this is to facilitate debugging.

The realization of the TabItem

const TabItem = ({ children }) = > {
    return <div>{children}</div>
}
TabItem.displayName = 'tabItem'
Copy the code

The TabItem function in this demo is very simple, leaving most of the work to the Tab.

What TabItem does is:

  • showchildren(What we write in TabItem)
  • Bind static propertiesdisplayName

The effect

5 concludes

Composite mode is widely used in daily development, especially in some excellent open source projects. The summary content of composite mode is as follows:

  • The composite mode gets the children from the inner component through the outer component, passes in a new state through the cloneElement, or controls the rendering of the inner component.
  • The combination mode can also be combined with other components, or render props, which are very extensible and achieve powerful functions.

The summary flow chart is as follows:

Three Render props mode

1 introduction

The Render Props mode is similar to the composite mode. The difference is, instead of children, we have a function. Function parameters are provided by the container component, and the benefit of raising the state of the container component to the current outer component is a neat thing, and one of the biggest differences from the composite pattern.

Let’s take a look at what a basic render props looks like:

export default function App (){
    const aProps = {
        name:'React Advanced Practice Guide'
    }
    return <Container>
        {(cProps) => <Children {. cProps} { . aProps} / >}
    </Container>
}
Copy the code

This is the basic look of render props. You can clearly see:

  • cPropsContainerThe state provided by the component.
  • aPropsAppThe status provided. The advantage of this mode is that the state of the App’s child Container can be promoted to the App’s render function. This can then be combined into new props and passed to Children, which makes the containerization feel more obvious.

Let’s look at the render props principles and details.

2 Principles and details

First of all, what kind of scenarios is the render props method suitable for? In fact, this mode is more suitable for obtaining container packaging state. Maybe some of you don’t understand. So let’s look at Consumer in context. Use the Render props mode.

const Context = React.createContext(null)
function Index(){
    return <Context.Consumer>
           {(contextValue)=><div>Name: {contextvalue.name} Author: {contextValue.author}</div>}
         </Context.Consumer>
}

export default function App(){
    const value = {
        name:'React Advanced Practice Guide'.author:'I'm not an alien'
    }
    return <Context.Provider value={value} >
        <Index />
    </Context.Provider>
}
Copy the code
  • We see that the Consumer is a container component that wraps the content to be rendered and then performs the state through the Children Render functioncontextValueFrom downstream to upstream.

So let’s simulate the internal implementation of Consumer.

function myConsumer(props){
    const contextValue = useContext(Context)
    return props.children(contextValue)
}
Copy the code

The render props is essentially a container component that generates state, which is then passed on through the children function. So in this pattern we should be more concerned with, what can container components provide?

Derived new state

Compared to the traditional combination mode, another flexibility is that render props can combine the state of the container component with the state of the current component to derive new states. Such as the following

 <Container>
        {(cProps) = > {
            const  const nProps =  getNewProps( aProps , cProps )
            return <Children {. nProps} / >
        }}
 </Container>
Copy the code
  • NProps is the status calculated by combining the component status aProps and Container component cProps.

Reverse state return

This is an extreme case, and I have also used this method, where the state in the render props can be promoted to the current component, passing the state in the container component to the parent component. Such as the following.

function GetContanier(props){
    const dom = useRef()
    const getDom = () = >  dom.current
    return <div ref={dom} >
        {props.children({ getDom })}
    </div>
}

export default function App(){
     /* Save the state returned by render props */
     const getChildren = useRef(null)
     useEffect(() = >{
        const childDom = getChildren.current()
        console.log( childDom,'childDom' )
     },[])
    return <GetContanier>
        {({getDom})=>{
            getChildren.current = getDom
            return <div></div>
        }}
    </GetContanier>
}
Copy the code

  • This is a complex state back scene inGetContanierMethod to get the elementgetDomBack to the parent component via render props.
  • The parent component App passesgetChildrenSave the content returned by render props inuseEffectCall the getDom method and print the following:

In real life, however, retrieving a DOM is not as simple as this. In real life, the content returned may be more complex.

3 Pay attention to problems

Render props is a function that needs to be verified to ensure that children are functions. In this case, functions can be performed and props can be passed. For example:

function Container (props){
    const renderChildren =  props.children
    return typeof renderChildren === 'function' ? renderChildren({ name:'React Advanced Time Guide' }) : null
}
export default function App(){
    return <Container>
        {(props)=> <div>Name: {props. The name}</div>}
    </Container>
}
Copy the code
  • throughtypeofjudgechildrenIs a function, and if it’s a function, then execute the function and pass props.

4 practice demo

So let’s implement a demo. Render props To implement a container component with loading. Wrapped by the container component, the method for enabling loading is passed back to the render props.

Container Component Container

function Container({ children }){
   const [ showLoading, setShowLoading ] = useState(false)
   const renderChildren = useMemo(() = > typeof children === 'function' ? children({ setShowLoading }) : null  ,[children] )
   return <div style={{ position:'relative'}} >
     {renderChildren}
     {showLoading &&  <div className="mastBox" >
          {<SyncOutlined  className="icon"  spin twoToneColor="#52c41a" />}
     </div>}
   </div>
}
Copy the code
  • useStateUsed to display loading effects, useMemo used to executechildrenThe setShowLoading method, which changes state, is passed to the props. One benefit is that useState does not trigger when it changeschildrenThe rendering.
  • throughshowLoadingTo display the loading effect.

The outer use

export default function Index(){
    const setLoading = useRef(null)
    return <div>
        <Container>{({setShowLoading})=>{console.log(' render ') setloading.current = setShowLoading return<div>
                     <div className="index1" >
                         <button onClick={()= > setShowLoading(true)} >loading</button>
                     </div>
                </div>
            }}
        </Container>
        <button onClick={()= >Setloading.current && setloading.current (false)} > Cancel loading</button>
    </div>
}
Copy the code
  • By directly callingsetShowLoading(true)The loading effect is displayed.
  • Save state with useRef setShowLoading,ContainerThe outer layer can also call setShowLoading to make the loading effect disappear.

The effect

5 concludes

Next we summarize the features of render Props.

  • The container component passes state and executes the children function.
  • The outer component can pass back props based on the container component, and then pass the props combination to the child component.
  • Outer components can use container components to return state.

The schematic in this mode is shown below:

Four hoc mode

1 introduction

Hoc high-order component mode is also one of the packaging reinforcement modes commonly used in React. High-order function is to receive a function and return a function, while the so-called high-order component is to receive a component and return a component, and the returned component is to strengthen the original component according to the needs.

Let’s look at the general pattern of Hoc. Hoc is essentially a function.

function Hoc (Component){
    return class Wrap extends React.Component{
        //---------
        // enhance the operation
        //---------
        render(){
            return <Component { . this.props} / >}}}Copy the code

With traditional HOC mode as above, we can see exactly what a traditional HOC does.

  • 1 HOC is essentially a function passed inComponentThat is, the original component itself.
  • Return a new wrapped component, Wrap, in which we can do something to enhance the original component.
  • 3 Wrap mounts the original component itselfComponent.

2 the principle of

Let’s take a look at how Hoc works. Hoc can be implemented in two ways, property proxy and reverse inheritance.

Property proxy forward property proxy is a component that wraps a layer of proxy components on top of which we can do some proxy operations on the source components. We can think of it as a parent-child relationship, where the parent performs a series of reinforcement actions on the child. Hoc itself is the parent component that returns the enhanced child component.

function HOC(WrapComponent){
    return class Advance extends React.Component{
       state={
           name: 'React Advanced Practice Guide'.author:'I'm not an alien'
       }
       render(){
           return <WrapComponent  { . this.props } { . this.state} / >}}}Copy the code

Property broker features:

  • ① Normal attribute proxy can be low coupling with business components, zero coupling, for conditional rendering andpropsProperty enhancement, which is only responsible for controlling subcomponent rendering and passing additionalpropsSo you don’t need to know what the business component does. So forward attribute proxy, more suitable for some open source projectshoc, currently open sourceHOCIt’s basically done through this pattern.
  • ② The same applies toclassDeclare components, andfunctionDeclared components.
  • ③ Can completely isolate the rendering of business components, compared to reverse inheritance, property proxy mode. You have complete control over whether business components are rendered or not, and you can avoid some of the side effects of reverse inheritance, such as lifecycle execution.
  • (4) It can be used in nesting. Multiple hoc can be used in nesting, and generally there is no limit on the sequence of packaging hoc.

Reverse inheritance

Reverse inheritance differs from property proxies in that the wrapped component inherits the business component itself, so we don’t need to instantiate our business component. Current high-level components are inherited, enhanced business components. This approach is similar to component hardening, so you have to know the state of the current inherited component. What’s going on internally?

class Index extends React.Component{
  render(){
    return <div> hello,world  </div>}}function HOC(Component){
    return class wrapComponent extends Component{ /* Directly inherits the component that needs to be wrapped */}}export default HOC(Index) 
Copy the code
  • It is easy to obtain the internal state of the component, such as state, props, lifecycle, and bound event functions
  • ② ES6 inheritance can inherit static attributes well. We don’t need to do extra processing for static properties and methods.

3 Functions and precautions

Now that we’ve seen two ways to implement hoc, what can hoc do? And hoc mode considerations.

The function of HOC

For attribute proxy HOC, we can:

  • Strengthen props & Extract state.
  • Conditional rendering, controlled rendering, Fragment rendering, lazy loading.
  • Hijackings and the life cycle.
  • Ref controls the component instance.
  • Add event listeners, logs

For reverse proxy HOC, we can:

  • Hijack render and manipulate render tree.
  • Control/replace life cycle, get component state directly, bind events.

If you’re not sure what the scenarios are for each of these features, check out another article I wrote:

HOC Considerations

  • 1. Carefully modify the prototype chain.
  • Inherit static properties. A library is recommendedhoist-non-react-staticsAutomatically copy all static methods.
  • 3 Capture across levelsrefThrough theforwardRefforwardingref.
  • 4. Do not declare in renderHOCIf hoc is declared in render, it may cause the component to repeatedly mount.

4 practice demo

Some students encountered such a question in the interview before, that is, how to control the sequence of mounting components, such as the following scenario

export default function Index(){
    return <div>
        <ComponentA />
        <ComponentB />
        <ComponentC />
    </div>
}
Copy the code

ComponentA, ComponentB, ComponentC, ComponentA, ComponentC That is, the three components are rendered and mounted in order, so how to achieve this?

In fact, this situation can be achieved with a hoc, so please follow my thinking to achieve this scenario. First of all, this hoc is to strengthen the function of a group of components ComponentA | ComponentB | ComponentC under the current index. So it’s best if hoc can be created dynamically and serves the current set of components. Declare a function factory that produces hoc.

function createHoc(){
   const renderQueue = []            /* Queue to render */
    return function Hoc(Component){  /* Component - Original Component */
        return class Wrap extends React.Component{  /* Hoc wrapper component */}}}Copy the code

So we need to first create a hoc to be used as this group of components.

Use:

const loadingHoc = createHoc()
Copy the code

Knowing the dynamic generation of hoc, let’s implement it concretely.

function createHoc(){
    const renderQueue = [] /* Queue to render */
    return function Hoc(Component){

        function RenderController(props){  /* RenderController is used to actually mount the original component */
            const{ renderNextComponent ,... otherprops } = props useEffect(() = >{
                renderNextComponent() /* Notifies the next component task to mount */}, [])return <Component  {. otherprops} / >
        }

        return class Wrap extends React.Component{
            constructor(){
                super(a)this.state = {
                    isRender:false
                }
                const tryRender = () = >{
                    this.setState({
                        isRender:true})}if(renderQueue.length === 0) this.isFirstRender = true
                renderQueue.push(tryRender)
            }
            isFirstRender = false      /* Is the first mount task in the queue */
            renderNextComponent=() = >{  /* From the update queue, mount the next task */
                if(renderQueue.length > 0) {console.log('Mount next component')
                    const nextRender = renderQueue.shift()
                    nextRender()
                }
            }
            componentDidMount(){  /* If it is the first mount task, */ is required
                this.isFirstRender && this.renderNextComponent()
            }
            render(){
                const { isRender } = this.state
                return isRender ? <RenderController {. this.props} renderNextComponent={this.renderNextComponent}  /> : <SyncOutlined   spin />}}}}Copy the code

Analyze the main processes:

  • First of all bycreateHocTo create hoc that needs to be loaded sequentially,renderQueueHold the queue to render.
  • Hoc receives the raw componentComponent.
  • RenderControllerTo actually mount the original component, use the useEffect notification to execute the next component task that needs to be mounted, as I described in the hooks principle articleuseEffectWith asynchronous execution, that is, after rendering, browser rendering is complete.
  • The Wrap component wraps a layerRenderController, mainly used for rendering update tasks,isFirstRenderIs the first mount task in the queue. If it is the first mount task, it needs to be in thecomponentDidMountStart mounting the first component.
  • Every mount task is essentiallytryRenderMethod, which calls setState to renderRenderController.
  • Function for each mount taskrenderNextComponentThe principle is very simple, is to get the first update task, and then execute.
  • Some details are left unaddressed, such as inheriting static attributes, ref forwarding, etc.

Use:

/* Create hoc */
const loadingHoc = createHoc()

function CompA(){
    useEffect(() = >{
        console.log('Component A has been mounted')
    },[])
    return <div>Component A</div>
}
function CompB(){
    useEffect(() = >{
        console.log('Component B has been mounted')
    },[])
    return <div>The component B</div>
}
function CompC(){
    useEffect(() = >{
        console.log('Component C has been mounted')
    },[])
    return  <div>Component C</div>
}

function CompD(){
    useEffect(() = >{
        console.log('Component D has been mounted')
    },[])
    return  <div>Component D</div>
}
function CompE(){
    useEffect(() = >{
        console.log('Component E has been mounted')
    },[])
    return  <div>The component of E</div>
}


const ComponentA = loadingHoc(CompA)
const ComponentB = loadingHoc(CompB)
const ComponentC = loadingHoc(CompC)
const ComponentD = loadingHoc(CompD)
const ComponentE = loadingHoc(CompE)

export default function Index(){
    const [ isShow, setIsShow ] = useState(false)
    return <div>
        <ComponentA />
        <ComponentB />
        <ComponentC />
        {isShow && <ComponentD />}
        {isShow && <ComponentE />}
        <button onClick={()= >SetIsShow (true)} > Mount components D, E</button>
    </div>
}
Copy the code

Effect:

Perfect fulfillment of needs.

5 concludes

HOC is widely used in practical projects, especially in some excellent open source projects. The schematic diagram of HOC is summarized here:

The property broker

Reverse inheritance

V. Provider Pattern

1 introduction

First of all, why does React have a provider model?

With that in mind, imagine a scenario: The React project has a global theme variable (which can be used to initialize data interactions or to switch themes), some view UI components (such as form input boxes, button buttons), We need the theme variable to render the corresponding view. The problem is how to pass the theme along and allocate it to the place where it is used.

If you use props to solve this problem, then you need to bind layers of props and consider pureComponent and Memo policies.

So this is a good time to use the provider pattern. React provides the context ‘Provider’ mode. The React component tree uses the Root node to inject the theme with the Provider Provider, and then fetches the theme as a Consumer where the theme is needed. Supply component rendering can be used, so as to reduce a lot of useless work. Context provides a way to pass data across a tree of components without manually adding props for each layer of components.

However, it must be noted that the provider is always on the top of the consumer, just as the so-called water flows downwards, the provider must be a parent of the consumer. The provider pattern looks like this:

2 Usage

The usage of the provider pattern is divided between the old version of context and the new version of context. Next, I’ll focus on two ways.

The old provider pattern

Prior to React V16.3.0, to implement the provider, you had to implement a React component, but this component had to do something special. Here is an example of implementing a “provider” component named ThemeProvider:

The provider

class ThemeProvider extends React.Component {
  getChildContext() {
    return {
      theme: this.props.value
    }
  }

  render() {
    return (
      <div>
         { this.props.children }
      </div>
    );
  }
}
ThemeProvider.childContextTypes = {
  theme: PropTypes.object
}
Copy the code
  • You need to implementgetChildContextMethod to return the context in which the data is passed to descendant components;
  • You need to definechildContextTypesProperty that declares the structure type of the context.

use

<ThemeProvider value={ { color:'pink'}} ><Index />
</ThemeProvider>
Copy the code

consumers

const ThemeConsumer = (props, context) = > {
  const {color} = context.theme
  return (
    <p style={{color}} >
      {props.children}
    </p>
  );
}

ThemeConsumer.contextTypes = {
  theme: PropTypes.object
}
Copy the code
  • The thing to notice here is that you need to passcontextTypesSpecifies which context will be consumed, otherwise it will not be valid.

New version of the provider schema

React V16.3.0 introduced a new Context API that allows developers to create a Context with two properties: Provider and Consumer.

  • ProviderUsed to provide context.
  • ConsumerUse it to consume the context.

First, the developer needs to create a context using the createContext API.

const ThemeContext = React.createContext();
Copy the code

Then there is the implementation of the new versions of Provider and Consumer.

New version of provider

function ThemeProvider(){
    const theme = { color:'pink' }
    return <ThemeContext.Provider value={ theme } >
        <Index />
    </ThemeContext.Provider>
}
Copy the code
  • throughThemeContextOn theProviderDeliver topic informationtheme
  • Index is the root component.

New Consumer

function ThemeConsumer(props){
    return <ThemeContext.Consumer>/* Render children function */ const {color} = theme return<p style={{color}} >
           {props.children}
       </p>}}</ThemeContext.Consumer>
}
Copy the code
  • The Consumer uses the render props pattern described above.
  • The Consumer subscribes to the context change, the context change, and the Render Children function is re-executed. The first argument in the Render Children function is the saved context information.
  • In the new consumer, there are also functions for componentsuseContextCustom hooks, for class componentscontextTypeStatic properties.

Practice 3 demo

Next we implement a practical demo of the provider pattern, using the dynamic context to render the Consumer of the consuming context dynamically.


const ThemeContext = React.createContext(null) // Create a context context with the theme color context

function ConsumerDemo(){
    return <div>
         <ThemeContext.Consumer>
        {
            (theme) => <div style={{ . theme}} >
                  <p>i am alien!</p>
                  <p>let us learn React!</p>
             </div>
        }
        </ThemeContext.Consumer>
    </div>
}

class Index extends React.PureComponent{
    render(){
        return <div>
            <ConsumerDemo />
        </div>}}export default function ProviderDemo(){
    const [ theme , setTheme ]= useState({ color:'pink' , background:'#ccc' })
    return <div>
       <ThemeContext.Provider value={theme}  >
          <Index  />
       </ThemeContext.Provider>
       <button onClick={()= >SetTheme ({color:'blue', background:'orange'})} > Click</button>
    </div>
}
Copy the code
  • The Provider changes, and the Consumer that consumes the subscription Provider is rerendered.

Effect:

4 summarizes

The provider pattern is frequently used in daily development, such as passing state globally and saving state. Here is a diagram summarizing the principle of the provider pattern.

Six classes of component inheritance

1 introduction

React has a very powerful combination mode. We recommend using composition rather than inheritance to achieve code reuse between components

React officially recommends composition over inheritance. However, this does not mean that inheritance is not useful, there are many application scenarios of inheritance.

After class components become popular, we can further enhance our components through inheritance. The beauty of this pattern is that we can encapsulate the basic functional components and then extend our base components as needed, reinforcing them as needed, but it’s important to note that a good understanding of the base components can lead to a number of unexpected situations.

Let’s look at one first

class Base extends React.Component{
  constructor(){
    super(a)this.state={
      name:'React Advanced Practice Guide'}}componentDidMount(){}
  say(){
    console.log('base components')}render(){
    return <div> hello,world <button onClick={ this.say.bind(this)} >Click on the</button>  </div>}}class Index extends Base{
  componentDidMount(){
    console.log( this.state.name )
  }
  say(){ /* overrides say */ in the base class
    console.log('extends components')}}export default Index
Copy the code
  • BaseProvides some basic methods and functions, including UI, for base components
  • IndexFor Base inheritance-based components, you can make some functional enhancements to Index.

2 features

Inheritance enhancement is excellent. Its advantages are as follows:

  • You can control the parent render class and add some other render content;
  • Parent class methods can be shared, and additional methods and attributes can be added.

It is important to note, however, that the state and lifecycle are modified by the inherited component. As in the demo above, the componentDidMount life cycle in the Person component will not be executed.

Practice 3 demo

Next we implement an inherited component, the Route component of the well-known React-Router, and enhance it so that it can be controlled by permissions.

  • When a page has permissions, display the page content directly.
  • When the page does not have permissions, the page without permissions is displayed.

The code

import { Route } from 'react-router'

const RouterPermission = React.createContext()

class PRoute extends Route{
    static contextType = RouterPermission  /* Use context */
    constructor(. arg){
        super(... arg)const { path } = this.props
        /* If you have permission */
        console.log(this.context)
        const isPermiss = this.context.indexOf(path) >= 0 /* Check whether you have permission */
        if(! isPermiss) {/* Change the render function, if no permission, re-render a Route, UI is no permission to display the content */
            this.render = () = >  <Route  {. this.props}   >
                <div>No permissions</div>
            </Route>}}}export default (props)=>{
    /* Emulated list of authorized routes */
    const permissionList = [ '/extends/a' , '/extends/b'  ]
   return  <RouterPermission.Provider value={permissionList} >
       <Index {. props} / >
   </RouterPermission.Provider>
}
Copy the code
  • Pass the permission route to the root component. In context mode, a list of routes with permissions is saved. Here, the simulation is/extends/a/extends/b.
  • Write PRoute permission route, inheritancereact-routerIn theRouteComponents.
  • PRoute throughcontextTypeConsumes the specified permission contextRouterPermission context .
  • inconstructorIf not, rewrite the render function and use Route to create a display container to display the UI without permissions.

use

function Test1 (){
    return <div>Permission routing test 1</div>
}

function Test2 (){
    return <div>Permission routing test 2</div>
}

function Test3(){
    return <div>Permission routing Test 3</div>
}

function Index({ history }){
    const routerlist=[
        { name:'Test One' ,path:'/extends/a' },
        { name:'Test Two' ,path:'/extends/b' },
        { name:'Test Three' ,path:'/extends/c'}]return <div>
        {
            routerlist.map(item=> <button key={item.path}
                onClick={()= > history.push(item.path)}
                                  >{item.path}</button>)}<PRoute component={Test1}
            path="/extends/a"
        />
        <PRoute component={Test2}
            path="/extends/b"
        />
        <PRoute component={Test3}
            path="/extends/c"
        />
    </div>
}

Copy the code

The effect

  • As you can see, only in the permissions list [ '/extends/a' , '/extends/b' ]Permissions can be displayed, no permissions prompt no permissions temporarily, perfect to achieve the effect.

4 summarizes

The premise of the inheritance pattern is that you need to know what the inherited component is, what state and methods are inside it, and be transparent about the inner workings of the inherited component. Next, a diagram shows the principle of inheritance patterns.

Seven summarizes

This section describes several design patterns commonly used in React. I hope students can manually tap after reading and apply these design patterns to real projects.

Finally, send rose, hand left lingering fragrance, feel a harvest of friends can give the author praise, pay attention to a wave, update the front end of the super core article.

First come, first served! Here are a few copies of React Advanced Practice Guide, 30% off the coupon code F3Z1VXtv

The resources

React Advanced Components (HOC)

React Advanced Practice guide