【第四章-react ajax】
第四章-react ajax
一、理解
1. 前置说明
- React 本身只关注于界面,并不包含发送
ajax请求的代码; - 前端应用需要通过
ajax请求与后台进行交互(json数据); - React 应用中需要集成第三方
ajax库(或自己封装)
2. 常用的 ajax 请求库
- jQuery:比较重,如果需要另外引入不建议使用;
- axios:轻量级,建议使用;
– 封装XmlHttpRequest对象的ajax;
–promise风格;
– 可以用在浏览器端和node服务器端;
二、axios
1. axios 的安装和使用
1.1 下载
npm i axios
1.2 引入
import axios from 'axios'
1.3 文档
https://github.com/axios/axios
1.4 相关 API
1.4.1 GET 请求
axios.get('/user?ID=12345').then(function (response) {
console.log(response.data)
}).catch(function (error) {
console.log(error);
})
1.4.2 POST 请求
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
}).then(function (response) {
return response.data;
}).catch(function (error) {
console.log(error);
})
1.5 案例
App.jsx:
import React, {Component} from 'react';
import axios from 'axios'
import './App.css'
class App extends Component {
render() {
return (
<div>
<button onClick={this.getStudentData}>点击获取学生数据</button>
</div>
);
}
// 发送请求
getStudentData = () => {
axios.get('http://localhost:5000/students').then(res => {
console.log(res.data)
}, err => {
console.log(err)
})
}
}
export default App;
我们使用node+express来搭建后台服务器1。
server1.js:
const express = require("express")
const app = express()
app.use((request, response, next) => {
console.log('有人请求服务器1了')
next()
})
app.get('/students', (request, response) => {
const students = [
{id: '001', name: 'tom', age: 18},
{id: '002', name: 'jerry', age: 19},
{id: '003', name: 'tony', age: 120}
]
response.send(students)
})
app.listen(5000, (err) => {
if (!err) console.log('服务器1启动成功了,请求学生信息地址为:http://localhost:5000/students')
})
我们当前本地的环境是http://localhost:3000,但是我们要给http://localhost:5000发请求,由于跨域我们的请求可以发送出去,但是同源策略导致无法返回,此时我们需要在react脚手架里配置代理去解决。
2. react脚手架配置代理
2.1 配置一个代理
在package.json中追加如下配置:
"proxy": "http://1ocalhost:5000"
再把请求地址中的5000端口调整为3000:
App.jsx
// 发送请求
getStudentData = () => {
axios.get('http://localhost:3000/students').then(res => {
console.log(res.data)
}, err => {
console.log(err)
})
}
}
该请求就可以发送成功了!
总结:
- 优点:配置简单,前端请求资源时可以不加任何前缀;
- 缺点:不能配置多个代理;
- 工作方式:上述方式配置代理,当请求了
3000存在的资源时,那么该请求不会转发给5000,则会直接使用3000存在的资源;当请求了3000不存在的资源时,那么该请求会转发给5000(优先匹配前端资源)。
2.2 配置多个代理
若此时除了server1.js,还有另一个服务器server2.js。
server2.js:
const express = require("express")
const app = express()
app.use((request, response, next) => {
console.log('有人请求服务器2了')
next()
})
app.get('/cars', (request, response) => {
const cars = [
{id: '001', name: '奔驰', price: 199},
{id: '002', name: '马自达', price: 109},
{id: '003', name: '捷达', price: 120},
]
response.send(cars)
})
app.listen(5001, (err) => {
if (!err) console.log('服务器2启动成功了,请求汽车信息地址为:http://localhost:5001/cars')
})
App.jsx:
import React, {Component} from 'react';
import axios from 'axios'
import './App.css'
class App extends Component {
render() {
return (
<div>
<button onClick={this.getStudentData}>点击获取学生数据</button>
<button onClick={this.getCarData}>点击获取汽车数据</button>
</div>
);
}
getStudentData = () => {
axios.get('http://localhost:5000/students').then(res => {
console.log(res.data)
}, err => {
console.log(err)
})
}
getCarData = () => {
axios.get('http://localhost:5001/cars').then(res => {
console.log(res.data)
}, err => {
console.log(err)
})
}
}
export default App;
server2.js请求的地址为http://localhost:5001/cars,若两台服务器同时存在,那如何配置多个代理呢?
- 第一步:创建代理配置文件
在src下创建配置文件:src/setupProxy.js
- 编写
setupProxy.js配置具体代理规则:
// 引入内置模块
const proxy = require('http-proxy-middleware')
module.exports = function (app){
app.use(
proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: 'http://localhost:5000', // 配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, // 控制服务器接收到的请求头中host字段的值
/*
* changedrigin设置为true时,服务器收到的请求头中的host为:1ocalhost:5000
* changeorigin设置为fa1se时,服务器收到的请求头中的host为:1oca1host:3000
* changeorigin设置为fa1se时,服务器收到的请求头中的host为:1oca1host:3000
* */
pathRewrite: {'^/api1':''} // 去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2':''}
})
)
}
App.jsx
getStudentData = () => {
axios.get('http://localhost:3000/api1/students').then(res => {
console.log(res.data)
}, err => {
console.log(err)
})
}
getCarData = () => {
axios.get('http://localhost:3000/api2/cars').then(res => {
console.log(res.data)
}, err => {
console.log(err)
})
}
总结:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理;
- 缺点:配置繁琐,前端请求资源时必须添加前缀。
3. 案例 - github用户搜索
页面默认有一个搜索框和提示语,当用户输入关键字进行搜索后,下方会展示和关键词有关的 github 用户列表。


