New bee store open source warehouse (connotation Vue 2.x and Vue 3.x H5 mall open source code, with server API interface) : github.com/newbee-ltd

Vue 3.x + Vant 3.x + vue-router 4.x high copy wechat bookable open source address (with server API interface) : github.com/Nick930826/…

React + Vite 2.0 + Zarm React + Vite 2.0 + Zarm

The article brief introduction

  • Definition: Introduces the concept of higher-order components.
  • Instances: Reinforce the concept of higher-order components with small instances.
  • Secondary packagingAntdPopover component, called as a function method.

The text start

Knock business knock to a certain amount of time, you need to launch a “qualitative change”. This “qualitative change” requires an opportunity to understand how to incorporate newly acquired advanced knowledge into your own projects. Don’t say a few words about the truth, we should all have a bit of force in mind. React advanced components.

define

Higher Order Component HOC, for short. In my own interpretation, a higher-order component is a function that takes a component as an argument and returns a new component (function component or class component). The React component converts props to UI, while the high-level component converts a component to another component.

The instance

Demand analysis

Write two list components of similar data types and display them in the parent component. Create a React project using YARN Create vite. Create two files in the project SRC directory: Alist. JSX and Blist.

// Alist.jsx
import { useEffect, useState } from 'react'
// Static JSON data generated manually by the author
const url = 'http://image.chennick.wang/1644916550730-0-a.json'

const Alist = () = > {
  const [data, setData] = useState([])
  
  useEffect(() = > {
    // Initiate a request to get list data
    fetch(url).then(res= > {
      return res.json()
    }).then(({ data }) = > {
      setData(data)
    })
  }, [])
  
  return <div style={{ padding: 20.border: '1px solid #e9e9e9', display: 'inline-block' }}>
    {
      data.map((item, index) => <div key={index}>
        <div>Name: {item.name}; Age: {item. Age}</div>
      </div>)}</div>
}

export default Alist
Copy the code
// Blist.jsx
import { useEffect, useState } from 'react'
// Static JSON data generated manually by the author
const url = 'http://image.chennick.wang/1644917014491-1-b.json'

const Blist = () = > {
  const [data, setData] = useState([])
  
  useEffect(() = > {
    // Initiate a request to get list data
    fetch(url).then(res= > {
      return res.json()
    }).then(({ data }) = > {
      setData(data)
    })
  }, [])
  
  return <div style={{ padding: 20.border: '1px solid #e9e9e9', display: 'inline-block' }}>
    {
      data.map((item, index) => <div key={index}>
        <div>Name: {item.name}; Price: {item.price} yuan</div>
      </div>)}</div>
}

export default Blist
Copy the code

Finally, to display the two list components on the page, modify the app.jsx file as follows:

import { useState } from 'react'
import Alist from './Alist'
import Blist from './Blist'
import './App.css'

function App() {
  return (
    <div className="App">
      <Alist></Alist>
      <Blist></Blist>
    </div>)}export default App
Copy the code

The page display effect is as follows:

Implement higher-order components

After implementing the above code, let’s not get too excited. After a few moments of calm reflection, you will notice that the above code has repeated parts of business logic, namely the data state data and the request logic in the useEffect hook function. Their public parts are extracted and maintained separately using higher-order components.

Higher-order components usually start With With, such as WithRouter for routing components, WithStore for state components, and so on. We’ll do the same and create a new withdata.jsx file in the SRC directory, adding the following. First, it is a function and returns a new component:

// WithData.jsx
const WithData = () = > {
	const WithDataComponent = () = > {
  	// do something
  }
  return WithDataComponent
}

export default WithData
Copy the code

Pull out the public parts of the two list components and write them to the returned component WithDataComponent above, and the WithDataComponent component needs to return the list component passed in as follows:

import { useEffect, useState } from 'react'
// WithData.jsx
// Component is the incoming Component, and the URL is the requested address for each Component
const WithData = (Component, url) = > {
	const WithDataComponent = () = > {
		const [data, setData] = useState([])
    useEffect(() = > {
    	fetch(url).then(res= > res.json()).then(({ data }) = > {
      	setData(data)
      })
    }, [])
    // Return the data attribute returned by the request to the Component as props
    return <Component data={data} />
  }
  return WithDataComponent
}

export default WithData
Copy the code

This code extracts the logic of the request and passes the requested data to the Component in the form passed in by the Component. At this point, the corresponding data can be obtained in the form of function parameters in the Alist and Blist components. Modify Alist. JSX and Blist. JSX as follows:

// Alist.jsx
import WithData from './WithData' // Introduce higher-order components
const url = 'http://image.chennick.wang/1644916550730-0-a.json'

const Alist = ({ data }) = > {
  return <div style={{ padding: 20.border: '1px solid #e9e9e9', display: 'inline-block' }}>
    {
      data.map((item, index) => <div key={index}>
        <div>Name: {item.name}; Age: {item. Age}</div>
      </div>)}</div>
}

export default WithData(Alist, url) // Pass in the Alist and URL parameters
Copy the code
// Blist.jsx
import WithData from './WithData' // Introduce higher-order components
const url = 'http://image.chennick.wang/1644917014491-1-b.json'

