React全家桶笔记(五):React网络请求 — 代理、Axios与Fetch

2 阅读6分钟

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"
}

工作机制

  1. 前端请求 http://localhost:3000/api/students
  2. 脚手架先在 public 文件夹中找有没有 /api/students 这个资源
  3. 如果有 → 直接返回(不走代理)
  4. 如果没有 → 转发给 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 回调(通过父组件中转)
│   └── 方案2PubSub 消息订阅发布(任意组件直接通信)
├── 生命周期应用:
│   ├── componentDidMount → 订阅消息
│   └── componentWillUnmount → 取消订阅
└── async/await:简化 Promise 链式调用

本章知识图谱

React 网络请求
├── 跨域与代理
│   ├── 跨域原因:浏览器同源策略
│   ├── 方法1package.json → proxy(单目标)
│   └── 方法2:setupProxy.js(多目标,灵活)
├── 请求方式
│   ├── axios:第三方库,功能全面(推荐)
│   └── fetch:原生 API,关注分离,两步取数据
├── 组件通信进阶
│   ├── PubSub:发布订阅,任意组件通信
│   ├── 订阅:componentDidMount
│   └── 取消:componentWillUnmount
└── 实战模式
    ├── 多状态管理:isFirst / isLoading / err / data
    └── 三元链式判断:展示不同 UI 状态

📌 下一篇:[React全家桶笔记(六):React Router 5 全解] 将进入 React 路由的世界,学习 SPA 应用的核心 — 前端路由。