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
React
It only focuses on the interface and does not include sendingajax
Requested code- Front-end applications need to pass
ajax
Requests interact with the background (json
Data) React
Third parties need to be integrated into the applicationajax
Library (or self-encapsulation)
1.2. Common Ajax request libraries
jQuery
: is heavy. Do not use it if you need to import itaxios
: Lightweight, recommended- encapsulation
XmlHttpRequest
The object’sajax
promise
style- It can be used on the browser side and node server side
- encapsulation
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:
- Advantages: Simple configuration and no prefix can be added when the front-end requests resources.
- Disadvantages: You cannot configure multiple agents.
- 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
- Step 1: Create the proxy profile
Create a configuration file under SRC: SRC/setupproxy.jsCopy the code
- 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:
- Advantages: Multiple proxies can be configured to flexibly control whether requests go through proxies.
- 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." />
<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" />
<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
- Welcome to the interface [Opening the page for the first time]
- Search load page [displayed between clicking the button to send the request and receiving the response]
- 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" />
<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…
- Tool library: PubSubJS
- 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" />
<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
- github.github.io/fetch/
- [Related blog post] Traditional Ajax is dead, Fetch lives forever
6.2 the characteristics of
- Fetch: A native function that no longer uses the XmlHttpRequest object to submit ajax requests
- 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
-
Consider all aspects of state design, such as components with network requests, and consider what happens when the request fails.
-
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
- Message subscription and publishing mechanism
- Subscribe first, publish later (understand: has a teleconversation feel)
- Applicable to communication between any components
- To unsubscribe from the component’s componentWillUnmount
- 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