请求地址: https://api.github.com/search/users?q=xxxxxx
3.1 拆分组件

建立组件文件夹:

3.2 完成静态页面
- 我们使用
Gemini来生成静态页面的文件;
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Search Github Users</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* 微调卡片边框颜色以匹配原图 */
.user-card {
border: 1px solid #efefef;
}
</style>
</head>
<body class="bg-white text-gray-800">
<header class="bg-gray-100 py-12 px-4 mb-8">
<div class="max-w-5xl mx-auto">
<h1 class="text-3xl font-medium mb-4">Search Github Users</h1>
<div class="flex gap-2">
<input
type="text"
placeholder="enter the name you search"
class="border border-gray-300 px-3 py-1 w-64 focus:outline-none focus:ring-1 focus:ring-blue-500"
>
<button class="bg-gray-200 border border-gray-400 px-4 py-1 hover:bg-gray-300 transition-colors text-sm">
Search
</button>
</div>
</div>
</header>
<main class="max-w-6xl mx-auto px-4">
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<div class="user-card flex flex-col items-center py-8">
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg" alt="reactjs"
class="w-24 h-24 mb-2 bg-black p-2">
<p class="text-sm text-gray-600">reactjs</p>
</div>
<div class="user-card flex flex-col items-center py-8">
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg" alt="reactjs"
class="w-24 h-24 mb-2 bg-black p-2">
<p class="text-sm text-gray-600">reactjs</p>
</div>
<div class="user-card flex flex-col items-center py-8">
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg" alt="reactjs"
class="w-24 h-24 mb-2 bg-black p-2">
<p class="text-sm text-gray-600">reactjs</p>
</div>
<div class="user-card flex flex-col items-center py-8">
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg" alt="reactjs"
class="w-24 h-24 mb-2 bg-black p-2">
<p class="text-sm text-gray-600">reactjs</p>
</div>
<div class="user-card flex flex-col items-center py-8">
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg" alt="reactjs"
class="w-24 h-24 mb-2 bg-black p-2">
<p class="text-sm text-gray-600">reactjs</p>
</div>
</div>
</main>
</body>
</html>
- 将生成的静态页面代码复制到
App.jsx中,将class替换成className,style改写成双花括号的格式style={{}};
App.jsx:
import React, {Component} from 'react';
import './App.css'
class App extends Component {
render() {
return (
<div className="app bg-white text-gray-800">
<header className="bg-gray-100 py-12 px-4 mb-8">
<div className="max-w-5xl mx-auto">
<h1 className="text-3xl font-medium mb-4">Search Github Users</h1>
<div className="flex gap-2">
<input
type="text"
placeholder="enter the name you search"
className="border border-gray-300 px-3 py-1 w-64 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
<button
className="bg-gray-200 border border-gray-400 px-4 py-1 hover:bg-gray-300 transition-colors text-sm">
Search
</button>
</div>
</div>
</header>
<main className="max-w-6xl mx-auto px-4">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<div className="user-card flex flex-col items-center py-8">
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg" alt="reactjs"
className="w-24 h-24 mb-2 bg-black p-2"/>
<p className="text-sm text-gray-600">reactjs</p>
</div>
<div className="user-card flex flex-col items-center py-8">
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg" alt="reactjs"
className="w-24 h-24 mb-2 bg-black p-2"/>
<p className="text-sm text-gray-600">reactjs</p>
</div>
<div className="user-card flex flex-col items-center py-8">
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg" alt="reactjs"
className="w-24 h-24 mb-2 bg-black p-2"/>
<p className="text-sm text-gray-600">reactjs</p>
</div>
<div className="user-card flex flex-col items-center py-8">
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg" alt="reactjs"
className="w-24 h-24 mb-2 bg-black p-2"/>
<p className="text-sm text-gray-600">reactjs</p>
</div>
<div className="user-card flex flex-col items-center py-8">
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg" alt="reactjs"
className="w-24 h-24 mb-2 bg-black p-2"/>
<p className="text-sm text-gray-600">reactjs</p>
</div>
</div>
</main>
</div>
);
}
}
export default App;
App.css:
.user-card {
border: 1px solid #efefef;
}
- 对于
Tailwind CSS这种成型的第三方样式库,我们统一放在public文件夹下面,并在index.html中引入;

