React全家桶笔记(五):React网络请求 — 代理、Axios与Fetch
本篇学习在 React 中发送 Ajax 请求、配置代理解决跨域、消息订阅与发布模式,并通过 GitHub 搜索案例串联所有知识点。 📺 对应张天禹react全家桶视频:P65 - P73
一、React 中的 Ajax
React 本身不包含发送 Ajax 请求的代码,需要借助第三方库:
- axios:轻量级,推荐使用 ✅
- jQuery:比较重,不推荐在 React 中使用
- fetch:浏览器原生 API,兼容性问题需注意
二、脚手架配置代理(P65-P66)
2.1 为什么需要代理?
前端运行在 localhost:3000,后端 API 在 localhost:5000,直接请求会产生跨域问题(浏览器的同源策略限制)。
🔗 概念扩展:跨域的本质 跨域请求其实已经发出去了,服务器也返回了数据,但浏览器发现响应的来源和当前页面不同源(协议+域名+端口),就把响应拦截了。代理服务器没有跨域限制(服务器之间通信不受同源策略约束),所以可以作为中间人转发请求。
2.2 方法一:package.json 配置(P65)
最简单的方式,在 package.json 中添加一行:
{
"name": "my-app",
"version": "0.1.0",
"proxy": "http://localhost:5000"
}
工作机制:
- 前端请求
http://localhost:3000/api/students - 脚手架先在 public 文件夹中找有没有
/api/students这个资源 - 如果有 → 直接返回(不走代理)
- 如果没有 → 转发给
http://localhost:5000/api/students
// 请求时直接写 3000 端口(自己的端口)
axios.get('http://localhost:3000/api/students').then(
response => { console.log('成功', response.data) },
error => { console.log('失败', error) }
)
优点:配置简单 缺点:只能配置一个代理目标,不能灵活控制哪些请求走代理
2.3 方法二:setupProxy.js 配置(P66)
在 src 目录下创建 setupProxy.js(必须是这个文件名):
// src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
createProxyMiddleware('/api1', {
target: 'http://localhost:5000', // 转发目标
changeOrigin: true, // 控制服务器收到的请求头中 Host 的值
pathRewrite: { '^/api1': '' } // 重写请求路径(去掉前缀)
}),
createProxyMiddleware('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: { '^/api2': '' }
})
)
}
// 请求时加上前缀来区分走哪个代理
axios.get('http://localhost:3000/api1/students') // → 转发到 5000/students
axios.get('http://localhost:3000/api2/cars') // → 转发到 5001/cars
参数说明:
target:转发的目标服务器地址changeOrigin:设为 true 时,服务器收到的请求头中的 Host 为 target 的值(推荐 true)pathRewrite:重写路径,把请求中的前缀去掉
优点:可以配置多个代理,灵活控制请求是否走代理 缺点:配置略繁琐,前端请求时需要加前缀
🎯 面试高频:React 脚手架中如何解决跨域? 两种方式:package.json 中配置 proxy(简单但只能配一个);src/setupProxy.js 中用 http-proxy-middleware(灵活,支持多个代理)。
三、GitHub 搜索案例(P67-P73)
3.1 案例效果
输入关键词搜索 GitHub 用户,展示用户头像、名称和主页链接。页面有四种状态:初始状态、加载中、展示数据、请求出错。
3.2 组件拆分
App
├── Search(搜索栏,发起请求)
└── List(展示结果列表)
3.3 静态组件(P67)
先搭建好 HTML + CSS 结构。
3.4 axios 发送请求(P68-P70)
// App.jsx — 管理状态
export default class App extends Component {
state = {
users: [], // 用户数据
isFirst: true, // 是否第一次打开(欢迎页)
isLoading: false, // 是否加载中
err: '' // 错误信息
}
// 更新 App 的 state
updateAppState = (stateObj) => {
this.setState(stateObj)
}
render() {
return (
<div className="container">
<Search updateAppState={this.updateAppState} />
<List {...this.state} />
</div>
)
}
}
// Search/index.jsx
export default class Search extends Component {
search = () => {
// 获取用户输入
const { keyWordElement: { value: keyWord } } = this
// 发送请求前 — 更新状态:加载中
this.props.updateAppState({ isFirst: false, isLoading: true })
// 发送请求
axios.get(`https://api.github.com/search/users?q=${keyWord}`).then(
response => {
this.props.updateAppState({ isLoading: false, users: response.data.items })
},
error => {
this.props.updateAppState({ isLoading: false, err: error.message })
}
)
}
render() {
return (
<section>
<h3>搜索 GitHub 用户</h3>
<div>
<input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词" />
<button onClick={this.search}>搜索</button>
</div>
</section>
)
}
}
// List/index.jsx — 根据状态展示不同内容
export default class List extends Component {
render() {
const { users, isFirst, isLoading, err } = this.props
return (
<div className="row">
{
isFirst ? <h2>欢迎使用,输入关键词搜索</h2> :
isLoading ? <h2>Loading...</h2> :
err ? <h2 style={{color: 'red'}}>{err}</h2> :
users.map(userObj => (
<div className="card" key={userObj.id}>
<a href={userObj.html_url} target="_blank" rel="noreferrer">
<img src={userObj.avatar_url} alt="avatar" style={{width: '100px'}} />
</a>
<p className="card-text">{userObj.login}</p>
</div>
))
}
</div>
)
}
}
💡 设计模式:用一个 state 对象管理多种页面状态(isFirst/isLoading/err/data),通过三元表达式链式判断展示不同 UI。这是 React 中非常常见的模式。
四、消息订阅与发布 — PubSub(P71)
4.1 问题引出
在 GitHub 搜索案例中,Search 和 List 是兄弟组件,数据传递需要通过父组件 App 中转。如果组件层级更深,这种"状态提升"会很繁琐。
4.2 PubSub 模式
npm install pubsub-js
// List/index.jsx — 订阅消息(接收方)
import PubSub from 'pubsub-js'
export default class List extends Component {
state = {
users: [],
isFirst: true,
isLoading: false,
err: ''
}
componentDidMount() {
// 订阅消息
this.token = PubSub.subscribe('searchResult', (msg, stateObj) => {
this.setState(stateObj)
})
}
componentWillUnmount() {
// 取消订阅
PubSub.unsubscribe(this.token)
}
render() {
// ... 和之前一样,但数据从自身 state 取
}
}
// Search/index.jsx — 发布消息(发送方)
import PubSub from 'pubsub-js'
search = () => {
const { keyWordElement: { value: keyWord } } = this
// 发布消息:通知 List 进入加载状态
PubSub.publish('searchResult', { isFirst: false, isLoading: true })
axios.get(`https://api.github.com/search/users?q=${keyWord}`).then(
response => {
PubSub.publish('searchResult', { isLoading: false, users: response.data.items })
},
error => {
PubSub.publish('searchResult', { isLoading: false, err: error.message })
}
)
}
PubSub 的优势:
- 任意组件之间都可以通信,不受层级限制
- 发布者和订阅者完全解耦
- App 组件不再需要管理 List 的状态,代码更清晰
使用要点:
- 在
componentDidMount中订阅 - 在
componentWillUnmount中取消订阅(防止内存泄漏) - 订阅的回调函数第一个参数是消息名(msg),第二个才是数据
🔗 概念扩展:组件通信方式对比
- props:父子通信(最基本)
- 回调函数:子 → 父通信
- PubSub:任意组件通信(发布订阅模式)
- Context:跨层级通信(后续章节)
- Redux:集中式状态管理(后续章节)
五、Fetch 发送请求(P72)
5.1 Fetch 是什么?
Fetch 是浏览器原生提供的 API,不需要安装任何库。它基于 Promise 设计,是 XMLHttpRequest 的替代方案。
5.2 Fetch 的特点
- 原生函数,不需要借助第三方库
- 采用关注分离的设计思想:第一次
.then拿到的不是数据,而是响应对象;需要再调用.json()才能拿到数据 - 兼容性问题:老版本浏览器可能不支持
5.3 Fetch 基本用法
// 传统 Promise 链式写法
fetch(`https://api.github.com/search/users?q=${keyWord}`).then(
response => {
console.log('联系服务器成功')
return response.json() // 返回一个 Promise,解析 JSON 数据
},
error => {
console.log('联系服务器失败', error)
return new Promise(() => {}) // 返回一个 pending 的 Promise,中断链
}
).then(
response => {
console.log('获取数据成功', response)
},
error => {
console.log('获取数据失败', error)
}
)
5.4 Fetch + async/await(推荐写法)
search = async () => {
const { keyWordElement: { value: keyWord } } = this
PubSub.publish('searchResult', { isFirst: false, isLoading: true })
try {
const response = await fetch(`https://api.github.com/search/users?q=${keyWord}`)
const data = await response.json()
PubSub.publish('searchResult', { isLoading: false, users: data.items })
} catch (error) {
PubSub.publish('searchResult', { isLoading: false, err: error.message })
}
}
5.5 Axios vs Fetch
Axios:
├── 第三方库,需要安装
├── 自动转换 JSON 数据
├── 支持请求/响应拦截器
├── 支持取消请求
├── 浏览器兼容性好
└── 社区生态丰富,推荐使用 ✅
Fetch:
├── 浏览器原生 API,无需安装
├── 基于 Promise,语法现代
├── 关注分离设计,需要两步获取数据
├── 不支持拦截器(需手动封装)
├── 老浏览器兼容性差
└── 适合简单场景或追求零依赖
🎯 面试高频:Fetch 和 Axios 的区别? Fetch 是原生 API,Axios 是第三方库。Fetch 需要两步获取数据(先 response 再 .json()),Axios 直接拿到数据。Axios 支持拦截器、取消请求等高级功能。实际项目中推荐 Axios。
六、GitHub 搜索案例总结(P73)
本案例核心收获:
├── 代理配置:解决开发环境跨域问题
├── 请求发送:axios / fetch 两种方式
├── 状态管理:用 state 对象管理多种页面状态
├── 组件通信:
│ ├── 方案1:状态提升 + props 回调(通过父组件中转)
│ └── 方案2:PubSub 消息订阅发布(任意组件直接通信)
├── 生命周期应用:
│ ├── componentDidMount → 订阅消息
│ └── componentWillUnmount → 取消订阅
└── async/await:简化 Promise 链式调用
本章知识图谱
React 网络请求
├── 跨域与代理
│ ├── 跨域原因:浏览器同源策略
│ ├── 方法1:package.json → proxy(单目标)
│ └── 方法2:setupProxy.js(多目标,灵活)
├── 请求方式
│ ├── axios:第三方库,功能全面(推荐)
│ └── fetch:原生 API,关注分离,两步取数据
├── 组件通信进阶
│ ├── PubSub:发布订阅,任意组件通信
│ ├── 订阅:componentDidMount
│ └── 取消:componentWillUnmount
└── 实战模式
├── 多状态管理:isFirst / isLoading / err / data
└── 三元链式判断:展示不同 UI 状态
📌 下一篇:[React全家桶笔记(六):React Router 5 全解] 将进入 React 路由的世界,学习 SPA 应用的核心 — 前端路由。