Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

Today we will use React to make a small demo (github user search page) that requires ajax requests. We will use Axios first and then implement a fetch version. We will also configure proxies in React to address cross-domain issues and improve our code using the message subscribe and publish model.

1. Understand

Having studied Ajax and AXIOS before, you can review ajax with these two notes

【Ajax】HTTP related issues -GET-POST-XHR use -jQuery Ajax – Cross domain – homology -jsonp-cors

【 AXIos 】 Build REST API with JSON-server – Use Axios – Customize Axios – Cancel request – interceptor

For a primer on React, see juejin.cn/column/7015…

1.1. Pre-instructions

  1. ReactIt only focuses on the interface and does not include sendingajaxRequested code
  2. Front-end applications need to passajaxRequests interact with the background (jsonData)
  3. ReactThird parties need to be integrated into the applicationajaxLibrary (or self-encapsulation)

1.2. Common Ajax request libraries

  1. jQuery: is heavy. Do not use it if you need to import it
  2. axios: Lightweight, recommended
    1. encapsulationXmlHttpRequestThe object’sajax
    2. promisestyle
    3. It can be used on the browser side and node server side

2. axios

Install NPM install Axios

2.1. The document

github.com/axios/axios

2.2. The relevant API

See this note for more details [Axios] Building REST APIS using JSON-Server – Using Axios – Customizing Axios – Canceling requests – Interceptors

3. Configure proxy in React to resolve cross-domain problems

3.1 Configuring the Proxy Method

To resolve cross-domain issues, enable the intermediary agent in React

In package.json in your project, add the last line “proxy”: “http://loaclhost:5000” to the port number

And then restart the scaffolding.

When sending the request again, write directly to port 3000

Resources on port 3000 directly request port 3000. Resources not on port 3000 directly request port 5000 set by the proxy

Add the following configuration to package.json

"proxy":"http://localhost:5000"
Copy the code

Description:

  1. Advantages: Simple configuration and no prefix can be added when the front-end requests resources.
  2. Disadvantages: You cannot configure multiple agents.
  3. How it works: Configure the proxy so that when 3000 resources are requested that do not exist, the request is forwarded to 5000 (matching front-end resources is preferred).

3.2 Configuring Multiple proxy methods

Configure multiple agents, not package.json

  1. Step 1: Create the proxy profile
Create a configuration file under SRC: SRC/setupproxy.jsCopy the code
  1. Write setupproxy.js to configure specific proxy rules:
const proxy = require('http-proxy-middleware')

module.exports = function(app) {
  app.use(
    proxy('/api1', {  // API1 is the request that needs to be forwarded (all requests with/API1 prefix are forwarded to 5000)
      target: 'http://localhost:5000'.// Configure the forwarding target address (the server address that can return data)
      changeOrigin: true.// Controls the value of the host field in the request header received by the server
      /* When changeOrigin is set to true, the host in the request header received by the server is: localhost:5000 The default changeOrigin value is false, but we usually set the changeOrigin value to true */
      pathRewrite: {'^/api1': ' '} // Remove the request prefix to ensure that the backend server is normal request address (must be configured)
    }),
    proxy('/api2', { 
      target: 'http://localhost:5001'.changeOrigin: true.pathRewrite: {'^/api2': ' '}}}))Copy the code

Description:

  1. Advantages: Multiple proxies can be configured to flexibly control whether requests go through proxies.
  2. Disadvantages: Cumbersome configuration and prefix must be added when the front-end requests resources.

4. Case – Github user search

Vue- CLI configuration proxy – Ajax actual combat – Demo3 -GitHub user query – AXIos – PubSub

4.1 the effect

Requested address:Api.github.com/search/user…

4.2 the React to realize

4.2.1 Implementation of static page splitting

App.jsx

import React, { Component } from 'react'
import Search from './Search'
import Users from './Users'

export default class App extends Component {
  render() {
    return (
      <div className="container">
        <Search />
        <Users />
    </div>)}}Copy the code

