likes
comments
collection
share

我在 vue3 开发中的踩的坑

作者站长头像
站长
· 阅读数 56

前期准备

由于 vite 在开发态是基于 ESM 进行模块化开发, 而 ESM 的浏览器兼容版本有限,如下图。

我在 vue3 开发中的踩的坑

所以,如果你打算使用 vite 作为构建工具去开发,你至少要有一个合适版本的浏览器。如果你和我一样,Chrome 版本的浏览器比较低,但是又不想升级,想留着偶尔方便自测和定位浏览器兼容问题,那我推荐你安装一个Chromium。这样你就可以一个电脑里面拥有两个 Chrome没有两个chrome的前端不是好前端[狗头]。

我在 vue3 开发中的踩的坑

这时候,你可能又会有另外一个问题,什么是 ESM? 关于这个问题,这里不展开说,有兴趣的可以看看这篇文章。通俗易懂的理解,就是在开发态,我们加载的是模块化的 ts 或者 js,而且在打包后,我们加载的就是的 CommonJS,如下图。

我在 vue3 开发中的踩的坑

我在 vue3 开发中的踩的坑

除此之外,你要升级你的 node 环境到 node 14 以上版本。而如果你也是用的 windows 7, 这就有了第二个问题, 如何在 windows 7下安装 node 14 需要将下载的 node 包放在指定的 nvm 文件夹同时将系统变量 NODE_SKIP_PLATFORM_CHECK 设置为 1

组件准备:因为希望组件风格和之前保持一致,为了更加灵活的修改组件,我们基于antdv进行了简单封装,并发布到私有的 npm 仓库。

组件自动引入unplugin-vue-components

上面的封装也带来另外一个坑,就是会导致无法使用 unplugin-vue-components。我去提了issues 希望可以支持组件名动态设置PR, 应该下个版本 AntDesignVueResolver 就可以支持了。

你可能要习惯的和 vue2 的不同

在实际开发过程中,从 vue2 升级到 vue3 我觉得有几个地方或许是需要适用一下的,这里也提一下。

组合式 API

组合式 API 是一系列 API 的集合, 它是 Vue 3 和 Vue2.7 的内置功能,而对于更老的 Vue 2 版本,则可以使用 @vue/composition-api包。组合式 API 包括:

我在 vue3 开发中的踩的坑

<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。个人感觉,不用这个语法糖写法上和 Vue 2 更加接近,而使用这个语法糖写起来则更丝滑些,写法对比如下图:

我在 vue3 开发中的踩的坑

我在 vue3 开发中的踩的坑

响应式

数组

有两种实现方式,如下图。我个人用下来,觉得写法一更丝滑些。

我在 vue3 开发中的踩的坑

响应式代理

你可能也注意到,对整个数组的变更,我用的是 Object.assign 去实现的,因为只有这样,才能保持数据的响应式。这和 Vue 2 也是有区别的,官网也有做说明响应式代理 vs. 原始值,原因和 Vue 3 的数据响应式原理有关。至于Vue 3 的数据响应式原理这里不展开说,可以参考我之前写的另一篇文章关于vue3的Proxy

我在 vue3 开发中的踩的坑

双向绑定实现

父组件

<template>
  <div class="hello">
    <h1 @click="showModal">打开弹窗</h1>
    <Modal v-model="visible"></Modal>
  </div>
</template>
<script setup lang="ts">
  import Modal from './modal-setup.vue'
  defineProps<{ msg: string }>()
  const visible = ref(false)
  const showModal = () => {
    visible.value = true
  }
</script>

<style scoped>
.hello {
  position: relative;
  width: 100px;
}
</style>

子组件

<template>
  <teleport to="#app">
    <div class="modal" @click="hideModal" v-show="visible">
      modal
    </div>
  </teleport>
</template>
<script setup lang="ts">
  const props = defineProps<{ modelValue: Boolean }>()
  const emit = defineEmits(['update:modelValue'])
  const visible = computed({
    get: () => props.modelValue,
    set: val => {
      emit('update:modelValue', val)
    }
  })
  const hideModal = () => {
    visible.value = false
  }
</script>
<style scoped>
.modal {
  position: absolute;
  top: 0;
  right: 0;
  background: #999;
  width: 300px;
  height: 100vh;
}
</style>
echarts 使用
<template>
  <div v-for="(card, index) in cardList" :key="`${card.id}-${index}`">
    <div class="card">
      <!-- 当你放置echart的元素是动态渲染时, 需要动态挂载元素-->
      <template v-if="card.type === 1">
        <div :ref="(el) => setEchartRef(el, index)" class="chart"></div>
      </template>
      <div v-else>empty-box</div>
    </div>
  </div>