index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>github用户搜索</title>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
<!-- 引入Tailwind CSS -->
<script src="%PUBLIC_URL%/tailwindcss.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
- 抽取
Search组件;
Search/index.jsx:
import React, {Component} from 'react';
class Search extends Component {
render() {
return (
<header className="bg-gray-100 py-12 px-4 mb-8">
<div className="max-w-5xl mx-auto">
<h1 className="text-3xl font-medium mb-4">Search Github Users</h1>
<div className="flex gap-2">
<input
type="text"
placeholder="enter the name you search"
className="border border-gray-300 px-3 py-1 w-64 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
<button
className="bg-gray-200 border border-gray-400 px-4 py-1 hover:bg-gray-300 transition-colors text-sm">
Search
</button>
</div>
</div>
</header>
);
}
}
export default Search;
- 抽取
List和Item组件;
List/index.jsx:
import React, {Component} from 'react';
import Item from '../Item'
import './index.css'
class List extends Component {
render() {
return (
<main className="max-w-6xl mx-auto px-4">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<Item/>
</div>
</main>
);
}
}
export default List;
List/index.css:
.welcome {
font-size: 30px;
box-sizing: border-box;
}
Item/index.jsx:
import React, {Component} from 'react';
class Item extends Component {
render() {
return (
<div className="user-card flex flex-col items-center py-8">
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg" alt="reactjs"
className="w-24 h-24 mb-2 bg-black p-2"/>
<p className="text-sm text-gray-600">reactjs</p>
</div>
);
}
}
export default Item;
App.jsx:
import React, {Component} from 'react';
import Search from './components/Search';
import List from './components/List';
import './App.css'
class App extends Component {
render() {
return (
<div className="bg-white text-gray-800">
<Search/>
<List/>
</div>
);
}
}
export default App;
页面效果:

