拉勾前端高薪就业课程笔记第十弹(模块3-6)

这篇笔记详细介绍了在Vue项目中使用TypeScript、ElementUI、样式处理、接口处理、vue-router路由拦截、axios请求与响应拦截以及token过期处理的方法。通过一个课程管理项目展示了如何在实际开发中应用这些技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本模块主要讲解Vue实战中遇到的一些技术点,通过一个课程管理项目的实操来讲解在实战中如何使用Vue进行开发。

Vue项目中使用TypeScript

在vue项目中使用ts有两种情况,一、在新项目中使用TypeScript;二、在已有Vue项目中使用TypeScript。

在新项目中使用TypeScript
使用@vue/cli工具创建是选择使用ts即可。创建完的项目中会有两个ts文件。
在这里插入图片描述
其中shims-ts.d.ts文件是jsx语法的类型补充

/**
 * Jsx 类型声明补充
 */
import Vue, { VNode } from 'vue'

declare global {
  namespace JSX {
    // tslint:disable no-empty-interface
    interface Element extends VNode {}
    // tslint:disable no-empty-interface
    interface ElementClass extends Vue {}
    interface IntrinsicElements {
      [elem: string]: any
    }
  }
}

shims-vue.d.ts则是.vue文件的类型声明

/**
 * import xx from 'xxx.vue'
 * ts 无法识别.vue文件
 * 通过这个声明.vue模块都是Vue
 */
declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}

在已有项目中使用TypeScript
只要使用@vue/cli来安装ts插件即可。

vue add @vue/typescript

TypeScript方式定义vue组件

TypeScript定义vue组件有两种方式:

  1. 使用Vue.component或Vue.extend定义组件,即OptionApi的方式定义组件。

    import Vue from 'vue'
    
    export default Vue.extend({
      name: 'App'
    })
    
  2. 使用vue-class-component装饰器。装饰器语法尚未定案,并不稳定,不推荐在生成中使用。

    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    @Component({
      name: 'App' // 选项参数
    })
    export default class App extends Vue {
      // 初始数据可以直接声明为实例的 property
      message: string = 'Hello!'
    
      // 组件方法也可以直接声明为实例的方法
      onClick (): void {
        window.alert(this.message)
      }
    }
    

在Vue项目中使用elementUI

要在Vue项目中使用elementUI,只需要安装之后,然后引入即可。

elementUI的引入有两种情况,完整引入和按需引入。
完整引入
引入整个Element。在main.ts中添加以下代码

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)

按需引入
需要借助 babel-plugin-component插件。安装插件之后,修改babel配置如下:

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

在main.ts中注册要使用的组件

import Vue from 'vue';
import { Button, Select } from 'element-ui';
import App from './App.vue';

Vue.component(Button.name, Button);
Vue.component(Select.name, Select);
/* 或写为
 * Vue.use(Button)
 * Vue.use(Select)
 */

new Vue({
  el: '#app',
  render: h => h(App)
});

项目中样式处理

在src/styles中创建下列四个样式文件
在这里插入图片描述
然后在main.ts中引入全局样式文件index.scss

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import ElementUI from 'element-ui'
// import 'element-ui/lib/theme-chalk/index.css'

// 引入全局样式,在全局样式中引入了element的样式
import './styles/index.scss'

Vue.use(ElementUI)

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

共享全局样式变量
在组件中要使用全局的样式变量,需要在样式文件中引入全局的样式变量文件,之后才能使用

<style lang="scss" scoped>
@import './style/variable.scss';
.text {
  color: $success-color;
}
</style>

但是这样需要在使用的组件里都需要引入样式文件,比较麻烦。设置共享全局样式变量来避免这种麻烦。
根目录下创建vue.config.js,添加下列代码。会为每个组件的style注入prependData中的内容。

module.exports = {
  css: {
    loaderOptions: {
      // 默认情况下 `sass` 选项会同时对 `sass` 和 `scss` 语法同时生效
      // 因为 `scss` 语法在内部也是由 sass-loader 处理的
      // 但是在配置 `prependData` 选项的时候
      // `scss` 语法会要求语句结尾必须有分号,`sass` 则要求必须没有分号
      // 在这种情况下,我们可以使用 `scss` 选项,对 `scss` 语法进行单独配置
      scss: {
        prependData: '@import "~@/styles/variables.scss";'
      }
    }
  }
}

这样在组件中就可以直接使用全局样式变量,不用在组件中手动引入文件。

<style lang="scss" scoped>
.text {
  color: $success-color;
}
</style>

接口处理

接口处理是为开发环境配置跨域处理,使得能够正常访问后台接口。
客户端配置服务端代理跨域需要在vue.config.js中配置devServer的代理。会拦截proxy中包含属性字符串的请求,将其替换成target。如下配置之后http://localhost:8099/boss开头的请求将转发到http://eduboss.lagou.com下。

module.exports = {
  devServer: {
    proxy: {
      '/boss': {
        target: 'http://eduboss.lagou.com ',
        // ws: true, // websocket协议
        changeOrigin: true // 是否修改请求头中的host
      },
      '/front': {
        target: 'http://edufront.lagou.com',
        changeOrigin: true // 是否修改请求头中的host
      }
    }
  }
}

vue-router 路由拦截

