Code前端首页关于Code前端联系我们

一、Axios 是啥?为啥 Vue2 项目里选它做请求?

terry 7小时前 阅读数 18 #Vue
文章标签 Axios;Vue2

不少同学刚接触 Vue2 做项目时,一碰到网络请求就犯懵 —— Axios 到底咋用?不同请求咋发?拦截器、封装这些概念咋落地?跨域报错咋解决?这篇把 Vue2 + Axios 开发里的常见问题拆碎了讲,从基础到实战一步步说清楚,新手也能跟着理顺思路~

Axios 是个基于 Promise 的 HTTP 客户端,浏览器和 Node.js 环境都能跑,Vue2 生态里常用它做请求,核心原因有这些: - Vue 本身不内置请求库,得自己选工具; - Axios 语法友好,用 `axios.get()` `axios.post()` 这类链式调用,比原生 `fetch` 或 `XMLHttpRequest` 好懂; - 支持**拦截器**(请求发出去前、响应回来后统一处理)、**取消请求**(避免重复请求)、**自动转换 JSON** 这些实用特性; - 社区资源多,碰到问题一搜一大把解决方案,学习成本低。

Vue2 项目里咋引入 Axios?全局和局部用法有啥区别?

先把 Axios 装到项目里:用 npm install axios 或者 yarn add axios 都行,接下来分全局引入局部引入两种方式:

全局引入(推荐高频请求场景)

在项目的 main.js 里这么写:

import Vue from 'vue'
import axios from 'axios'
// 把 axios 挂到 Vue 原型上,所有组件都能通过 this.$axios 调用
Vue.prototype.$axios = axios 

这样做的好处是不用每个组件都重复 import,写请求时直接 this.$axios.get(...) 就行,但如果项目里有多个不同域名的接口(比如一个调内部系统,一个调第三方),全局配置可能不够灵活,这时候得结合「实例化封装」(后面会讲)。

局部引入(适合低频/特殊配置请求)

如果只是某个组件要用 Axios,就在组件里单独引入:

import axios from 'axios'
export default {
  methods: {
    fetchData() {
      axios.get('/api/data').then(...)
    }
  }
}

局部引入的优势是配置更灵活,比如给这个请求单独设超时时间、请求头,但缺点是代码重复率高,多个组件用的话得重复写 import

最基础的 GET/POST 请求咋写?参数咋传?

实际开发里,GET 常用在「查数据」(比如列表查询),POST 常用在「提交数据」(比如登录、上传),这两种请求的参数传递方式不一样,得注意细节:

GET 请求:参数走 params(自动拼到 URL 后)

比如做一个「文章列表查询」,需要传页码和每页数量:

this.$axios.get('/api/articles', {
  params: {
    page: 1,
    size: 10
  }
}).then(res => {
  console.log(res.data) // 后端返回的数据
}).catch(err => {
  console.error('请求失败:', err)
})

Axios 会自动把 params 里的对象转成 ?page=1&size=10 拼到 URL 后面,还会帮你处理特殊字符的编码,不用自己操心。

POST 请求:参数走 data(默认发 JSON)

用户登录」需要传账号密码,用 POST 发 JSON 数据:

this.$axios.post('/api/login', {
  username: 'test',
  password: '123456'
}).then(res => {
  // 登录成功逻辑,比如存 token
}).catch(err => {
  // 错误处理,比如提示“账号密码错误”
})

如果后端需要「表单格式(form-data)」(比如上传文件),得用 FormData 构造数据,还要手动设请求头:

// 假设 fileObj 是文件对象(<input type="file"> 选的文件)
let formData = new FormData()
formData.append('file', fileObj)
this.$axios.post('/api/upload', formData, {
  headers: { 'Content-Type': 'multipart/form-data' }
})

这里要注意:Axios 默认 POST 请求头是 application/json,如果要发 form-data,必须手动把请求头改成 multipart/form-data,否则后端可能解析不了。

拦截器有啥用?请求和响应拦截器咋配置?

拦截器能在「请求发出去前」和「响应回来后」做统一处理,减少重复代码,比如给所有请求加 token、全局处理错误码,都靠它:

请求拦截器:给所有请求加 token

很多接口需要验证身份,得在请求头里带 token,用请求拦截器,不用每个请求单独写:

// 先引入 axios(如果是全局配置,直接用 Vue.prototype.$axios)
import axios from 'axios'
axios.interceptors.request.use(
  config => {
    // 从 localStorage 里拿 token(假设登录后存在这里)
    const token = localStorage.getItem('token')
    if (token) {
      // 把 token 加到请求头,格式看后端要求,这里是 Bearer 格式举例
      config.headers.Authorization = `Bearer ${token}`
    }
    return config // 必须 return 配置,请求才会发出去
  },
  error => {
    // 请求还没发就出错了(比如参数错了),直接 reject
    return Promise.reject(error)
  }
)