Search/index.js

import React, { Component } from 'react'

export default class Search extends Component {
  render() {
    return (
      <section className="jumbotron">
      <h3 className="jumbotron-heading">Search for Github users</h3>
      <div>
          <input type="text" placeholder="Please enter the user name you are searching for." />&nbsp;
          <button>search</button>
      </div>
      </section>)}}Copy the code

User/index.jsx

import React, { Component } from 'react'
import './index.css'

export default class Users extends Component {
  render() {
    return (
      <div className="row">
        <div className="card">
          <a rel="noreferrer" href="https://github.com/reactjs" target="_blank">
              <img alt="avatar" src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ 'width': '100px'}} / >
          </a>
          <p className="card-text">reactjs</p>
        </div>
        <div className="card">
          <a rel="noreferrer" href="https://github.com/reactjs" target="_blank">
            <img alt="avatar" src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ 'width': '100px'}} / >
          </a>
          <p className="card-text">reactjs</p>
        </div>
        <div className="card">
          <a rel="noreferrer" href="https://github.com/reactjs" target="_blank">
            <img alt="avatar" src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ 'width': '100px'}} / >
          </a>
          <p className="card-text">reactjs</p>
        </div>
        <div className="card">
          <a rel="noreferrer" href="https://github.com/reactjs" target="_blank">
            <img alt="avatar" src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ 'width': '100px'}} / >
          </a>
          <p className="card-text">reactjs</p>
        </div>
        <div className="card">
          <a rel="noreferrer" href="https://github.com/reactjs" target="_blank">
            <img alt="avatar" src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ 'width': '100px'}} / >
          </a>
          <p className="card-text">reactjs</p>
        </div>
      </div>)}}Copy the code

User/index.css

.album {
  min-height: 50rem; /* Can be removed; just added for demo purposes */
  padding-top: 3rem;
  padding-bottom: 3rem;
  background-color: #f7f7f7;
}

.card {
  float: left;
  width: 33.333%;
  padding:.75rem;
  margin-bottom: 2rem;
  border: 1px solid #efefef;
  text-align: center;
}

.card > img {
  margin-bottom:.75rem;
  border-radius: 100px;
}

.card-text {
  font-size: 85%;
}
Copy the code

4.2.2 Dynamic interactive implementation

Since Github access failed, you can fake a server that returns fixed results for a better user experience using Express to build a server

serve.js

const express = require("express")
const axios = require("axios")
const app = express()


/ * request address: http://localhost:3000/search/users? Key: /search/users value: function () {} */
app.get("/search/users".function (req, res) {
  const {q} = req.query
  axios({
    url: 'https://api.github.com/search/users'.params: {q}
  }).then(response= > {
    res.json(response.data)
  })
})