router.beforeEach((to,from,next)=>{}) // 在路由跳转之前会触发回调。必须调用next,否则不会路由不会变化
// 路由拦截,再这里判断是否已经登录,没有登录则重定向到登录页面,并记录当前地址

router.beforeEach((to, from, next) => {
  if (to.matched.some(item => item.meta.auth)) { // 判断目标地址是否需要登录,meta是自定义数据对象,在定义路由时传入的
    if (!store.state.user) { // 未登录,重定向到登录页,并记录页面地址
      next({
        name: 'login',
        query: {
          redirect: to.fullPath
        }
      })
    } else {
      next()
    }
  } else {
    next()
  }
})

axios请求拦截

每次请求发送之前都会执行回调函数,回调函数返回config将作为最终请求下发的配置。

// 请求拦截器
request.interceptors.request.use(config => {
  unResponseNum++
  // 设置头部Authorization
  if (store.state.user && store.state.user.access_token) {
    config.headers.Authorization = store.state.user.access_token
  }
  return config
})

axios响应拦截

请求返回响应之后,如果返回的状态码是2xx,将执行第一个回调函数,即成功的回调函数,非2xx状态码将执行第二个回调函数,即失败的回调函数。

request.interceptors.response.use(res => {}, err => {})

token过期处理

token过期将会返回401状态码。需要根据情况判断是否是刷新token还是重新登录。整体流程如下:

  1. 首先需要判断是否是token过期还是没有登录过
  2. 如果是token过期,才去根据记录的刷新token属性去刷新token
  3. 刷新token过程中,可能有请求返回401,需要记录返回401的请求,待刷新token之后,使用新的token重新下发这些请求。
  4. 有一种特殊请求,有些401请求,在token刷新之后才返回,需要通过判断是否所有请求都返回了,所有请求返回之后再去重新下发请求。
import axios from 'axios'
import store from '@/store/index'
import { Message } from 'element-ui'
import router from '@/router'
import qs from 'qs'

const request = axios.create({
  // 配置选项
})

function redirectLogin () {
  router.push({
    name: 'login',
    query: {
      redirect: router.currentRoute.fullPath
    }
  })
  needReRequest = [] // 清空需要重发的请求
}

function refleshToken () {
  return axios.create()({
    method: 'POST',
    url: '/front/user/refresh_token',
    data: qs.stringify({ refreshtoken: store.state.user.refresh_token })
  })
}

// 未返回的请求的个数
let unResponseNum = 0

// 请求拦截器
request.interceptors.request.use(config => {
  unResponseNum++
  // 设置头部Authorization
  if (store.state.user && store.state.user.access_token) {
    config.headers.Authorization = store.state.user.access_token
  }
  return config
})

let isRefleshToken = false // 是否正在刷新token
let needReRequest: any[] = [] // 刷新token期间的401请求

function doReRequest () {
  if (unResponseNum === 0) {
    needReRequest.forEach(cb => cb())
    needReRequest = [] // 清空请求缓存
  }
}

// 响应拦截器
request.interceptors.response.use(res => {
  unResponseNum--
  doReRequest()
  return new Promise((resolve, reject) => { // 2xx的成功响应会执行这里
    if (res.data.state === 1) {
      resolve(res)
    } else {
      Message.error(res.data.message)
      reject(res)
    }
  })
}, error => { // 失败响应会执行这里
  unResponseNum--
  if (error.response) { // 请求有非2xx响应
    const { status } = error.response
    // 根据状态吗判断
    if (status === 400) { // 请求参数错误
      Message.error('请求参数错误')
    } else if (status === 401) { // token 无效,可能没有传递token, token过期
      // 判断是否是token过期
      if (!store.state.user) { // 没有记录过token,跳转到登录页
        redirectLogin()
        return Promise.reject(error)
      }
      if (!isRefleshToken) { // 没有刷新token
        isRefleshToken = true
        // 已经有token,刷新token
        return refleshToken().then(response => { // 请求成功,刷新store中的user信息,重新请求401状态的取数
          const { data } = response
          if (data.state === 1) {
            // 重新记录token
            store.commit('setUser', data.content)
            // 重新发送刷新token期间的401请求
            doReRequest()
            // 重新请求
            return request(error.config)
          } else {
            throw new Error(data)
          }
        }).catch(() => { // 刷新token失败,重新登录
          // 清空保存的用户信息
          store.commit('setUser', null)
          if (unResponseNum === 0) { // 只有当所有请求都返回了时才跳转到登录
            redirectLogin()
          }
          return Promise.reject(error)
        }).finally(() => {
          isRefleshToken = false
        })
      }
      // 正在刷新token,要返回一个挂起的请求,使用promise挂起请求并保存
      return new Promise(resolve => {
        needReRequest.push(() => {
          resolve(request(error.config))
        })
        doReRequest()
      })
    } else if (status === 403) { // 没有权限访问资源
      Message.error('没有权限,请联系管理员')
    } else if (status === 404) {
      Message.error('请求资源不存在')
    } else if (status >= 500) {
      Message.error('服务器发生错误,请联系管理员')
    }
  } else if (error.request) { // 发出了请求,但是没有响应
    Message.error('请求超时,请重试!')
  } else { // 设置请求时触发了错误
    Message.error(`请求失败:${error.message}`)
  }
  doReRequest()
  return Promise.reject(error)
})

export default request
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值