总结 Vite2.x + Vue3.x 有哪些常用的基操

1,476 阅读3分钟

这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战

前言

到现在 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.ts
const 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 -D
npm 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];

// axios
const instance = axios.create({
  baseURL: base_URL,
  timeout: 30000,
});

去除 console.log 和 vconsole 调试

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

//...import
import { 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.js
import { 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.js
import 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 编译是真的快~,尤大给力( ̄▽ ̄)。