vue3项目搭建规范:第三方库集成(项目实战)

2022-10-04 11:16:23

本文章根据coderwhy老师课程整理,结合视频食用更佳深入Vue3+TypeScript技术栈-coderwhy大神新课-学习视频教程-腾讯课堂

一、vue.config.js配置

vue.config.js有三种配置方式:三选一即可

* 方式一:直接通过CLI提供给我们的选项来配置:
        比如publicPath:配置应用程序部署的子目录(默认是 `/`,相当于部署在 `https://www.my-app.com/`);
        比如outputDir:修改输出的文件夹;
* 方式二:通过configureWebpack修改webpack的配置:
        可以是一个对象,直接会被合并;
        可以是一个函数,会接收一个config,可以通过config来修改配置;
* 方式三:通过chainWebpack修改webpack的配置:

        是一个函数,会接收一个基于webpack-chain的config对象,可以对配置进行修改;

根目录下创建vue.config.js文件

//方式二的依赖path模块
const path = require('path')

module.exports = {
  //方式一:
  // outputDir: './build',
  //方式二:里面的内容最终会在webpack中合并
  // configureWebpack: {
  //   resolve: {
  //     alias: {
  //       views: '@/views',
  //       components: '@/components'
  //     }
  //   }
  // },
  
  // configureWebpack: (config) => {
  //   config.resolve.alias = {
  //     '@': path.resolve(__dirname, 'src'),
  //     views: '@/views'
  //   }
  // },
  //方式三:
  chainWebpack: (config) => {
    config.resolve.alias
      .set('@', path.resolve(__dirname, 'src'))
      .set('views', '@/views')
  }
}

二、vue-router集成

安装vue-router的最新版本:

npm install vue-router@next

创建router对象:

import { createRouter, createWebHashHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    redirect: '/main'
  },
  {
    path: '/main',
    component: () => import('../views/main/main.vue')
  },
  {
    path: '/login',
    component: () => import('../views/login/login.vue')
  }
]

const router = createRouter({
  routes,
  history: createWebHashHistory()
})

export default router

main.ts中安装router:

import router from './router'

createApp(App).use(router).mount('#app')

在App.vue中配置跳转:

<template>
  <div id="app">
    <router-link to="/login">登录</router-link>
    <router-link to="/main">首页</router-link>
    <router-view></router-view>
  </div>
</template>


三、vuex集成

安装vuex:

npm install vuex@next

创建store对象:

import { createStore } from 'vuex'

const store = createStore({
  state() {
    return {
      name: 'coderwhy'
    }
  },
  mutations: {},
  getters: {},
  actions: {},
  modules: {}
})

export default store

main.ts中安装store:

import store from './store'

createApp(App).use(router).use(store).mount('#app')

在App.vue中使用:

<h2>{{ $store.state.name }}</h2>

四、element-plus集成

Element Plus,一套为开发者、设计师和产品经理准备的基于 Vue 3.0 的桌面端组件库:

* 在Vue2中都使用过element-ui,而element-plus正是element-ui针对于vue3开发的一个UI组件库;
* 它的使用方式和很多其他的组件库是一样的,所以学会element-plus,其他类似于ant-design-vue、NaiveUI、VantUI都是差不多的;

安装element-plus

npm install element-plus

1. 全局引入

一种引入element-plus的方式是全局引入,代表的含义是所有的组件和插件都会被自动注册:

import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'//引用组件库的样式

import router from './router'
import store from './store'

createApp(App).use(router).use(store).use(ElementPlus).mount('#app')

2. 局部引入

也就是在开发中用到某个组件对某个组件进行引入:

<template>
  <div id="app">
    <router-link to="/login">登录</router-link>
    <router-link to="/main">首页</router-link>
    <router-view></router-view>

    <h2>{{ $store.state.name }}</h2>

    <el-button>默认按钮</el-button>
    <el-button type="primary">主要按钮</el-button>
    <el-button type="success">成功按钮</el-button>
    <el-button type="info">信息按钮</el-button>
    <el-button type="warning">警告按钮</el-button>
    <el-button type="danger">危险按钮</el-button>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'

