文章目录

  • 目录结构
  • api 和 views
  • 封装 axios
  • axios 基本案例
  • axios.create 示例
  • axios 请求拦截器
  • axios 响应拦截器
  • request 库源码分析
  • router-view
  • 参考链接


目录结构

api 和 views

建议根据业务模块来划分 views,并且 将views 和 api 两个模块一一对应,从而方便维护。如下图:

element画登录页面_vue

封装 axios

axios 基本案例

const url = 'https://test.youbaobao.xyz:18081/book/home/v2'
axios.get(url, { 
  params: { openId: '1234' },
  // 在 http header 中添加一个 token
  headers: { token: 'abcd' }
}).then(response => {
  console.log(response)
}).catch(err => {
  // 捕获服务端抛出的异常,即返回非 200 请求
  console.log(err)
})

这个案例有两个问题:

  • 每个需要传入 token 的请求都需要添加 headers 对象,会造成大量重复代码
  • 每个请求都需要手动定义异常处理,而异常处理的逻辑大多是一致的,如果将其封装成通用的异常处理方法,那么每个请求都要调用一遍

axios.create 示例

下面使用 axios.create 对整个请求进行重构:

const url = '/book/home/v2'
const request = axios.create({
  baseURL: 'https://test.youbaobao.xyz:18081',
  timeout: 5000
})
request({
  url, 
  method: 'get',
  params: {
    openId: '1234'
  }
})

首先通过 axios.create 生成一个函数,该函数是 axios 实例,通过执行该方法完成请求,它与直接调用 axios.get 区别如下:

  • 需要传入 url 参数,axios.get 方法的第一个参数是 url
  • 需要传入 method 参数,axios.get 方法已经表示发起 get 请求

axios 请求拦截器

上述代码完成了基本请求的功能,下面需要为 http 请求的 headers 中添加 token,同时进行白名单校验,如 /login 不需要添加 token,并实现异步捕获和自定义处理:

const whiteUrl = ["/login"];
const url = "/book/home/v2";
const request = axios.create({
  baseURL: "https://test.youbaobao.xyz:18081",
  timeout: 5000
});
// 请求拦截器
request.interceptors.request.use(
  // 拦截了axios对象
  config => {
    // throw new Error('error...')
    // 将baseURL置空
    const url = config.url.replace(config.baseURL, "");
    // 如果白名单中有,直接返回axios
    if (whiteUrl.some(wl => url === wl)) {
      return config;
    }
    // 当白名单中没有时,追加token
    config.headers["token"] = "abcd";
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);
request({
  url,
  method: "get",
  params: {
    openId: "1234"
  }
})
  .then(res => {
    console.log(res);
  })
  .catch(err => {
    console.log(err);
  });

这里核心是调用了 request.interceptors.request.use 方法,即 axios 的请求拦截器,该方法需要传入两个参数,第一个参数是拦截器方法,包含一个 config 参数,我们可以在这个方法中修改 config 并且进行回传,第二个参数是异常处理方法,我们可以使用 Promise.reject(error) 将异常返回给用户进行处理,所以我们在 request 请求后可以通过 catch 捕获异常进行自定义处理

axios 响应拦截器

下面我们进一步增强 axios 功能,我们在实际开发中除了需要保障 http statusCode 为 200,还需要保证业务代码正确,上述案例中,我定义了 error_code 为 0 时,表示业务返回正常,如果返回值不为 0 则说明业务处理出错,此时我们通过 request.interceptors.response.use 方法定义响应拦截器,它仍然需要2个参数,与请求拦截器类似,注意第二个参数主要处理 statusCode 非 200 的异常请求,源码如下:

const whiteUrl = ["/login"];
const url = "/book/home/v2";
const request = axios.create({
  baseURL: "https://test.youbaobao.xyz:18081",
  timeout: 5000
});
// 请求拦截器
request.interceptors.request.use(
  // 拦截了axios对象
  config => {
    // throw new Error('error...')
    // 将baseURL置空
    const url = config.url.replace(config.baseURL, "");
    // 如果白名单中有,直接返回axios
    if (whiteUrl.some(wl => url === wl)) {
      return config;
    }
    // 当白名单中没有时,追加token
    config.headers["token"] = "abcd";
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);

// 响应拦截器
request.interceptors.response.use(
  response => {
    const res = response.data;
    if (res.error_code != 0) {
      // 请求失败,提示错误信息
      alert(res.msg);
      return Promise.reject(new Error(res.msg));
    } else {
      // 请求成功,返回数据
      return res;
    }
  },
  error => {
    return Promise.reject(error);
  }
);

request({
  url,
  method: "get",
  params: {
    openId: "1234"
  }
})
  .then(res => {
    console.log(res);
  })
  .catch(err => {
    console.log(err);
  });

request 库源码分析

有了上述基础后,我们再看 request 库源码就非常容易了

const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 5000
})

service.interceptors.request.use(
  config => {
    // 如果存在 token 则附带在 http header 中
    if (store.getters.token) {
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

service.interceptors.response.use(
  response => {
    const res = response.data

    if (res.code !== 20000) {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })
      // 判断 token 失效的场景
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // 如果 token 失效,则弹出确认对话框,用户点击后,清空 token 并返回登录页面
        MessageBox.confirm('Token已失效,是否重新登录', '确认登出', {
          confirmButtonText: '重新登录',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            // 为了重新实例化vue-router对象 避免bug
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      return res
    }
  },
  error => {
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service

router-view

创建和编辑的页面使用的是同一个component,默认情况下当这两个页面切换时并不会触发vue的created或者mounted钩子,官方说你可以通过watch $route的变化来做处理,但其实说真的还是蛮麻烦的。后来发现其实可以简单的在 router-view上加上一个唯一的key,来保证路由切换时都会重新渲染触发钩子了。这样简单的多了。

<router-view :key="key" />
computed: {
  key() {
    return this.$route.path
  }
}