前端与服务端交互——从UI请求处理到Mock.js数据响应

332 阅读5分钟

🧑‍💻前端与服务端交互-HowieCong

  • 在不同的项目中,一个完整的前端UI交互到服务端处理的大致流程如下:

1. UI组件交互操作

  • 从技术层面来看,这一操作会触发相应的事件监听器

  • 以购物车为例,在按钮的模板代码<button @click="addToCart">添加到购物车</button>中,addToCart就是对应的事件处理函数

2. 调用统一管理的api serive请求函数

  • 事件处理函数内部会调用统一管理的api service请求函数。这些请求函数被集中放置在@/api文件夹下,并按照model维度进行拆分

  • 以购物车添加商品为例,addToCart函数可能会调用@/api/cart.js文件中的addProductToCart函数

  • 当购物车相关的 API 发生变化时,开发人员只需在cart.js文件中进行修改,而不会影响到其他模块的请求逻辑。

// @/api/cart.js 
import request from '../utils/request'; 
export function addProductToCart(productId, quantity) { 
    return request({ 
        url: '/cart/add', 
        method: 'post', 
        data: { 
            productId, 
            quantity 
        } 
    }); 
}

3. 使用封装的 request.js 发送请求

  • @/utils/request.js文件基于axios进行了深度封装,目的是为了统一处理POST , GET 等请求参数,参数头,以及错误提示信息,封装了全局request拦截器、response拦截器、统一的错误提示、统一做了超时处理、baseURL设置等

  • 在构建请求时,request函数会根据传入的参数,如urlmethodgetpostputdelete等)、params(用于get请求的查询参数)或data(用于postput等请求的请求体数据),生成符合服务端接口要求的请求

  • 在请求发送之前,通过全局request拦截器对请求进行预处理。例如,在每个请求中添加公共的请求头信息,这对于需要进行身份验证的 API 尤为重要。假设我们使用 JSON Web Token(JWT)进行身份验证,拦截器可以在每个请求的Authorization头中添加Bearer <token>。设置了统一的超时处理。如果请求在规定的时间(如 5000 毫秒)内没有得到响应,会自动终止请求并抛出错误。这有助于避免因网络问题导致的长时间等待,提升用户体验。

// @/utils/request.js 
import axios from 'axios'; 
const service = axios.create({ 
    baseURL: process.env.BASE_API, 
    timeout: 5000 
}); 
service.interceptors.request.use(config => { 
    const token = localStorage.getItem('token'); 
    if (token) { 
        config.headers.Authorization = `Bearer ${token}`; 
    } 
    return config; 
}, error => { 
    return Promise.reject(error); 
});

4. 获取服务端返回

  • 服务端接收到请求后,会进行相应的业务逻辑处理,例如:在购物车添加商品的场景中,服务端可能会验证用户的身份、检查商品库存、更新购物车数据库等操作。处理完成后,服务端会返回响应数据给前端

  • 前端通过axiosresponse拦截器来统一处理服务端的返回。response拦截器首先会检查响应的状态码。

  • 如果状态码表示请求成功(如 200 表示成功,201 表示资源已成功创建等),则会进一步处理响应数据,

  • 如果状态码表示请求失败(如 400 表示客户端请求错误,401 表示未授权,500 表示服务器内部错误等),response拦截器会触发统一的错误处理机制。这可能包括向用户展示友好的错误提示信息,告知用户请求失败的原因。例如,如果是 401 未授权错误,可以提示用户 “您的登录状态已过期,请重新登录”。

  • Eg:对于购物车添加商品的请求,服务端可能返回更新后的购物车信息,包括商品列表、总价等

service.interceptors.response.use(response => {
    const { status, data } = response;
    if (status === 200) {
        return data;
    } else {
        // 处理非成功状态码的情况
        return Promise.reject(new Error(`请求失败,状态码: ${status}`));
    }
}, error => {
    return Promise.reject(error);
});