const Blist = ({ data }) = > {
  return <div style={{ padding: 20.border: '1px solid #e9e9e9', display: 'inline-block' }}>
    {
      data.map((item, index) => <div key={index}>
        <div>Name: {item.name}; Price: {item.price} yuan</div>
      </div>)}</div>
}

export default WithData(Blist, url) // Pass in the Alist and URL parameters
Copy the code

The Alist and Blist list components, wrapped with WithData, throw a new Component returned within a higher-order Component
. So, in app.jsx, it is equivalent to the following:

So in Alist. JSX and Blist. JSX, you can get the data parameter in the parameter of the function. The final render remains the same:

Expand knowledge

If I pass a parameter to the Alist component in the parent app.jsx, it looks like this:

import { useState } from 'react'
import Alist from './Alist'
import Blist from './Blist'
import './App.css'

function App() {
  return (
    <div className="App">
      <Alist title='I am the title argument passed in by the parent component'></Alist>
      <Blist></Blist>
    </div>)}export default App
Copy the code

Print this property in Alist. JSX as follows:

.const Alist = ({ data, title }) = > {
	console.log('title', title)
  ...
}
Copy the code

The result is as follows:

The reason is that the Alist component introduced in the parent component app.jsx is the function component returned after wrapping the higher-order component WithData, that is, the WithDataComponent component.

import React, { useState, useEffect } from 'react'

const WithData = (Component, url) = > {
  const WithDataComponent = () = > {
    const [data, setData] = useState([])
    useEffect(() = > {
      fetch(url).then(res= > res.json()).then(({ data }) = > {
        setData(data)
      })
    }, [])
    return <Component data={data} />
  }
  return WithDataComponent
}

export default WithData
Copy the code

In the WithDataComponent Component, the title passed in is not brought to the Component, resulting in the title property being lost in Alist. JSX. So we need to pass all the props passed in by the parent Component to the Component as follows:

const WithDataComponent = (props) = > {
  const [data, setData] = useState([])
  useEffect(() = > {
    fetch(url).then(res= > res.json()).then(({ data }) = > {
      setData(data)
    })
  }, [])
  return <Component {. props} data={data} />
}
return WithDataComponent
Copy the code

Then we re-run the project and look at the print under Alist. JSX. The result is as follows:

conclusion

At this point, a simple higher-order component is written. Of course, in the case of complex business, higher-order components are far more than that. It requires a high understanding of the business and overall planning of the project structure to extract many similar and similar structures one by one and encapsulate them into higher-order components.

Secondary encapsulation of Ant Design popover components

Taking advantage of higher-order component properties, we’ll encapsulate an Ant Design popover in the form of a method call.

Demand analysis

In business development, we often use the Modal popover component provided by Ant Design. If you have multiple popover requirements on a page, you will encounter something like this:

const [visible1, setVisible1] = useState(false)
const [visible2, setVisible2] = useState(false)
const [visible3, setVisible3] = useState(false)
const [visible4, setVisible4] = useState(false)
const [visible5, setVisible5] = useState(false)...Copy the code

N Multiple visible states can be managed on the same page. Therefore, in order to solve this dilemma, optimize a wave by using higher-order components and encapsulate the visible state of popover into higher-order components for control.

Implementation logic

First, based on the above project, install Ant Design with the command:

yarn add antd
Copy the code

Modify main.jsx to introduce antD globally as follows:

.import 'antd/dist/antd.css'
Copy the code

By using the Visible control popover, add dialogrename.jsx to the SRC directory, as shown below:

import { Input, Modal } from 'antd'
const DialogRename = ({ visible, onCancel }) = > {
  return <Modal
    title="I'm popover."
    visible={visible}
    onOk={onCancel}
    onCancel={onCancel}
  >
    <>
      <label>Name:</label>
      <Input style={{ width: 400 }} onChange={(e)= >{setValue(e.target.value)}} placeholder=' please input a placeholder name '/></>
  </Modal>
}

export default DialogRename
Copy the code

In the above code, the visible state is accepted from the parent component, and the method onCancel controls popover hiding. Then add the popover component to the app.jsx entry page with the following code:

import { useState } from 'react'
import { Button } from 'antd'
import DialogRename from './DialogRename'
import './App.css'

function App() {
  const [visible, setVisible] = useState(false) // Controls the display or hiding of the DialogRename component
  return (
    <div className="App">
      <Button onClick={()= >SetVisible (true)} > open it</Button>
      <DialogRename visible={visible} onCancel={()= > setVisible(false)} />
    </div>)}export default App
Copy the code

The effect is as follows:

Next, we will create a higher-order component withdialog.jsx, as follows:

// WithDialog.jsx
import React from 'react'
import { render, unmountComponentAtNode } from 'react-dom'

export default class WithDialog {
  constructor(Component) {
    this._ele = null
    this._dom = <Component onCancel={this.close} />
    this.show()
  }
  