import { ElButton } from 'element-plus'

export default defineComponent({
  name: 'App',
  components: {
    ElButton
  }
})
</script>

<style lang="less">
</style>

但是我们会发现是没有对应的样式的,引入样式有两种方式:

* 全局引用样式(像之前做的那样);
* 局部引用样式(通过babel的插件);

1.安装babel的插件:

npm install babel-plugin-import -D

2.配置babel.config.js

module.exports = {
  plugins: [
    [
      'import',
      {
        libraryName: 'element-plus',
        customStyleName: (name) => {
          return `element-plus/lib/theme-chalk/${name}.css`
        }
      }
    ]
  ],
  presets: ['@vue/cli-plugin-babel/preset']
}

但是这里依然有个弊端:

* 这些组件我们在多个页面或者组件中使用的时候,都需要导入并且在components中进行注册;
* 所以我们可以将它们在全局注册一次;

可以封装起来。根目录下建global文件夹,下建index.ts放全局的注册

import 'element-plus/lib/theme-chalk/base.css'
import {
  ElButton,
  ElTable,
  ElAlert,
  ElAside,
  ElAutocomplete,
  ElAvatar,
  ElBacktop,
  ElBadge,
} from 'element-plus'

const components = [
  ElButton,
  ElTable,
  ElAlert,
  ElAside,
  ElAutocomplete,
  ElAvatar,
  ElBacktop,
  ElBadge
]

export function registerApp (app) {
  for (const cpn of components) {
    app.component(cpn.name, cpn)
  }
}

在main.ts中引用

import { registerApp } from '../global'

const app = createApp(App)

registerApp(app)

五、axios集成

axios安装

npm install axios

可全局配置

全局的配置
axios.defaults.baseURL = 'http://httpbin.org'
axios.defaults.timeout = 10000

可局部配置,即每一个单独配置

axios
  .get('/get', {
    params: {
      name: 'coderwhy',
      age: 18
    },
    timeout: 5000,
    headers: {}
  })
  .then((res) => {
    console.log(res.data)
  })

多个请求一起返回

axios.all -> 多个请求, 一起返回
axios
  .all([
    axios.get('/get', { params: { name: 'why', age: 18 } }),
    axios.post('/post', { data: { name: 'why', age: 18 } })
  ])
  .then((res) => {
    console.log(res[0].data)
    console.log(res[1].data)
  })

axios的拦截器

axios 的拦截器:interceptors

  如果我们想在请求之前做点什么,用拦截器再好不过了

  拦截器一般做什么?

    1. 修改请求头的一些配置项

    2. 给请求的过程添加一些请求的图标

    3. 给请求添加参数

  1. 全局的拦截器配置

// fn1: 请求发送成功会执行的函数
// fn2: 请求发送失败会执行的函数
axios.interceptors.request.use(
  (config) => {
    // 想做的一些操作
    // 1.给请求添加token
    // 2.isLoading动画
    console.log('请求成功的拦截')
    return config
  },
  (err) => {
    console.log('请求发送错误')
    return err
  }
)

// fn1: 数据响应成功(服务器正常的返回了数据 20x)
axios.interceptors.response.use(
  (res) => {
    console.log('响应成功的拦截')
    return res
  },
  (err) => {
    console.log('服务器响应失败')
    return err
  }
)

 2. 局部的拦截器

let instance = axios.create({
    baseURL:"./json/",
    timeOut:5000
  });
  instance.interceptors.request.use(config=>{
    console.log(1234);
    return config
  },err=>{
    console.log(err)
  })
  instance({
    url:"/01.json",
    method:"get"
  }).then(res=>{
    console.log(res)
  })

六、区分不同环境

在开发时,我们可能需要根据不同的环境设置环境变量