3.2 axios发送请求
- 搜索按钮绑定点击事件;
<button
className="bg-gray-200 border border-gray-400 px-4 py-1 hover:bg-gray-300 transition-colors text-sm"
onClick={this.search}>
Search
</button>
...
// 搜索按钮绑定点击事件
search = () => {
}
}
- 使用
ref获取用户的输入;
<input
type="text"
placeholder="enter the name you search"
ref={c => this.keywordElement = c}
className="border border-gray-300 px-3 py-1 w-64 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
<button
className="bg-gray-200 border border-gray-400 px-4 py-1 hover:bg-gray-300 transition-colors text-sm"
onClick={this.search}>
Search
</button>
...
// 搜索按钮绑定点击事件
search = () => {
// 获取用户输入
const {value} = this.keywordElement;
console.log(value);
}
}
上面获取value还可以换一种写法——使用连续解构赋值。
// 搜索按钮绑定点击事件
search = () => {
const {keywordElement:{value}} = this;
console.log(value)
}
如果你的value还是一个对象,你可以继续写下去。
// 常规写法
const obj = {a: {b: {c: 1}}};
console.log('c', obj.a.b.c); // 1
// 连续解构赋值的写法
let {a: {b: {c}}} = obj;
console.log('解构赋值c', c); // 1
连续解构赋值的写法想必你已经学会了,我们再加深一下:连续结构赋值的同时,再对变量重命名。
// 连续结构赋值 + 重命名
const obj2 = {a: {b: 2}};
let {a: {b: data}} = obj2;
console.log(data); // 2
那我们按照上面的方式,使用【连续解构赋值+重命名】来获取一下输入框的内容。
// 搜索按钮绑定点击事件
search = () => {
// 获取用户的输入(连续解构赋值 + 重命名)
const {keywordElement: {value: keyword}} = this;
console.log(keyword);
}
- 发送网络请求;
// 引入axios
import axios from 'axios';
...
// 搜索按钮绑定点击事件
search = () => {
// 获取用户的输入(连续解构赋值 + 重命名)
const {keywordElement: {value: keyword}} = this;
// 发送网络请求
axios.get(`https://api.github.com/search/users?q=${keyword}`).then(res => {
console.log(res.data);
}, err => {
console.log(err);
});
}
搜索atguigu:
返回数据:
- 配置proxy
src/setupProxy.js:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app){
app.use(
createProxyMiddleware('/search', {
target: 'https://api.github.com',
changeOrigin: true
})
)
}
修改请求路径:
// 搜索按钮绑定点击事件
search = () => {
// 获取用户的输入(连续解构赋值+重命名)
const {keywordElement: {value: keyword}} = this;
// 发送网络请求
axios.get(`/search/users?q=${keyword}`).then(res => {
console.log(res.data);
},err => {
console.log(err);
});
}
3.3 展示数据
Search组件将获取到的数据传给父组件App,然后再交给List做展示。
App.jsx:
...
class App extends Component {
// 初始化状态
state = {
// 用户数据
users: []
}
render() {
const {users} = this.state;
return (
<div className="bg-white text-gray-800">
<Search saveUsers={this.saveUsers}/>
<List users={users}/>
</div>
);
}
// 更新用户数据
saveUsers = (users) => {
this.setState({users});
}
}
...
Search/index.jsx:
// 搜索按钮绑定点击事件
search = () => {
// 获取用户的输入(连续解构赋值+重命名)
const {keywordElement: {value: keyword}} = this;
// 发送网络请求
axios.get(`/search/users?q=${keyword}`).then(res => {
// 调用App.jsx的方法,更新users
this.props.saveUsers(res.data.items);
},err => {
console.log(err);
});
}
List组件接收数据,然后遍历users展示Item。
List/index.jsx:
class List extends Component {
render() {
return (
<main className="max-w-6xl mx-auto px-4">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{
this.props.users.map((user) => {
return <Item user={user} key={user.id}/>
})
}
</div>
</main>
);
}
}
我们需要的 3 个用户对象中的属性:
avatar_url:头像地址;login:用户名;html_url:个人主页地址;

Item/index.jsx:
import React, {Component} from 'react';
class Item extends Component {
render() {
const {user} = this.props;
return (
<div className="user-card flex flex-col items-center py-8" onClick={this.jumpHtml}>
<img src={user.avatar_url} alt={user.login}
className="w-24 h-24 mb-2 bg-black p-2"/>
<p className="text-sm text-gray-600">{user.login}</p>
</div>
);
}
// 跳转到个人主页
jumpHtml = () => {
window.open(this.props.user.html_url);
}
}
export default Item;
页面效果:

3.4 细节和完善
这个案例的基本功能已经完成,我们再来完善一些细节:
- 初次进入页面,下方应该显示欢迎词
Welcome to enter name to search! - 搜索和返回数据的空档期,应当展示
loading状态; - 请求出错时,下方应该提示用户错误信息;
- 我们想让
List组件中有 4 种不同的展示行为(用户列表,欢迎词,加载中,错误信息),需要由 4 种不同的状态去控制,我们先在App组件中声明这些状态。
App.jsx:
// 初始化状态
state = {
// 用户数据
users: [],
// 是否为第一次打开页面
isFirst: true,
// 标识是否处于加载中
isLoading: false,
// 存储请求相关的错误信息
err: null,
};
- 按照我们之前的方式,还需要在
App.jsx中声明 4 个函数(saveUsers,changeIsFirst,changeIsLoading和saveErr)用来更新对应的状态值,但是那样太麻烦了…这次我们换一种更加通用和简洁的写法,只声明一个统一管理状态的方法:updateAppState。
App.jsx:
// 更新App的state
updateAppState = (stateObj) => {
this.setState(stateObj);
};
- 我们将该方法传给
Search组件,用于Search组件进行搜索后来通知App更新状态。
App.jsx:
...
class App extends Component {
// 初始化状态
state = {
// 用户数据
users: [],
// 是否为第一次打开页面
isFirst: true,
// 标识是否处于加载中
isLoading: false,
// 存储请求相关的错误信息
err: null,
};
render() {
const { users } = this.state;
return (
<div className="bg-white text-gray-800">
<Search updateAppState={this.updateAppState} />
<List/>
</div>
);
}
// 更新App的state
updateAppState = (stateObj) => {
this.setState(stateObj);
};
}
export default App;
Search/index.jsx:
// 搜索按钮绑定点击事件
search = () => {
// 发送请求前通知 App 更新状态
this.props.updateAppState({ isFirst: false, isLoading: true });
// 获取用户的输入(连续解构赋值+重命名)
const {keywordElement: { value: keyword }} = this;
// 发送网络请求
axios.get(`/search/users?q=${keyword}`).then(
(res) => {
// 请求成功后通知 App 更新状态
this.props.updateAppState({ isLoading: false, users: res.data.items });
},
(err) => {
// 请求失败后通知 App 更新状态
this.props.updateAppState({ isLoading: false, err: err.message });
},
);
};
}
App组件接收到新的状态对象后,将状态对象传递给List组件;
App.jsx:
render() {
const { users } = this.state;
return (
<div className="bg-white text-gray-800">
<Search updateAppState={this.updateAppState} />
<List users={users} {...this.state}/>
</div>
);
}
List组件根据状态值来做不同的展示;
这里涉及到条件判断的逻辑,我们使用三元表达式来代替以往的if...else。
List/index.jsx:
...
class List extends Component {
render() {
const { users, isFirst, isLoading, err } = this.props;
return (
<main className="max-w-6xl mx-auto px-4">
{isFirst ? (
<h2 style={{ fontSize: "24px" }}>Welcome to enter name to search!</h2>
) : isLoading ? (
<h2>loading...</h2>
) : err ? (
<h2 style={{ color: "red" }}>{err}</h2>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{users.map((user) => {
return <Item user={user} key={user.id} />;
})}
</div>
)}
</main>
);
}
}
...
- 页面效果;
初次进入页面,展示欢迎词的效果:

点击搜索按钮,显示加载中的效果:

将请求地址随便改一下,比如users234,模拟一下请求出错的效果:

3.5 消息订阅与发布机制
我们把state放在App组件中,是因为目前无法实现Search组件和List组件之间的通信,接下来我们来学习消息订阅-发布机制,让兄弟组件或任意组件直接实现数据的通信。
3.5.1 工具库 PubSubJS
有很多的库可以实现这种机制,我们使用比较主流的 PubSubJS。
- 下载
npm install pubsub-js --save
- 引入
import PubSub from "pubsub-js"
- 使用
发布(Publish):
import PubSub from "pubsub-js";
PubSub.publish("eventName", "Hello PubSub");
订阅(Subscribe):
import PubSub from "pubsub-js";
const token = PubSub.subscribe("eventName", (msg, data) => {
// msg ==> 消息名 "eventName"
// data ==> 数据 "Hello PubSub"
console.log(msg, data);
});
// 取消订阅
PubSub.unsubscribe(token);
下面我们就把这个技术应用到我们的Search组件和List组件上。
3.5.2 使用 PubSubJS 完成兄弟组件间的通信
- 首先,我们要明确一下我们的 订阅者 和 发布者 ;
- Search:发布者,
Search决定了List的状态变化,搜索后通知List更新状态; - List:订阅者,接收
Search传递过来的数据做判断展示;
结论:谁接收,谁订阅消息;谁传递,谁发布消息。
- 准备性工作,优化
App组件,删掉无用代码;
因为App无需成为Search和List间的通信媒介,所以关于state的代码可以全部删掉。
App.jsx:
import React, { Component } from "react";
import Search from "./components/Search";
import List from "./components/List";
import "./App.css";
class App extends Component {
render() {
return (
<div className="bg-white text-gray-800">
<Search />
<List />
</div>
);
}
}
export default App;
这样才是App本该有的样子。
- 在
List组件中定义初始化状态,并订阅消息;
List/index.jsx:
...
import PubSub from "pubsub-js";
...
class List extends Component {
// 初始化状态
state = {
// 用户数据
users: [],
// 是否为第一次打开页面
isFirst: true,
// 标识是否处于加载中
isLoading: false,
// 存储请求相关的错误信息
err: null,
};
render() {
const { users, isFirst, isLoading, err } = this.state;
return (
<main className="max-w-6xl mx-auto px-4">
{isFirst ? (
<h2 style={{ fontSize: "24px" }}>
Welcome to enter name to search!
</h2>
) : isLoading ? (
<h2>loading...</h2>
) : err ? (
<h2 style={{ color: "red" }}>{err}</h2>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{users.map((user) => {
return <Item user={user} key={user.id} />;
})}
</div>
)}
</main>
);
}
componentDidMount() {
// 订阅消息
this.token = PubSub.subscribe("updateListState", (_, stateObj) => {
this.setState(stateObj);
});
}
componentWillUnmount() {
// 取消订阅消息
PubSub.unsubscribe(this.token);
}
}
...
- 在
Search组件中发布消息,通知List组件更新状态;
Search/index.jsx:
...
import PubSub from "pubsub-js";
...
// 搜索按钮绑定点击事件
search = () => {
// 发送请求前通知 List 更新状态
PubSub.publish("updateListState", { isFirst: false, isLoading: true })
// 获取用户的输入(连续解构赋值+重命名)
const {keywordElement: { value: keyword }} = this;
// 发送网络请求
axios.get(`/search/users?q=${keyword}`).then(
(res) => {
// 请求成功后通知 List 更新状态
PubSub.publish("updateListState", { isLoading: false, users: res.data.items })
},
(err) => {
// 请求失败后通知 List 更新状态
PubSub.publish("updateListState", { isLoading: false, err: err.message })
},
);
};
...
这种通信方式不仅仅适用于兄弟组件间的沟通,还适用于任意组件间的沟通。
3.6 fetch发送请求
3.6.1 fetch
- 文档
使用 Fetch API - MDN
Fetch API 教程 - 阮一峰
传统 Ajax 已死,Fetch 永生
- 特点
- fetch:原生函数,不再使用
XmlHttpRequest对象提交ajax请求; - 基于
Promise,语法简单; - 老版本浏览器可能不支持;
- 相关 API
GET请求:
fetch("https://api.example.com/data")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("Error:", error));
POST请求:
fetch("https://api.example.com/data", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
key: "value",
}),
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("Error:", error));
3.6.2 结合案例使用 fetch 发送网络请求
- 使用
fetch发送(未优化版)
Search/index.jsx:
// 发送网络请求——使用fetch发送
fetch(`/search/users?q=${keyword}`).then(res => {
console.log('请求成功', res)
}, err => {
console.log('请求失败', err)
})
我们打开控制台,查看返回的数据`:

fetch 返回的 response 是一个 Response 对象,读取 Body 数据需要使用response.json(),解析为 JSON 对象:
// 发送网络请求——使用fetch发送
fetch(`/search/users?q=${keyword}`).then(res => {
console.log('请求成功', res.json())
}, err => {
console.log('请求失败', err)
})
这次返回的是一个Promise 实例:

为了输出我们要的Json数据,我们将得到的Promise实例对象返回,再进行一次.then的链式调用:
// 发送网络请求——使用fetch发送
fetch(`/search/users?q=${keyword}`).then(res => {
return res.json()
}, err => {
console.log('请求失败', err)
}).then(res => {
console.log("请求成功", res);
}, err => {
console.log("请求失败", err);
})
现在我们已经成功的拿到了我们想要的数据了!

现在有一个问题:如果数据请求失败,此时第一个then里面毫无疑问的会执行err函数,那么第二个then还会继续执行吗?如果执行的话,它会走成功回调函数还是走失败回调函数呢?
我们可以将控制台的Network选项卡调整为Offline ,用断网来模拟出请求失败的效果:

我们看到请求失败后,第二个then会继续执行,且走的是成功回调函数!

这是因为第一个then里面的err没有写返回值,这就相当于返回了undefined,而undefined属于非 Promise 值,那么第一个then返回的Promise 实例状态就是成功的,值为undefined,所以第二个函数就走了成功的回调函数。
我们需要让第一个then里面的err里停下来,不要继续往下走:
// 发送网络请求——使用fetch发送
fetch(`/search/users?q=${keyword}`).then(res => {
return res.json()
}, err => {
// 返回一个初始化状态的Promise,来中断Promise链
return new Promise()
}).then(res => {
console.log("请求成功", res);
}, err => {
console.log("请求失败", err);
})
- 使用
fetch发送(优化版)
我们可以不在每个then里面声明失败回调函数,而是使用.catch兜底,统一处理错误。
// 发送网络请求——使用fetch发送
fetch(`/search/users?q=${keyword}`).then(res => {
res.json()
}).then(res => {
console.log("请求成功", res);
}).catch(err => {
console.log("请求出错", err);
})
- 使用
Async/Await(更优雅的异步)
使用 async/await 可使异步代码更清晰。
search = async () => {
try {
const res = await fetch(`/search/users?q=${keyword}`);
const data = await res.json();
console.log("请求成功", data);
} catch (err) {
console.log("请求出错", err);
}
}
Search/index.jsx:
// 搜索按钮绑定点击事件
search = async () => {
// 发送请求前通知List更新状态
PubSub.publish("updateListState", { isFirst: false, isLoading: true });
// 获取用户的输入(连续解构赋值+重命名)
const {
keywordElement: { value: keyword },
} = this;
// 使用await+async发送网络请求
try {
const res = await fetch(`/search/users?q=${keyword}`);
const data = await res.json();
PubSub.publish("updateListState", {
isLoading: false,
users: data.items,
});
} catch (err) {
PubSub.publish("updateListState", { isLoading: false, err: err.message });
}
};
3.7 总结
- 设计状态时要考虑全面,例如:带有网络请求的组件,要考虑请求失败怎么办;
- ES6小知识点:【解构赋值+重命名】
let obj = {a:{b:1}}
const {a}= obj; //传统解构赋值
const {a:{b}}= obj; //连续解构赋值
const {a:{b:value}}= obj; //连续解构赋值 + 重命名
- 消息订阅与发布机制
(1)先订阅,再发布(理解:有一种隔空对话的感觉);
(2)适用于任意组件间的通信;
(3)要在组件的componentWillUnmount中取消订阅;
fetch发送请求(关注分离的设计思想)
fetch(`/search/users?q=${keyword}`).then(res => {
// 第一步:到底看看服务器能不能联系成功
res.json()
}).then(res => {
// 第二步:它才把真正的数据给你
console.log("请求成功", res);
}).catch(err => {
console.log("请求出错", err);
})
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)