5. 更新 data 与 Mock.js 结合

  • 在前端开发,通常会用Mock.js来模拟服务端返回的数据,以便没有真实服务端的情况进行开发和测试。

  • Mock.js可以生产随机数据,并按照指定的格式返回给前端

  • Eg:在购物车添加商品的场景中,可以使用Mock.js来模拟服务端返回的更新后的购物车信息,使用 Mock.js 生成了一个包含 3 到 10 个商品的购物车信息。每个商品包含 id、name、price 和 quantity 四个属性,其中 id 是自增的,name 是随机生成的 5 到 10 个字符的字符串,price 是 10 到 100 之间的随机数,quantity 是 1 到 10 之间的随机数。totalPrice 属性是通过计算购物车中所有商品的价格和数量得出的。在组件中,可以将模拟数据赋值给 cart 属性,以便在页面上显示购物车信息。

import Mock from 'mockjs'

// 模拟服务端返回的购物车信息
const cartData = Mock.mock({
  'items|3-10': [
    {
      'id|+1': 1,
      'name': '@cword(5, 10)',
      'price|10-100': 10,
      'quantity|1-10': 1
    }
  ],
  'totalPrice': function () {
    return this.items.reduce((total, item) => total + item.price * item.quantity, 0)
  }
})

// 在组件中使用模拟数据
export default {
  data() {
    return {
      cart: cartData
    }
  }
}

6. Eg:一个完善的request.js

import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'

// 创建一个 axios 实例
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = 基础 url + 请求 url
  // withCredentials: true, // 当跨域请求时发送 cookies
  timeout: 5000 // 请求超时时间
})

// request 拦截器
service.interceptors.request.use(
  config => {
    // 请求发送前做一些处理

    if (store.getters.token) {
      // 让每个请求携带 token
      // ['X-Token'] 是自定义的 headers 键
      // 请根据实际情况进行修改
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // 处理请求错误
    console.log(error) // 用于调试
    return Promise.reject(error)
  }
)

// response 拦截器
service.interceptors.response.use(
  /**
   * 如果你想获取 HTTP 信息如 headers 或者状态
   * 请返回 response => response
   */

  /**
   * 通过自定义代码来判断请求状态
   * 这里只是一个例子
   * 你也可以通过 HTTP 状态码来判断状态
   */
  response => {
    const res = response.data

    // 如果自定义代码不是 20000,则判断为错误
    if (res.code!== 20000) {
      Message({
        message: res.message || '错误',
        type: 'error',
        duration: 5 * 1000
      })

      // 50008: 非法 token; 50012: 其他客户端登录了; 50014: token 过期;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // 重新登录
        MessageBox.confirm('你已被登出,可以取消以留在此页面,或者重新登录', '确认登出', {
          confirmButtonText: '重新登录',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.message || '错误'))
    } else {
      return res
    }
  },
  error => {
    console.log('错误' + error) // 用于调试
    if (error.response) {
      switch (error.response.status) {
        case 400:
          Message.error('请求错误')
          break
        case 401:
          Message.error('未授权,请重新登录')
          // 清除 token 并跳转到登录页面
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
          break
        case 403:
          Message.error('禁止访问')
          break
        case 404:
          Message.error('请求的资源不存在')
          break
        case 500:
          Message.error('服务器内部错误')
          break
        default:
          Message.error('未知错误')
      }
    } else {
      Message.error('网络连接错误,请检查网络设置')
    }
    return Promise.reject(error)
  }
)

export default service

❓其他

1. 疑问与作者HowieCong声明

  • 如有疑问、出错的知识,请及时点击下方链接添加作者HowieCong的其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong

  • 若想让作者更新哪些方面的技术文章或补充更多知识在这篇文章,请及时点击下方链接添加里面其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong

  • 声明:作者HowieCong目前只是一个前端开发小菜鸟,写文章的初衷只是全面提高自身能力和见识;如果对此篇文章喜欢或能帮助到你,麻烦给作者HowieCong点个关注/给这篇文章点个赞/收藏这篇文章/在评论区留下你的想法吧,欢迎大家来交流!

2. 作者社交媒体/邮箱-HowieCong