开发环境: development

生成环境: production

测试环境: test

常见方式有三种:

1.手动修改不同的变量(不推荐)

2.根据peocess.env.NODE_ENV的值来区分(用的比较多)

3.编写不同的环境变量配置文件(也推荐)

一、根据peocess.env.NODE_ENV的值来区分

创建config.ts文件

// 开发环境: development
// 生成环境: production
// 测试环境: test

let BASE_URL = ''
const TIME_OUT = 10000

if (process.env.NODE_ENV === 'development') {
  BASE_URL = 'http://123.207.32.32:8000/'
} else if (process.env.NODE_ENV === 'production') {
  BASE_URL = 'http://coderwhy.org/prod'
} else {
  BASE_URL = 'http://coderwhy.org/test'
}

export { BASE_URL, TIME_OUT }

二、编写不同的环境变量配置文件

在文件中配置对应的参数

VUE_APP_BASE_URL=https://coderwhy.org/dev
VUE_APP_BASE_NAME=coderwhy

在其他文件中调用时

console.log(process.env.VUE_APP_BASE_URL)
console.log(process.env.VUE_APP_BASE_NAME)

六、axios+ts请求库封装

思路:创建service目录

service
  request
    --config.ts
    --index.ts
    --type.ts
  --index.ts

在request的index.ts中封装导出,封装是封装到类里,比function封装性更强

import axios from 'axios'


class HYRequest {
    //封装方法。到时候hyRequest.request等调用
    request() {}
    get() {}
}
export default HYRequest

在service下index.ts中封装导出

import HYRequest from './request'
import { BASE_URL, TIME_OUT } from './request/config'
const hyRequest = new HYRequest({
})

export default hyRequest

在main.ts中使用

import hyRequest from './service'

hyRequest.request({
  url: '/home/multidata',
  method: 'GET',
  headers: {},
  interceptors: {
    requestInterceptor: (config) => {
      console.log('单独请求的config')
      config.headers['token'] = '123'
      return config
    },
    responseInterceptor: (res) => {
      console.log('单独响应的response')
      return res
    }
  }
})

 在request下index.ts中封装类calss HYRequest,可以创建多个实例,如果有多个baseURL的时候,就可以创建多个实例,每个实例有自己的baseURL,大多数项目是向一个服务器请求,就一个实例对象就行

可给实例创建拦截器,拦截器的封装可分为给全局、单个实例、单个请求添加拦截器(用的比较少),如果觉得复杂,也可只给全局添加封装拦截器

最终封装

service
  request
    --config.ts
    --index.ts
    --type.ts
  --index.ts

request--type.ts

import type { AxiosRequestConfig, AxiosResponse } from 'axios'

export interface HYRequestInterceptors<T = AxiosResponse> {
  requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
  requestInterceptorCatch?: (error: any) => any
  responseInterceptor?: (res: T) => T
  responseInterceptorCatch?: (error: any) => any
}

export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
  interceptors?: HYRequestInterceptors<T>
  showLoading?: boolean
}

request--config.ts

let BASE_URL = ''
const TIME_OUT = 10000

if (process.env.NODE_ENV === 'development') {
  BASE_URL = 'http://123.207.32.32:8000/'
} else if (process.env.NODE_ENV === 'production') {
  BASE_URL = 'http://coderwhy.org/prod'
} else {
  BASE_URL = 'http://coderwhy.org/test'
}

export { BASE_URL, TIME_OUT }

request--index.ts

import axios from 'axios'
import type { AxiosInstance } from 'axios' //导入类型
import type { HYRequestInterceptors, HYRequestConfig } from './type'

import { ElLoading } from 'element-plus'
import { ILoadingInstance } from 'element-plus/lib/el-loading/src/loading.type'

const DEAFULT_LOADING = true

class HYRequest {
  instance: AxiosInstance  //类型注解
  interceptors?: HYRequestInterceptors
  showLoading: boolean
  loading?: ILoadingInstance