</template>

<script setup lang="ts">
  import * as echarts from 'echarts/core';
  import { PieChart } from 'echarts/charts';
  import { CanvasRenderer } from 'echarts/renderers';
  import { GridComponent, TooltipComponent } from 'echarts/components';
  echarts.use([GridComponent, PieChart, CanvasRenderer, TooltipComponent]);
  const cardList = ref([]);
  const echartsRef = ref<HTMLElement[]>([]);

  function setEchartRef = (el: HTMLElement, index: number) => {
    echartsRef.value[index] = el;
  }
  function drawEchart(index) {
    cardList.value[index].echart = echarts.init(echartsRef?.value?.[index] as unknown as HTMLElement);
    cardList.value[index].echart.setOption({
      //  ...
    })
  }
  function setEchartData() {
    cardList.value[index].type = 1;
    await nextTick();
    drawEchart(index);
  }
</script>

关于构建部署踩的坑

  1. 混用 requireimport

如果项目中存在混用 commonJS 和 ES6 模块的情况,需要使用 @originjs/vite-plugin-commonjs 这个插件的 transformMixedEsModules 配置进行 hotfix。不然会报错Uncaught ReferenceError: require is not defined不过,尽量不要混用,因为尤大大说了这么干不好....Vite will likely never support such dependencies.

import { defineConfig } from 'vite'
import { viteCommonjs } from '@originjs/vite-plugin-commonjs';
export default defineConfig({
  // ...
  plugins: [
    viteCommonjs({
      transformMixedEsModules: true,
    }),
  ]
})

个人理解,这个配置类似于 babelsourceType配置项。因为之前在babel也踩过类似的坑,这里贴出对应 issues4039。其实简单概括就是出现了import和module.exports的混用

所以,原来项目中用 h 函数渲染图片的写法也要改为es引入,如下:

import exampleImg from './assets/example.png'
import { h } from 'vue';
function renderModal() {
   Modal.confirm({
    title: '操作确认',
    icon: null,
    content: () =>
      h('div', { style: 'text-align: center;padding-bottom: 32px;' }, [
        // 原来vue2的写法 h('img', {attrs: {src: require('./assets/example.png')}})
        h('img', { src: exampleImg })]),
  });
}
  1. 关于浏览器兼容问题

vitebuild.target 配置项可以配置希望兼容的浏览器版本或者 ES 版本,cssTarget可以对 CSS 的压缩设置一个target,该配置应针对非主流浏览器使用。例如,安卓微信中的 webview,并不支持 CSS 中的十六进制颜色符号, 此时将 build.cssTarget 设置为 chrome61,可以防止 vitergba() 颜色转化为 #RGBA 十六进制符号的形式。

我在 vue3 开发中的踩的坑

除此之外, 还可以使用插件 @vitejs/plugin-legacy 进行更多的浏览器兼容问题处理。例如,在内核 chrome 69 版本的360浏览器中,遇到过Uncaught ReferenceError: globalThis is not defined这样的报错。网上搜到可以通过解决浏览器端 globalThis is not defined 报错简单快速的 hotfix 可以解决这个问题,但是我始终觉得不够优雅。

后来翻了下文档,实际可以通过 @vitejs/plugin-legacymodernPolyfills配置去解决这个问题,解决配置如下代码。同理,你也可以 Polyfills 你需要的es

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  server: {
    port: 8080
  },
  build: {
    target: 'es2015', // js兼容处理
    cssTarget: 'chrome49', // css兼容处理
  }
  plugins: [
    vue(),
    legacy({
      targets: ['chrome 49'],
      modernPolyfills: ['es.global-this'], // 解决浏览器端 globalThis is not defined 报错
    }),
  ]
})

说完这么多坑,最后附上一张比较有意思的图2021年前端框架开发:满意度-感兴趣程度-使用度-熟知度

我在 vue3 开发中的踩的坑

踩了这么多坑,你可能会问,后悔在新项目里面用 vue3了吗?我的答案是没有。对于一个不太重的新项目,你又想尝试卷卷 vue3,我个人觉得或许是个不错的选择。

vue中文官网

Vite中文网

Vue2升级到Vue3到底是不是一个正确的选择?

plugin-legacy

How to access dynamic ref-tagged html elements in Vue.js 3?