Skip to main content

Vite + Vue3 项目基操

前言#

到现在 Vue3.0 正式发布快有一个年头了,同时后续也发布的更快的打包工具 Vite 也更新到了 2.x 版本,相信有很多小伙伴也已经在公司的新项目中应用起来,体验这一快感,下面这篇文章总结下项目中基于 Vite2.x + Vue3.0 常用小基操。

注册组件#

在开发项目多数情况少不了使用 UI 框架组件来快速开发搭建我们的页面,一般使用组件库时,为了减小包体积(tree-shshaking 技术),都采用按需加载的方式。

以 Vant 组件使用为例,通常我们会:

import { createApp } from "vue";import App from "./App.vue";
const app = createApp(App);
// 引入组件import {  Progress,  Picker,  Popup,  ....} from "vant";
// app.use() 注册app.use(Progress)   .use(Picker)   .use(Popup)   ...   .mount("#app");

简单的引用几个组件还好,看起来没有拉面条,想想就这样引入算了,如果有二十三十个就显得很长也会让 main.ts 越来越大,基于模块化开发思想,最好单独封装到一个配置文件中。

创建一个 config 文件夹下 vant.config.ts 文件名中

import {  Progress,  Picker,  Popup,  ....} from "vant";
const components = {  Progress,  Picker,  Popup,  ....}
// .use 内部会自动寻找 install 方法进行调用注册挂载const componentsInstallHandler = {    install(app) {        Object.keys(components).forEach(key => app.use(components[key]))    }}
export default componentsInstallHandler

main.ts 中

// ...省略 createApp 引入
// 导入import vantComponentsInstall from "./config/vant.config";
app.use(vantComponentsInstall).mount("#app");

这样操作完是不是舒服多了~

组件全局注册#

自己编写了一个比如 <Header/> 组件想要全局使用,这时候可以调用 createApp() 返回的 component 函数进行全局注册

// ...省略 createApp 引入
import Header from "@/components/Header/Index.vue";
app.component("Header", Header);

过滤器#

在 3.x 中,过滤器已删除,不再支持。相反地,可以用方法调用或计算属性代替它。