  show = () = > {
    this._ele = document.createElement('div')
    render(this._dom, document.body.appendChild(this._ele))
  }

  close = () = > {
    unmountComponentAtNode(this._ele)
    this._ele.remove()
  }
}
Copy the code

Declare a WithDialog class Component. The constructor accepts the Component argument as the wrapped Component, such as the wrapped DialogRename Component. Declare a global attribute this._ele for the target div tag to be mounted later when creating a popover. This._dom is used to mount the popover component passed in. Finally, the this.show method is executed by default to mount the popover.

The show method creates a div tag and mounts the popover component this._dom to the page using the Render method provided with the React-dom package.

Close method, unmountComponentAtNode method provided by the react-DOM method, unmounts this._ele node to which the popup component is mounted, and then removes it.

Dialogrename.jsx:

import { useState } from 'react'
import { Input, Modal } from 'antd'
import WithDialog from './WithDialog'

const DialogRename = ({ ...props }) = > {
  const [value, setValue] = useState(' ') // Enter the value of the box
  const handleOk = () = > {
    props.onCancel()
  }
  return <Modal
    title="I'm popover."
    visible
    onCancel={props.onCancel}
    onOk={handleOk}
  >
    <>
      <label>Name:</label>
      <Input style={{ width: 400 }} onChange={(e)= >{setValue(e.target.value)}} placeholder=' please input a placeholder name '/></>
  </Modal>
}

export default() = >new WithDialog(DialogRename)
Copy the code

First of all, it is clear that in the parent component app.jsx, the popover component needs to be initiated by calling methods. Export default () => new WithDialog(DialogRename). After the WithDialog wrap, the DialogRename component can receive the onCancel method passed in withDialog. JSX, which is retrieved in a deconstructed form and assigned to Modal’s onCancel property.

After that, we call the component in app.jsx as a method, as follows:

import { Button } from 'antd'
import DialogRename from './DialogRename'
import './App.css'

function App() {
  const handleOpen = () = > {
    DialogRename()
  }
  return (
    <div className="App">
      <Button onClick={handleOpen}>To open it</Button>
    </div>)}export default App
Copy the code

Restart the project and the result should look like this:

When we click the confirm component in the popover, we need to perform some methods, so we can pass in the onOk method to the DialogRename method as follows:

import { Button } from 'antd'
import DialogRename from './DialogRename'
import './App.css'

function App() {
  const handleOpen = () = > {
    DialogRename({
    	onOk: (val) = > {
				console.log('the onOk:', val)
      }
    })
  }
  return (
    <div className="App">
      <Button onClick={handleOpen}>To open it</Button>
    </div>)}export default App
Copy the code

JSX accepts onOk assigned by dialogrename.jsx to the Modal component’s onOk, and modifyingdialogrename.jsx as follows:

import { useState } from 'react'
import { Input, Modal } from 'antd'
import WithDialog from './WithDialog'

const DialogRename = ({ onOk, ... props }) = > {
  const [value, setValue] = useState(' ')
  const handleOk = () = > {
    onOk(value)
    props.onCancel()
  }
  return <Modal
    title="I'm popover."
    visible
    onOk={handleOk}
    {. props}
  >
    <>
      <label>Name:</label>
      <Input style={{ width: 400 }} onChange={(e)= >{setValue(e.target.value)}} placeholder=' please input a placeholder name '/></>
  </Modal>
}

export default() = >new WithDialog(DialogRename)
Copy the code

Execute the onOk(value) method to call back the value of the input box to the onOk method in app.jsx, as follows:

After clicking the OK button, an error appears as shown above. If the DialogRename is wrapped by the WithDialog component, the onOk method passed to it is lost. In this case, we need to pass the props that the parent component passes to the DialogRename in the constructor as follows:

// DialogRename.jsx.export default (props) => new WithDialog(DialogRename, props)
Copy the code

Then go to Withdialog. JSX and pass props to Component as follows:

// WithDialog.jsx.constructor(Component, props) {
  this._ele = null
  this._dom = <Component onCancel={this.close} {. props} / >
  this.show()
}
Copy the code

Then you can actually get the onOk method passed in, as follows:

You can try passing another property in app.jsx to override the Modal component’s title property, as follows:

// App.jsx
// ...
const handleOpen = () = > {
  DialogRename({
    onOk: (val) = > {
      console.log('the onOk:', val)
    },
    title: 'I'm the title the App passed in.'})}Copy the code

The popover title will be overwritten with the following effect:

JSX = withdialog.jsx = withdialog.jsx = withdialog.jsx

// WithDialog.jsx.import zhCN from 'antd/lib/locale/zh_CN'
import { ConfigProvider } from 'antd'.constructor(Component, props) {
  this._ele = null
  this._dom = <ConfigProvider locale={zhCN}>
      <Component onCancel={this.close} {. props} / >
    </ConfigProvider>
  this.show()
}

Copy the code

The effect is as follows:

conclusion

With a good grasp of high-level components, you will have a deeper understanding of business development. This is the only way to advance from intermediate front-end to advanced front-end, focusing on business optimization, but also to measure the technical quality of a front-end developer.