  //HYRequestConfig为了扩展类型
  constructor(config: HYRequestConfig) {
    // 创建axios实例
    this.instance = axios.create(config)

    // 保存基本信息
    this.showLoading = config.showLoading ?? DEAFULT_LOADING
    this.interceptors = config.interceptors

    // 使用拦截器
    // 1.从config中取出的拦截器是对应的实例的拦截器
    this.instance.interceptors.request.use(
      this.interceptors?.requestInterceptor,
      this.interceptors?.requestInterceptorCatch
    )
    this.instance.interceptors.response.use(
      this.interceptors?.responseInterceptor,
      this.interceptors?.responseInterceptorCatch
    )

    // 2.添加所有的实例都有的拦截器(如果觉得复杂,就只封装这个全局的)
    this.instance.interceptors.request.use(
      (config) => {
        console.log('所有的实例都有的拦截器: 请求成功拦截')

        if (this.showLoading) {
          this.loading = ElLoading.service({
            lock: true,
            text: '正在请求数据....',
            background: 'rgba(0, 0, 0, 0.5)'
          })
        }
        return config
      },
      (err) => {
        console.log('所有的实例都有的拦截器: 请求失败拦截')
        return err
      }
    )

    this.instance.interceptors.response.use(
      (res) => {
        console.log('所有的实例都有的拦截器: 响应成功拦截')

        // 将loading移除
        this.loading?.close()

        const data = res.data
        if (data.returnCode === '-1001') {
          console.log('请求失败~, 错误信息')
        } else {
          return data
        }
      },
      (err) => {
        console.log('所有的实例都有的拦截器: 响应失败拦截')
        // 将loading移除
        this.loading?.close()

        // 例子: 判断不同的HttpErrorCode显示不同的错误信息
        if (err.response.status === 404) {
          console.log('404的错误~')
        }
        return err
      }
    )
  }

  request<T>(config: HYRequestConfig<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      // 1.单个请求对请求config的处理
      if (config.interceptors?.requestInterceptor) {
        config = config.interceptors.requestInterceptor(config)
      }

      // 2.判断是否需要显示loading
      if (config.showLoading === false) {
        this.showLoading = config.showLoading
      }

      this.instance
        .request<any, T>(config)
        .then((res) => {
          // 1.单个请求对数据的处理
          if (config.interceptors?.responseInterceptor) {
            res = config.interceptors.responseInterceptor(res)
          }
          // 2.将showLoading设置true, 这样不会影响下一个请求
          this.showLoading = DEAFULT_LOADING

          // 3.将结果resolve返回出去
          resolve(res)
        })
        .catch((err) => {
          // 将showLoading设置true, 这样不会影响下一个请求
          this.showLoading = DEAFULT_LOADING
          reject(err)
          return err
        })
    })
  }

  get<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'GET' })
  }

  post<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'POST' })
  }

  delete<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'DELETE' })
  }

  patch<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'PATCH' })
  }
}

export default HYRequest

service---index.ts

// service统一出口
import HYRequest from './request'
import { BASE_URL, TIME_OUT } from './request/config'

const hyRequest = new HYRequest({
  baseURL: BASE_URL,
  timeout: TIME_OUT,
  interceptors: {
    requestInterceptor: (config) => {
      // 携带token的拦截
      const token = ''
      if (token) {
        config.headers.Authorization = `Bearer ${token}`
      }

      console.log('请求成功的拦截')
      return config
    },
    requestInterceptorCatch: (err) => {
      console.log('请求失败的拦截')
      return err
    },
    responseInterceptor: (res) => {
      console.log('响应成功的拦截')
      return res
    },
    responseInterceptorCatch: (err) => {
      console.log('响应失败的拦截')
      return err
    }
  }
})

export default hyRequest
  • 作者:米儿web
  • 原文链接:https://blog.csdn.net/qq_24767091/article/details/119859988
    更新时间:2022-10-04 11:16:23