<template>  <div>    {{ computedFunc("computed") }}  </div></template><script lang="ts">import { computed, defineComponent } from "vue";export default defineComponent({  setup() {    // 使用 闭包 接受传递参数    const computedFunc = () => {      return (val) => val;    };    return {      computedFunc,    };  },});</script>

全局过滤器#

比如多个组件中数据渲染值都需要解密后才能正确显示,不可能每一个组件里面都写 computed,这时候可以通过 全局属性 在所有组件中使用它:

// main.tsconst app = createApp(App)
app.config.globalProperties.$filters = {  decrypt(value) {    return mdDecrypt(value)},

然后,就可以通过 $filters 对象修改所有的模板:

<template>  <p>{{ $filters.decrypt(encryptVal) }}</p></template>

自定义 UI 框架主题色#

每个设计师采用的主题色不一样,UI 框架默认的主题色不是我们想要的,想必我们需要自定义配置一下。下面以 Vant 为例:

import { defineConfig } from 'vite'
export default defineConfig({  css: {    preprocessorOptions: {      less: {        javascriptEnabled: true,        // 需要自定义样式变量名称        modifyVars: {          'blue': '#00C6B8',          'gray-7': '#333333',          'gray-4': '#00C6B8',           ...        },      },    },  },})

服务代理 server#

在开发测试调用后端服务地址,少不了需要 server 代理,Vue2.x 配合 Vue-CLI 可以在 config/index.js 添加 proxyTable 修改添加, Vite 中又是怎么添加的呢,Vite 官网也有说明,下面看看如何配置:

import { defineConfig } from 'vite'
export default defineConfig({  server: {    port: 3006, // 设置端口,默认 3000    open: true, // 设置启动时,自动打开浏览器    cors: true, // 允许跨域    host:'10.x.x.x', // 本地 IP 访问,如何移动端手机调试就需要填写本地 IP 了    proxy: {      '/api': {        target: 'http://10.x.x.x.x',        changeOrigin: true,        secure: false,        rewrite: path => path.replace('/api/', '/')      }      // 配置多个继续添加      '/api2': {        ...      }    },  },})

配置别名#

写法有很多种,可以是一个对象,或一个 { find, replacement } 具体可以看 options

import vue from '@vitejs/plugin-vue'import { defineConfig } from 'vite'
export default defineConfig({  resolve: {    alias: [      {        find: "@",        replacement: resolve(__dirname, "src"),      },      {        find: "@ASS",        replacement: resolve(__dirname, "src/assets"),      },    ]  },
  server: {    ....  },})

移动端 rem 自适应#

结合使用 amfe-flexiblepostcss-pxtorem 实现移动端适配。

amfe-flexible

amfe-flexible 是配置克伸缩布局方案,主要将 1rem 设为 viewWidth/10。

postcss-pxtorem

postcss-pxtorem 是 postcss 的插件,用于将像素单位生成 rem 单位。

npm 安装

npm i amfe-flexible -Dnpm i postcss-pxtorem -D

安装完毕后,在 main.ts 中导入 amfe-flexible 插件

// ...import "amfe-flexible/index.js";

postcss-pxtorem 配置

配置 postcss-pxtorem 方式有三种,可以在 vite.config.ts、.postcssrc.ts、postcss.copnfig.ts 其中之一配置即可。

vite.config.ts 中配置如下:

import { defineConfig } from 'vite'
export default defineConfig({  css: {    ... ,    loaderOptions: {      postcss: {        plugins: [          require('postcss-pxtorem')({            rootValue: 37.5, // 根据设计稿宽度除以10进行设置,这边假设设计稿为375,即rootValue设为37.5,我这里设置            propList: ['*'] // // propList是设置需要转换的属性,这边*为所有都进行转换。          })        ]      }    }  },})

根目录创建 .postcssrc.ts 或者 postcss.copnfig.ts 配置如下:

module.exports = {  plugins: {    autoprefixer: {      overrideBrowserslist: [        'Android 4.1',        'iOS 7.1',        'Chrome > 31',        'ff > 31',        'ie >= 8',        'last 10 versions', // 所有主流浏览器最近10版本用      ],    },    postcss-pxtorem: {      rootValue: 37.5,      propList: ['*'],    },  },}

配置完毕重启看看效果:

image.png

还可以 引入 rem.js 方式,不过需要编写样式的时候自己 px 转 rem ,好在 vscode 扩展商店中有 px to rem 插件一件转换,这里展开了,可以自行 google 一下就有了。

环境变量#

有时候项目开发中需要部署有三种运行环境中比如 测试环境(test)、开发环境(dev)、生成环境(prod),请求的接口地址也需要更换,如何动态的更换呢? Vite 在一个特殊的 import.meta.env 对象上暴露环境变量,可以通过暴露的环境变量来判断是运行在哪一个环境中。

npm run dev 运行 import.meta.env 暴露的变量值为 development

npm run build import.meta.env 暴露的变量值为 production

还有一种 测试环境(test)如何拿到呢?

可以通过 package.json 配置 scripts run 命令

  "scripts": {    "dev": "vite",    "build": "vite build",    "test:build": "vite build --mode test",  },

运行 npm run test:build import.meta.env 暴露变量值为 test 了 --mode xxx 名称可以自定义

请求 URL 统一管理 config.ts:

export const DEV_BASE_URL = "https://xxx";export const TEST_BASE_URL = "https://xxx";export const PRO_BASE_URL = "https://xxx";

以封装 axios 设置 baseURL 为例:

import { DEV_BASE_URL, PRO_BASE_URL, TEST_BASE_URL, TIMEOUT } from "./config";
const envMap = {  development: DEV_BASE_URL,  production: PRO_BASE_URL,  test: TEST_BASE_URL,};
const base_URL = envMap[import.meta.env.MODE];
// axiosconst instance = axios.create({  baseURL: base_URL,  timeout: 30000,});

去除 console.log 和 vconsole 调试#

在 vue-cli 中我们可以配置 configureWebpack去除,来看 vite 如何根据在生成环境去除的:

//...importimport { defineConfig, loadEnv, ConfigEnv, UserConfigExport } from "vite";
export default ({ command, mode }): UserConfigExport => {  const isEnvProduction = mode === "production"  return defineConfig({    //...其他配置    build: {      terserOptions: {        compress: {          drop_console: isEnvProduction, // true 为关闭          drop_debugger: isEnvProduction,        },      },    },  });};

在配置文件中我们无法通过 import.meta.env 获取到运行环境变量,因为它是暴露再客户端源码中的,所以我们可以通过返回一个函数的方式,在函数接收的 mode 参数中获取到环境变量。

hooks 使用#

升级 Vue3 后,让人最脑壳痛应该不是 Compostion API 语法,而是提供了给我们全新的组织代码的思维方式。

刚开始 Vue3.0 正式发布学习完之后,代码也是遵循 Compostion API 正确用法,可是当组件庞大,逻辑复杂,代码量也是很多,发现比 Options API 写法维护性更差,也是慢慢写成了 拉面条...

下面结合真实业务需求讲解一下

比如有一个获取验证码方法,点击获取完毕 60s 倒计时后才能重新获取,正常霹雳巴拉我们会这么写:

example.vue

<template>  <button @click="onGetCode">{{ codeText }}</button></template><script lang="ts">import { defineComponent, reactive, toRefs } from "vue";export default defineComponent({  setup(props, ctx) {    const state = reactive({      isGetCode: false,      codeText: "获取验证码",      countDown: 60,      phone: "",    });    // 点击后倒计时    const countTime = () => {      state.isGetCode = true;      let timer;      timer = setInterval(        (function setIntervalFunc() {          if (countDown !== 0) {            state.codeText = `重新发送${countDown.value--}`;          } else {            state.isGetCode = false;            state.codeText = "获取验证码";            state.countDown = 60;            clearInterval(timer);          }          return setIntervalFunc;        })(),        1000      );    };    // 获取验证码请求    const onGetCode = async () => {      const { success, message } = await getPhoneCode(encrypt(state.phone));      if (success) {        countTime();        Toast.success(message);      }    };    return {      ...toRefs(state),      onGetCode,    };  },});</script>

从新上面代码看没有问题啊,Compostion API 不就是这么用啊,梭哈完毕,可是再想想,如何这个页面只是其中一个逻辑,还有十个逻辑,都内聚在一起,依次继续写在后面,写到后面不就是我们所说的拉面条了吗?

所以可以单独将逻辑拆分到一个文件中

// useVerificationCode.jsimport { reactive, toRefs } from "vue";
export default const useVerificationCode = () => {    const state = reactive({        isGetCode: false,        codeText: '获取验证码',        countDown: 60,        phone: ''    })    // 点击后倒计时    const countTime = () => {        state.isGetCode = true        let timer        timer = setInterval(            (function setIntervalFunc() {                if (countDown !== 0) {                    state.codeText = `重新发送${countDown.value--}`                } else {                    state.isGetCode = false                    state.codeText = '获取验证码'                    state.countDown = 60                    clearInterval(timer)                }                return setIntervalFunc            })(),            1000        )    }    // 获取验证码请求    const onGetCode = async () => {        const { success, message } = await getPhoneCode(encrypt(state.phone))        if (success) {            countTime()            Toast.success(message)        }    }    return {        ...toRefs(state)        onGetCode,    }}

再看看改造后:

// example.vue<template>  <button @click="onGetCode">{{ codeText }}</button></template><script lang="ts">import { defineComponent } from "vue";// 引入 useVerificationCode.jsimport useVerificationCode from "./useVerificationCode.js";export default defineComponent({  setup(props, ctx) {    // 获取验证码    const { onGetCode, isGetCode, codeText, countDown, phone } =      useVerificationCode();
    // ...    // ...    return {      onGetCode,      isGetCode,      codeText,      countDown,      phone,    };  },});</script>

这样是不是清爽许多了,以前错误的观点,就是只有复用的逻辑才应该封装到 hook 中,在 官方的给例子 中,并不是强调逻辑复用,而是逻辑关注点分离,到这里相信大家都明白该怎么写了。但是在简单逻辑的组件中,过度抽离就没必要了。

总结

目前自己应用 Vue3.0 开发中的小项目中还没有遇到过大坑,面向百度、文档都能轻松解决,赶紧用起来吧,两个字 好使,Vite 编译是真的快~,尤大给力( ̄ ▽  ̄)。