app.get("/search/users2".function (req, res) {
  res.json({
    items: [{login: "yyx990803".html_url: "https://github.com/yyx990803".avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4".id: 1}, {login: "ruanyf".html_url: "https://github.com/ruanyf".avatar_url: "https://avatars2.githubusercontent.com/u/905434?s=460&v=4".id: 2}, {login: "yyx9908032".html_url: "https://github.com/yyx990803".avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4".id: 3}, {login: "ruanyf2".html_url: "https://github.com/ruanyf".avatar_url: "https://avatars2.githubusercontent.com/u/905434?s=460&v=4".id: 4}, {login: "yyx9908033".html_url: "https://github.com/yyx990803".avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4".id: 5}, {login: "ruanyf3".html_url: "https://github.com/ruanyf".avatar_url: "https://avatars2.githubusercontent.com/u/905434?s=460&v=4".id: 6}, {login: "yyx9908034".html_url: "https://github.com/yyx990803".avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4".id: 7}, {login: "ruanyf4".html_url: "https://github.com/ruanyf".avatar_url: "https://avatars2.githubusercontent.com/u/905434?s=460&v=4".id: 8}, {login: "yyx9908035".html_url: "https://github.com/yyx990803".avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4".id: 9,}]}); }); app.listen(5000."localhost".(err) = > {
  if(! err){console.log("Server started successfully")
  	console.log("Request making real data please visit: http://localhost:5000/search/users")
  	console.log("Request local simulation data please visit: http://localhost:5000/search/users2")}else console.log(err);
})
Copy the code

src/setupProxy.js

Configure the proxy server to solve the cross-domain problem SRC/setupproxy.js

const proxy = require('http-proxy-middleware')

module.exports = function(app) {
  app.use(
    proxy('/api1', {  // API1 is the request that needs to be forwarded (all requests with/API1 prefix are forwarded to 5000)
      target: 'http://localhost:5000'.// Configure the forwarding target address (the server address that can return data)
      changeOrigin: true.// Controls the value of the host field in the request header received by the server
      pathRewrite: {'^/api1': ' '} // Remove the request prefix to ensure that the backend server is normal request address (must be configured)}}))Copy the code

Continuous deconstruction assignment

let obj = {a: {b: {c:1}}}
console.log(a.b.c) / / 1
const {a: {b:{c}}} = obj
console.log(c) / / 1

let obj2 = {a: {b:1}}
const {a: {b:data}} = obj2 / / renamed
console.log(data) / / 1
Copy the code

App.jsx

The state data is defined in the App and the methods for manipulating the state are placed in the App

export default class App extends Component {
  state = {
    users: []
  }
  saveUsers = (users) = > {
    this.setState({ users })
  }
  render() {
    const {users} = this.state
    return (
      <div className="container">
        <Search saveUsers={this.saveUsers} />
        <Users users={users} />
    </div>)}}Copy the code

Search/index/jsx

export default class Search extends Component {
  search = () = > {
    // Get user input (continuous destruct assignment + rename)
    const {keyWordElement: {value: keyWord}} = this
    // console.log(keyWord)
    // Send a network request
    axios.get(`/api1/search/users? q=${keyWord}`).then(
      response= > {
        console.log('success')
        this.props.saveUsers(response.data.items)
      },
      error= > {console.log('failure',error)}
    )
  }
  render() {
    return (
      <section className="jumbotron">
      <h3 className="jumbotron-heading">Search for Github users</h3>
      <div>
          <input ref={c= >This. keyWordElement = c} type=" placeholder "placeholder=" placeholder" />&nbsp;
          <button onClick={this.search}>search</button>
      </div>
      </section>)}}Copy the code

Users/index.jsx

export default class Users extends Component {
  render() {
    return (
      <div className="row">
        {
          this.props.users.map((userObj) => {
            return (
              <div key={userObj.id} className="card">
                <a rel="noreferrer" href={userObj.html_url} target="_blank">
                    <img alt="avatar" src={userObj.avatar_url} style={{ 'width': '100px'}} / >
                </a>
                <p className="card-text">{userObj.login}</p>
              </div>)})}</div>)}}Copy the code

Results show

4.2.3 Optimize user experience

In the Users component, there should be more than just a user list page, there should be

  1. Welcome to the interface [Opening the page for the first time]
  2. Search load page [displayed between clicking the button to send the request and receiving the response]
  3. Search failure page [Request failure display]

There are four different displays, which require different state states to control

// Initialization state
state = { 
  users: [].// users initial value
  isFirst: true.// Whether to open the page for the first time
  isLoading: false.// Indicates whether it is being loaded
  err:' ' // Request failed message
}
Copy the code

App.jsx

export default class App extends Component {
  // Initialization state
  state = { 
    users: [].// users initial value
    isFirst: true.// Whether to open the page for the first time
    isLoading: false.// Indicates whether it is being loaded
    err:' ' // Request failed message
  }
  // saveUsers = (users) => {
  // this.setState({ users })
  // }
  // Update App state
  updateAppState = (stateObj) = > {
    this.setState(stateObj)
  }
  render() {
    return (
      <div className="container">
        <Search updateAppState={this.updateAppState} />
        <Users {. this.state} / >
    </div>)}}Copy the code

Search/index/jsx

export default class Search extends Component {
  search = () = > {
    // Get user input (continuous destruct assignment + rename)
    const {keyWordElement: {value: keyWord}} = this
    // console.log(keyWord)
    // Notify App to update status before sending the request
    this.props.updateAppState({
      isFirst: false.isLoading: true
    })
    // Send a network request
    axios.get(`/api1/search/users? q=${keyWord}`).then(
      response= > {
        // console.log(' success ')
        // The request succeeds, notifying App to update the status
        this.props.updateAppState({isLoading: false.users: response.data.items})
        // this.props.saveUsers(response.data.items)
      },
      error= > {
        // console.log(' failed ', error)
        // Request failed, notify App to update status
        this.props.updateAppState({isLoading: false.err: error.message})
      }
    )
  }
  render() {
    return (
      <section className="jumbotron">
      <h3 className="jumbotron-heading">Search for Github users</h3>
      <div>
          <input ref={c= >This. keyWordElement = c} type=" placeholder "placeholder=" placeholder" />&nbsp;
          <button onClick={this.search}>search</button>
      </div>
      </section>)}}Copy the code

Users/index.jsx

export default class Users extends Component {
  render() {
    const {users, isFirst, isLoading, err} = this.props
    return (
      <div className="row">
        {
          isFirst ? <h2>Welcome to use, please enter the keyword, then click search</h2> :
          isLoading ? <h2>Loading...</h2> :
          err ? <h2 style={{color: 'red'}} >{err}</h2> :
          users.map((userObj) => {
            return (
              <div key={userObj.id} className="card">
                <a rel="noreferrer" href={userObj.html_url} target="_blank">
                    <img alt="avatar" src={userObj.avatar_url} style={{ 'width': '100px'}} / >
                </a>
                <p className="card-text">{userObj.login}</p>
              </div>)})}</div>)}}Copy the code

Results show

5. Message subscription-publish mechanism

In the previous case, communication between siblings was always done through the parent component. Now we introduce the message subscription-publish mechanism to communicate between siblings

Introduce PubSubJS library

Github.com/mroderick/P…

  1. Tool library: PubSubJS
  2. Download:npm install pubsub-js

3. Use subscribe messages in the component that receives data

import PubSub from 'pubsub-js' / / introduction
PubSub.subscribe('delete'.function(data){});/ / subscribe
PubSub.publish('delete', data) // Publish messages carrying data
Copy the code

Use in the case

The Users component receives the data, so the Users component subscribes to the message Search component sends the data, and publishes the message

App.js

export default class App extends Component {
  render() {
    return (
      <div className="container">
        <Search />
        <Users />
    </div>)}}Copy the code

Users/index.jsx

The User component uses the state data, where the state is defined and subscribes to messages, where the Search component changes the state data and publishes messages

export default class Users extends Component {
  // Initialization state
  state = { 
    users: [].// users initial value
    isFirst: true.// Whether to open the page for the first time
    isLoading: false.// Indicates whether it is being loaded
    err:' ' // Request failed message
  }
  componentDidMount() {
    // Subscribe to the message
    PubSub.subscribe('ykyk'.(_, data) = > {
      this.setState(data)
    })
  }
  render() {
    const {users, isFirst, isLoading, err} = this.state
    return (
      <div className="row">
        {
          isFirst ? <h2>Welcome to use, please enter the keyword, then click search</h2> :
          isLoading ? <h2>Loading...</h2> :
          err ? <h2 style={{color: 'red'}} >{err}</h2> :
          users.map((userObj) => {
            return (
              <div key={userObj.id} className="card">
                <a rel="noreferrer" href={userObj.html_url} target="_blank">
                    <img alt="avatar" src={userObj.avatar_url} style={{ 'width': '100px'}} / >
                </a>
                <p className="card-text">{userObj.login}</p>
              </div>)})}</div>)}}Copy the code

Search/index.jsx

The Search component changes the state data and publishes a message here.

export default class Search extends Component {
  search = () = > {
    const {keyWordElement: {value: keyWord}} = this
    // Notify Users to update the status before sending the request
    // this.props.updateAppState({isFirst: false,isLoading: true})
    PubSub.publish('ykyk', {isFirst: false.isLoading: true})
    // Send a network request
    axios.get(`/api1/search/users? q=${keyWord}`).then(
      response= > {
        // The request is successful, notifying Users to update the status
        PubSub.publish('ykyk', {isLoading: false.users: response.data.items})
        // this.props.updateAppState({isLoading: false, users: response.data.items})
      },
      error= > {
        // Request failed, notify Users to update status
        PubSub.publish('ykyk', {isLoading: false.err: error.message})
        // this.props.updateAppState({isLoading: false, err: error.message})})}render() {
    return (
      <section className="jumbotron">
      <h3 className="jumbotron-heading">Search for Github users</h3>
      <div>
          <input ref={c= >This. keyWordElement = c} type=" placeholder "placeholder=" placeholder" />&nbsp;
          <button onClick={this.search}>search</button>
      </div>
      </section>)}}Copy the code

6. Fetch

Axios is a wrapper to XHR on the front end and Fetch is a built-in network request method that does not require a separate download and installation

6.1 document

  1. github.github.io/fetch/
  2. [Related blog post] Traditional Ajax is dead, Fetch lives forever

6.2 the characteristics of

  1. Fetch: A native function that no longer uses the XmlHttpRequest object to submit ajax requests
  2. Older browsers may not support it

6.3 Example

Search/index.jsx

Before optimization

export default class Search extends Component {
	search = async() = > {// Get user input (continuous destruct assignment + rename)
		const {keyWordElement: {value:keyWord}} = this
		// Notify List of status updates before sending requests
		PubSub.publish('ykyk', {isFirst:false.isLoading:true})
		
		// Send network requests -- use fetch to send (not optimized)
		fetch(`/api1/search/users2? q=${keyWord}`).then(
			response= > {
				console.log('Server contacted successfully');
				return response.json()
			},
			error= > {
				console.log('Failed to contact the server',error);
				return new Promise(() = >{})
			}
		).then(
			response= > {console.log('Data retrieval succeeded',response); },error= > {console.log('Failed to get data',error); }}})Copy the code

The optimized

export default class Search extends Component {
	search = async() = > {// Get user input (continuous destruct assignment + rename)
		const {keyWordElement: {value:keyWord}} = this
		// Notify List of status updates before sending requests
		PubSub.publish('ykyk', {isFirst:false.isLoading:true})

		// Send network requests -- use fetch to send (optimized)
		try {
			const response = await fetch(`/api1/search/users2? q=${keyWord}`)
			const data = await response.json()
			// console.log(data);
			PubSub.publish('ykyk', {isLoading:false.users:data.items})
		} catch (error) {
			// console.log(' request error ',error);
			PubSub.publish('ykyk', {isLoading:false.err:error.message})
		}
	}
}
Copy the code

7. To summarize

  1. Consider all aspects of state design, such as components with network requests, and consider what happens when the request fails.

  2. Deconstruct assignment + rename

let obj = {a: {b:1}}
const {a} = obj; // Traditional deconstruction assignment
const {a:{b}} = obj; // Continuously deconstruct assignments
const {a: {b:value}} = obj; // Continuously destruct assignment + rename
Copy the code
  1. Message subscription and publishing mechanism
    1. Subscribe first, publish later (understand: has a teleconversation feel)
    2. Applicable to communication between any components
    3. To unsubscribe from the component’s componentWillUnmount
  2. Fetch sends requests (focus on the design idea of separation)
try {
	const response= await fetch(`/api1/search/users2? q=${keyWord}`)
	const data = await response.json()
	console.log(data);
} catch (error) {
	console.log('Request error',error);
}
Copy the code