这样配置后,所有通过这个 axios 实例发的请求,请求头都会自动带上 token,不用每个请求手动写。

响应拦截器:全局处理错误码

后端接口一般会返回统一的结构({ code, data, msg }),用响应拦截器可以提前处理错误:

axios.interceptors.response.use(
  response => {
    // 假设后端约定 code=200 是成功,其他是业务错误
    if (response.data.code === 401) {
      // 401 代表未登录,跳转到登录页(用 Vue Router 的话,this.$router.push)
      window.location.href = '/login'
    } else if (response.data.code !== 200) {
      // 业务错误,密码错误”“权限不足”,全局提示
      alert(response.data.msg)
    }
    // 把响应里的 data 直接返回,组件里不用再写 res.data
    return response.data 
  },
  error => {
    // 处理网络错误(比如超时、断网)
    if (error.message.includes('timeout')) {
      alert('请求超时,请重试')
    } else if (!window.navigator.onLine) {
      alert('网络连接异常')
    }
    return Promise.reject(error)
  }
)

配置后,组件里调接口时,then 里直接拿到的是 response.data,不用再判断 code,代码更简洁。

为啥要封装 Axios?怎么封装更实用?

直接用全局 axios 有个问题:不同环境(开发、测试、生产)的基础 URL 不好切换,而且拦截器改动得改全局配置,维护起来麻烦。「封装 Axios」就是创建自定义实例,让配置更灵活:

步骤:创建实例 + 加拦截器 + 导出

在项目里新建 utils/request.js(路径自己定),写这样的代码:

import axios from 'axios'
// 创建 axios 实例,自定义配置
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // 不同环境配 .env 文件(比如开发环境是 /api,生产是 https://xxx.com)
  timeout: 5000, // 全局超时时间,5 秒没响应就算超时
  headers: { 'Content-Type': 'application/json' } // 全局请求头
})
// 给实例加请求拦截器(和之前全局配置逻辑一样,只是作用于这个实例)
service.interceptors.request.use(
  config => {
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)
// 给实例加响应拦截器
service.interceptors.response.use(
  response => {
    if (response.data.code === 401) {
      window.location.href = '/login'
    } else if (response.data.code !== 200) {
      alert(response.data.msg)
    }
    return response.data
  },
  error => {
    if (error.message.includes('timeout')) {
      alert('请求超时')
    }
    return Promise.reject(error)
  }
)
// 导出实例,其他文件用这个实例发请求
export default service 

组件里咋用封装后的实例?

在需要发请求的组件里,引入这个实例:

import request from '@/utils/request' // 路径对应你放 request.js 的位置
export default {
  methods: {
    fetchArticle() {
      request.get('/articles', {
        params: { page: 1 }
      }).then(res => {
        // res 已经是 response.data 了,直接用
      }).catch(err => {
        // 错误处理
      })
    }
  }
}

封装的好处很明显:不同模块可以创建多个实例(比如一个调内部接口,一个调第三方接口),每个实例的基础 URL、超时时间、拦截器都是独立的,维护起来更灵活。

Vue2 里 Axios 跨域问题咋解决?开发和生产有啥区别?

跨域是浏览器的「同源策略」导致的(协议、域名、端口有一个不一样,就会跨域),开发和生产环境的解决思路不同:

开发环境(Vue CLI 项目):用代理转发

在项目根目录的 vue.config.js 里配置代理:

module.exports = {
  devServer: {
    proxy: {
      '/api': { // 匹配所有以 /api 开头的请求
        target: 'https://xxx.com', // 后端真实域名
        changeOrigin: true, // 开启代理:虚拟一个和后端同域的请求源,绕开浏览器拦截
        pathRewrite: { '^/api': '' } // 把 /api 替换成空,比如请求 /api/login 实际发到 https://xxx.com/login
      }
    }
  }
}

配置后,前端发请求到 /api/login,会被代理到 https://xxx.com/login,浏览器里看请求是前端自己的域名(http://localhost:8080),就不会触发跨域报错。

生产环境:用 Nginx 反向代理

开发时的 devServer.proxy 只在本地生效,生产环境得用 Nginx 配置反向代理,在 Nginx 的配置文件(nginx.conf)里加:

server {
  listen 80; # 前端项目运行的端口
  server_name 你的域名; # www.xxx.com
  location /api { # 匹配 /api 开头的请求
    proxy_pass https://xxx.com; # 后端真实地址
    proxy_set_header Host $host; # 把请求头里的 Host 设为前端域名,让后端识别
    proxy_set_header X-Real-IP $remote_addr; # 传递客户端真实 IP
  }
}

这样生产环境下,前端请求 /api/login 会被 Nginx 转发到后端,同样绕开跨域问题。

Axios 错误处理咋做更完善?

除了拦截器里的全局处理,组件里调接口时也要「分层处理」,区分网络错误业务错误

组件内的错误处理逻辑

假设用封装后的 request 实例发请求,代码可以这么写:

request.post('/login', { username, password })
  .then(res => {
    // 拦截器已经把 response.data 返回来,这里 res 就是后端数据
    if (res.code === 200) {
      // 登录成功:存 token、跳首页等
      localStorage.setItem('token', res.data.token)
      this.$router.push('/home')
    } else {
      // 业务错误:账号不存在”“密码错误”,给用户提示
      alert(res.msg)
    }
  })
  .catch(err => {
    // 网络错误:比如拦截器里没处理完的超时、断网
    console.log('请求失败原因:', err)
    alert('网络波动,请稍后再试')
  })

进阶:取消重复请求

如果用户快速点按钮,同一个请求发了多次,会给后端增加压力,用 Axios 的 CancelToken 可以取消重复请求:

// 在 request.js 里加这段逻辑(给封装的实例用)
const pending = new Map() // 存请求标识和取消函数
// 生成请求唯一标识(method + url + params + data)
const getRequestKey = (config) => {
  return [config.method, config.url, JSON.stringify(config.params), JSON.stringify(config.data)].join('&')
}
// 请求拦截器里加“取消重复请求”逻辑
service.interceptors.request.use(config => {
  const key = getRequestKey(config)
  // 如果之前有相同的请求在pending,取消它
  if (pending.has(key)) {
    pending.get(key)() // 执行取消操作
    pending.delete(key) // 从map里删掉
  }
  // 给当前请求加 CancelToken
  config.cancelToken = new axios.CancelToken(cancel => {
    pending.set(key, cancel)
  })
  return config
}, error => {
  return Promise.reject(error)
})
// 响应拦截器里移除已完成的请求
service.interceptors.response.use(response => {
  const key = getRequestKey(response.config)
  pending.delete(key) // 请求完成,从map里删掉
  return response
}, error => {
  // 如果是主动取消的请求,不视为错误
  if (axios.isCancel(error)) {
    return new Promise(() => {}) // 空Promise,组件里catch不会触发
  }
  return Promise.reject(error)
})

这样配置后,同一时间重复的请求会被自动取消,减少后端压力。

Axios 和 Vuex 结合咋玩?异步请求咋管理状态?

Vuex 里的 action 适合处理异步操作(比如登录、获取用户信息),可以把请求逻辑放到 action 里,用 mutation 更新状态:

例子:用户登录 + 获取信息流程

store/modules/user.js 里写:

import request from '@/utils/request' // 引入封装好的 axios 实例
const state = {
  token: '',
  userInfo: {}
}
const mutations = {
  // 存 token
  SET_TOKEN(state, token) {
    state.token = token
  },
  // 存用户信息
  SET_USER_INFO(state, info) {
    state.userInfo = info
  }
}
const actions = {
  // 登录 action,接收用户名和密码
  login({ commit }, { username, password }) {
    return new Promise((resolve, reject) => {
      request.post('/login', { username, password })
        .then(res => {
          if (res.code === 200) {
            // 登录成功,存 token
            commit('SET_TOKEN', res.data.token)
            // 接着获取用户信息
            return request.get('/user/info')
          }
          return res // code 不对,把结果抛出去
        })
        .then(userRes => {
          // 存用户信息
          commit('SET_USER_INFO', userRes.data)
          resolve(userRes) // 通知组件登录成功
        })
        .catch(err => {
          reject(err) // 通知组件登录失败
        })
    })
  }
}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}

组件里触发这个 action

export default {
  methods: {
    handleLogin() {
      this.$store.dispatch('user/login', {
        username: this.username,
        password: this.password
      })
        .then(() => {
          // 登录成功,跳转到首页
          this.$router.push('/home')
        })
        .catch(err => {
          // 登录失败,提示错误
          alert('登录失败:' + err.message)
        })
    }
  }
}

把请求逻辑放到 Vuex 的 action 里,状态更新用 mutation,组件只负责「触发 action」和「处理结果」,代码分层更清晰,维护起来也方便。

实际开发里还有哪些 Axios 小技巧?

除了基础用法,这些细节能帮你避坑:

参数序列化:复杂对象转 URL 参数

GET

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门