vue3+vite3使用总结

899 阅读7分钟

1. 项目准备

1.1 文档vue3vitevant

vite3.0+vue3.2+vant3.5

1.2 项目为移动端项目适配方案使用vw+rem

参考这篇文章, 屏幕总宽度为100vw,如果设计图宽度为750px,那么html的font-size设置为1px为0.13333333333333333vw,100px设置为13.333333333333333vw,然后css选择器中就可以写rem,比方说设计图为50px,代码中直接写0.5rem就可以了,同时也可以通过媒体查询设置最大rem值,实践发现rem设置为0.13333333333333333vw在钉钉浏览器中样式会混乱,建议设置为13.333333333333333vw网易新闻也是用的这种适配方式

@media screen and (max-width: 960px) {
  html {
    font-size: 13.333333333333333vw !important;
  }
}

@media screen and (min-width: 961px) {
  html {
    font-size: 50px !important;
  }
}

1.3 代码格式化使用eslint+prettier+stylelint

vscode安装Vue Language Features(volar)插件、安装eslint插件、安装stylelint插件、EditorConfig for VS Code插件,项目安装依赖包

"@vue/eslint-config-prettier": "^7.0.0",
"eslint": "^8.21.0",
"eslint-plugin-vue": "^9.3.0",
"prettier": "^2.5.1",
"stylelint": "^14.9.1",
"stylelint-config-standard": "^26.0.0",
"stylelint-plugin-stylus": "^0.16.1",

.eslintrc文件

{
  "root": true,
  "env": {
    "browser": true,
    "es2021": true,
    "node": true
  },
  "parserOptions": {
    "sourceType": "module"
  },
  "parser": "vue-eslint-parser",
  "extends": [
    "eslint:recommended",
    "plugin:prettier/recommended",
    "plugin:vue/vue3-essential"
  ],
  // 全局变量
  "globals": {
    "window": true
  },
  "rules": {
    "vue/max-attributes-per-line": 0
  }
}

.stylelintrc.json文件

{
  "extends": [
    "stylelint-config-standard",
    "stylelint-plugin-stylus/recommended"
  ],
  "rules": {
    "number-max-precision": 17,
    "selector-pseudo-class-no-unknown": [
      true,
      {
        "ignorePseudoClasses": [
          "deep"
        ]
      }
    ],
    "color-function-notation": "legacy"
  }
}

.editorconfig文件

root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
indent_size = 4
trim_trailing_whitespace = false

vscode settings.json增加

 "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact",
    "vue"
  ],
  "editor.codeActionsOnSave": { // 保存时自动修复
    "source.fixAll.eslint": true,
    "source.fixAll.stylelint": true
  },
  "eslint.alwaysShowStatus": true, // 展示右下角eslint图标
  "eslint.enable": true, // 为工作区文件夹启用/禁用 ESLint默认启用
  "stylelint.enable": true,
  // "stylelint.customSyntax": "stylelint-plugin-stylus/custom-syntax",
  "stylelint.validate": [
    "css",
    "less",
    "scss",
    "vue",
    "sass",
    "stylus"
  ],
  "[less]": {
    "editor.defaultFormatter": "stylelint.vscode-stylelint"
  },
  // "editor.formatOnSave": true, // 保存自动格式化,需要关闭否则可能与eslint缩进冲突,会在eslint格式化后再格式化
  "editor.formatOnType": true

2. 使用vue3的一些总结

2.1 vue3与vue2的一些对比

  1. vue3对tree shaking支持更友好,各种方法通过import按需导入
  2. vue2的new Vue()变成了import { createApp } from 'vue' createApp()
  3. vue2的Vue.prototype变成了vue3的app.config.globalProperties
  4. Vue2中的beforeDestroy、destroyed变成了在Vue3的 beforeUnmount、unmounted
  5. vue2中mixins代码在vue3中改成hooks写法
  6. vue3深度选择器使用:deep()伪类
  7. vue3的template可以有多个根元素

2.2 自定义hook注意事项:

  1. 命名推荐使用use开头;
  2. 只能在最顶层使用,不能在if,for等代码块中使用
  3. vue中所有Hook(包括自定义hook)只能在setup中使用;
  4. hook应该包含组件状态,否则他应该是util,不能作为hook。

2.3 "vue-router": "^4.0.0"报错exports is not defined

再安装一次npm install vue-router@next -D 参考

2.4 vscode提示Component name "index" should always be multi-word.

vue3推荐组件名name使用多个单词组合

2.5 <script setup> 中的顶层的导入和变量声明可在同一组件的模板中直接使用

script setup中import组件后可以直接在template中使用,不需要使用components注册

2.6 reactive解构出的值类型变量会失去响应式

2.7 pinia是Vuex的代替者

pinia中没有mutations和modules,pinia不用以嵌套(通过modules引入)的方式引入模块,因为它的每个store便是一个模块,与 Vuex 相比,Pinia 提供了 Composition-API 风格的 API,在与 TypeScript一起使用时具有可靠的类型推断支持,一文解析 Pinia 和 Vuex

2.8 vue3的组合式api没有导出becreate和created钩子?

组合式 API 中的 setup() 钩子会在所有选项式 API 钩子之前调用,包括beforeCreate();在 beforeCreate 和 created 生命周期钩子中编写的代码可以直接在 setup 函数中编写。

2.9 setup中怎么像vue2中访问this一样访问实例对象

在setup中可以通过 getCurrentInstance函数返回值解构的proxy作为当前vue组件的实例对象

import {getCurrentInstance} from 'vue'
const { proxy } = getCurrentInstance();

2.10 怎么使得解构出的props的属性不失去响应性

解构了 props 对象,解构出的变量将会丢失响应性。因此推荐通过 props.xxx 的形式来使用其中的 props,如果你确实需要解构 props 对象,可以使用 toRefs()处理对象然后再解构 或者使用 toRef()对对象的单个属性进行处理

// 1. 将 `props` 转为一个其中全是 ref 的对象,然后解构
const { title } = toRefs(props)
// `title` 是一个追踪着 `props.title` 的 ref
console.log(title.value)

// setup函数暴漏给template
setup(props) {
    const data= reactive({
        a: {
            b:'',
        },
    })
    return {
       ...toRefs(data),
    };
}

// 2. 如果不想要.value可以使用reactive包裹toRefs
const { title } = reactive({
    ...toRefs(props)
})
console.log(title)

// 3. 或者,将 `props` 的单个属性转为一个 ref
const title = toRef(props, 'title')

2.11 使用script setup语法时怎么定义组件的name

<script setup>
defineOptions({
  name: 'myApp'
})
</script>
<script setup lang="ts" name="myApp">
  • 或者另写一个script标签
<script>
export default {
    name: "myApp"
}
</script>

2.12 选项式和组合式生命周期

选项式api组合式api
beforeCreate
created
beforeMountoonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted

2.13 reactive与ref的使用

reactive解构后的基础类型变量要暴露给template的话要使用toRef包一下,否则会失去响应式

const loadingState = reactive({
    loading: true
})
cosnt loading = toRef(loadingState, "loading")

ref暴露给template会自动解构,template中可以直接使用;ref中的基本类型如果在setup中解构再暴露给template也会失去响应式,应该直接把ref变量暴漏给template;computed产生的数据如果要在setup中访问要使用.value, 在template中使用ref的值不用通过value获取,在js中使用ref的值必须通过value获取

2.14 父组件调用子组件方法

子组件使用setup(){}语法,通过return返回可以让父组件调用;子组件使用<script setup>语法,不会自动暴漏,需要结合来defineExpose暴漏给父组件;

defineExpose({
  a,
  b
})

父组件使用

<child ref="childRef"/>
const childref = ref()
childref.value.a来使用

2.15 父组件和子组件通过v-model通信

// 父组件
<input v-model="searchText" />
上面的代码其实等价于下面这段
<input :value="searchText" @input="searchText = $event.target.value" />
而当使用在一个组件上时,`v-model` 会被展开为如下的形式:
<CustomInput :modelValue="searchText" @update:modelValue="newValue => searchText = newValue" />

// 子组件
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>


2.16 vue2的.sync在vue3中怎么写?

vue2的v-bind 的 .sync 修饰符和组件的 model 选项已移除,可在 v-model 上加一个参数代替,官网说明

// 父组件
<UserName v-model:first-name="first" />


// 子组件
<!-- UserName.vue -->
<script setup>
defineProps({
  firstName: String
})

defineEmits(['update:firstName'])
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
</template>

2.17 props传参

setup()语法,通过和vue2一样定义props会直接暴漏给template;

export default {
    props: {
      test: {
        type: Array,
        default: () => [],
      },
    },
    setup(props, context) {
        // 透传 Attributes(非响应式的对象,等价于 $attrs)
        console.log(context.attrs)

        // 插槽(非响应式的对象,等价于 $slots)
        console.log(context.slots)

        // 触发事件(函数,等价于 $emit)
        console.log(context.emit)

        // 暴露公共属性(函数)
        console.log(context.expose)
   }
}

<script setup>语法,通过defineProps定义的props会暴漏给template

<script setup>
    defineProps({
      test: {
        type: Array,
        default: () => [],
      },
    });
</script>

2.18 vue-router4的使用

import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
console.log("route:", route);
console.log("router:", router);
const pushWithQuery = (query) => {
  router.push({
    name: "search",
    query,
  });
};

路由配置文件

import { createRouter, createWebHistory } from "vue-router";

const routes = [
  {
    name: "notFound", // 配置404页面
    path: "/:path(.*)+",
    redirect: {
      name: "404",
    },
  },
  {
    name: "404",
    path: "/404",
    component: () => import("./views/notFound/index.vue"), // 路由懒加载
  }
];

const router = createRouter({
  routes,
  history: createWebHistory("/base/"), // 设置base为基础路径
});

2.19 options api中怎么访问setup函数中的变量

import { useCounterStore } from '../stores/counterStore'

export default {
  setup() {
    const counterStore = useCounterStore()

    return { counterStore } // 先return
  },
  computed: {
    tripleCounter() {
      return counterStore.counter * 3 // 然后直接访问
    },
  },
}

2.20 [报错] [Vue warn]: Avoid app logic that relies on enumerating keys on a component instance. The keys will be empty in production mode to avoid performance overhead.

blog.csdn.net/weixin_4733…

2.21[报错][Vue warn]: onMounted is called when there is no active component instance to be associated with. Lifecycle injection APIs can only be used during execution of setup().

blog.csdn.net/IICOOM/arti…

使用vite5.2.8的一些总结

批量引入图片

import.meta.glob: 该函数接收一个基于项目根目录的 glob 模式字符串,并返回一个包含所有匹配 URL 和对应加载函数的对象。只有在实际调用返回的函数时,才会执行实际的动态导入。

import.meta.globEager 已经弃用,请使用 import.meta.glob('*', { eager: true }) 来代替,它会立即执行匹配的导入,并返回一个包含所有匹配 URL 和模块默认导出的对象。

批量获取某个文件夹下面的所有图片

const getDirImgs = (images, getUrlFn) => {
    return Object.keys(images).map(imgUrl => {
        const arr = imgUrl.split('/');
        const imgName = arr[arr.length - 1]
        return getUrlFn(imgName);
    });
}
// 注意!!!相对路径必须写到new URL里面,不同的文件夹要用不同的getImgUrl函数,因为里面的相对路径要重写
const getImgUrl = name => new URL(`../imgs/${name}`, import.meta.url).href;
export const imgList = getDirImgs(import.meta.glob(`../imgs/*.png`), getImgUrl);

vue3+vite assets动态引入图片的几种方式,解决打包后图片路径错误不显示的问题

支持https,ssl

安装"@vitejs/plugin-basic-ssl": "^1.1.0"

vite.config.js中增加

import basicSsl from '@vitejs/plugin-basic-ssl';

plugins: [
    basicSsl()
]

3. 使用vite3.0的一些总结

3.1 vite怎么支持vue?

"@vitejs/plugin-vue": "^3.0.0",
"@vitejs/plugin-vue-jsx": "^2.0.0",

# vite.config.js
plugins: [
  vue(),
  vueJsx(),
]

3.2 vite设置文件系统路径别名

# vite.config.js文件
resolve: {
  // 设置别名
  alias: {
    '~': path.resolve(__dirname, './'),
    '@': path.resolve(__dirname, './src')
  }
}
# vscode要点击路径别名实现快捷跳转需要在Vite根路径下增加jsconfig.json文件
# 如果用的是ts就在tsconfig.json中加,文件内容如下
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "~/*": ["./*"]
    }
  }
}

3.3 静态资源(图片、文件)存放目录

可以放在public或者src/assets/目录

  • public目录中的资源在开发时能直接通过 / 根路径访问到,并且打包时会被完整复制到目标目录的根目录下,其中的文件不会被hash,举个例子,public/icon.png 应该在源码中被引用为 /icon.png
  • src/assets/目录中的文件会被编译,体积小的图片会变成base64,打包后的文件名会增加hash值

3.4 vue文件setup()函数中debugger定位不准确?

这个没找到解决办法

3.5 vite开发服务怎么通过局域网ip访问?

  • server.host的默认值为'127.0.0.1',启动开发服务后不会暴漏给局域网
    image.png
  • 设置server.host的值为0.0.0.0 或者 true 将监听所有地址,包括局域网和公网地址。
    image.png

3.6 vite开发服务局域网在手机访问页面空白报错globalThis is not defined

html中添加如下代码解决

!(function (t) {
    function e() {
      var e = this || self;
      (e.globalThis = e), delete t.prototype._T_;
    }
    "object" != typeof globalThis &&
      (this
        ? e()
        : (t.defineProperty(t.prototype, "_T_", {
            configurable: !0,
            get: e,
          }),
          _T_));
})(Object);

3.7 vite设置开发服务请求代理

server: {
  port: 8080, // 开发服务端口
  open: true, // 自动打开浏览器
  proxy: {
    "/test": {
      target: "https://www.mysite.com",
      changeOrigin: true,
      // configure: (proxy, options) => {
      //   console.log("proxy, options", proxy);
      //   // proxy 是 'http-proxy' 的实例
      // },
      // cookieDomainRewrite: { // 重写cookie的域名
      //   "mysite.com": "http://192.168.66.66:8080/",
      // },
    },
  },
},

3.8 怎么访问环境变量?

参考1参考2

  • 访问mode
// package.json
"dev": "vite --mode development",
"build": "vite build --mode production",
"build:staging": "vite build --mode staging",

//普通文件访问
console.log("import:", import.meta.env.MODE);

// vite.config.js文件访问
export default defineConfig(({ mode }) => {
      console.log("mode:", mode);
    }
});
  • 访问.env文件中的变量 定义.env.development文件对应mode为development
VITE_BASE_PATH = '/dev/'

定义.env.staging文件对应mode为staging

VITE_BASE_PATH = '/staging/'

定义.env.production文件对应mode为production

VITE_BASE_PATH = '/pro/'

普通文件还是通过**import.meta.env**对象访问,注意import.meta.env.xxx不能以字符串的形式出现在文件中否则打包会报错

vite.config.js中访问需要进行如下配置

import { defineConfig, loadEnv } from "vite";
export default defineConfig(({ mode }) => {
  // loadEnv用来加载.env.[mode]文件中的变量
  // loadEnv有三个参数,mode、路径、要加载变量的前缀(默认为VITE_)
  const env = loadEnv(mode, process.cwd());
  console.log("VITE_BASE_PATH:", env.VITE_BASE_PATH);
  console.log("mode:", mode);
  return {
    base: "./"
  }
});

分别执行命令看输出
image.png

image.png

image.png

3.9 vite打包怎么去除log和debug

# 安装terser
npm i terser
# vite.config.js中添加
build: {
  assetsDir: "static", // 定义静态资源文件夹名字,默认为assets
  minify: "terser", // 压缩使用terser
  terserOptions: {
    compress: {
      drop_console: mode === "production", // 默认为false
      drop_debugger: mode !== "development", // 默认为true
    },
  },
}

3.10 vite资源分类打包到不同文件夹

rollupOptions: {
   output: {
      //静态资源分类打包
      chunkFileNames: "static/js/[name]-[hash].js",
      entryFileNames: "static/js/[name]-[hash].js",
      assetFileNames: "static/[ext]/[name]-[hash].[ext]",
      // manualChunks(id) {
      //   //静态资源分拆打包
      //   if (id.includes("node_modules")) {
      //     return id
      //       .toString()
      //       .split("node_modules/")[1]
      //       .split("/")[0]
      //       .toString();
      //   }
      // },
    },
}

image.png

3.11 vite怎么压缩静态资源?

vite-plugin-compression

import viteCompression from "vite-plugin-compression"; 
plugins: [
  viteCompression({
    verbose: true, // 是否在控制台输出压缩结果
    disable: false, // 是否禁用
    threshold: 10240, // 体积大于 threshold 才会被压缩,单位 b
    algorithm: "gzip", // 压缩算法,可选 [ 'gzip' , 'brotliCompress' ,'deflate' , 'deflateRaw']
    ext: ".gz", // 生成的压缩包后缀
  }),
],

3.12 为打包后的文件提供传统浏览器兼容性支持

@vitejs/plugin-legacy

import legacy from "@vitejs/plugin-legacy";
 plugins: [
      legacy({
        // 为打包后的文件提供传统浏览器兼容性支持
        targets: ["> 1%, last 1 version, ie >= 11"],// 需要兼容的浏览器列表
        additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
      }),
],

打包会生成名字含有legacy的文件
image.png
html引用有module和nomodule两种 image.png

3.13 异步import怎么自定义文件名?

使用vite-plugin-webpackchunkname插件

{
    name: "404",
    path: "/404",
    component: () =>
      import(/* webpackChunkName: "404" */ "./views/notFound/index.vue"),
  }

image.png

3.14 资源部署在非根目录访问页面空白?

排查发现是html访问不到js资源,vite.config.js中base属性默认值为/,base是 开发或生产环境服务的公共基础路径。合法的值包括以下几种:

  • 绝对 URL 路径名,例如 /foo/
  • 完整的 URL,例如 https://foo.com/
  • 空字符串或 ./(用于开发环境)
  1. 设置为/path/,html中引用资源路径增加/path/前缀 image.png
  2. 设置为./,html中引用资源使用相对路径 image.png

3.15 vite怎么支持多页应用?

使用vite-plugin-html插件

3.16 每个组件都需要从vue中引入react、ref等方法,可不可以自动按需导入?

使用unplugin-auto-import插件

3.17 vite热更新失效怎么解决

如果每次重新启动服务才能更新页面,就是全量热更新的问题,可能涉及到文件地址引用问题,如:文件大小写问题、同一个文件夹下存在大驼峰小驼峰的命名方式的2种文件等`参考地址

3.18 vite有没有批量引入功能?类似webpack的require.context

const req: Record<string, any> = import.meta.glob("./common/*.*", { eager: true })

const imagesMap = Object.keys(req).reduce((prev, cur) => {
  const key = (cur.match(/(?<=\/)[^/]*(?=\.)/g) ?? [""])[0]
  prev[key] = req[cur].default
  return prev
}, {} as Record<string, string>)

console.log(JSON.stringify(imagesMap))
// {"asset":"/front/src/static/images/common/asset.png"}
export default imagesMap

3.19 vite怎么定义全局变量,类似webpack的defineplugin

define: {
  ROUTES: new TransformPages().routes, // 注入路由表
  TEST: JSON.stringify("666"),
},

这样就可以在代码template或者script中访问ROUTES变量了,与环境变量import.meta.env.VITE_XXX的区别就是,环境变量不能在template中访问,而define定义的变量可以在template中访问

3.20 打包的时候有些图片自动变成base64有的没变怎么回事?

build.assetsInlineLimit

  • 类型:  number
  • 默认:  4096 (4kb) 小于此阈值的导入或引用资源将内联为 base64 编码,以避免额外的 http 请求。设置为 0 可以完全禁用此项。

3.21 如何从本地批量引入图片

const loadImages = () => {
  const images = import.meta.glob('你的图片路径/*.png');
  // 遍历匹配到的图片,批量导入
  iconList.value = Object.keys(images).map(key => new URL(key, import.meta.url).href);
};

4. vant3.5使用的一些总结

4.1. 实现vant的按需加载

1.安装 npm i unplugin-vue-components -D
2.配置vite.config.js

import vue from '@vitejs/plugin-vue'; 
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers'; 
export default 
{ 
    plugins: [ 
        vue(), 
        Components({ 
            resolvers: [VantResolver()], 
        }),
    ], 
};

可以直接在模板中使用 Vant 组件了,unplugin-vue-components 会解析模板并自动注册对应的组件。

<template>
  <van-button type="primary" />
</template>

4.2 vant组件怎么自定义样式

参考 ConfigProvider 组件

<van-config-provider :theme-vars="themeVars">
    <van-tabs>
      <van-tab :title="item.name" :key="item.name" v-for="item in list" ></van-tab>
    </van-tabs>
</van-config-provider>

<script setup>
const list = [
    {
      name: "tab1",
    },
    {
      name: "tab2",
    },
    {
      name: "tab3",
    },
]
const themeVars = {
    tabFontSize: "0.4rem",
    tabsLineHeight: "0.7rem", // 改成0.69可以
    tabLineHeight: "0.7rem",
    tabsBottomBarColor: "#fff",
    tabsBottomBarHeight: ".05rem",
    tabsBottomBarWidth: "1.2rem",
    tabActiveTextColor: "#fff",
    tabTextColor: "#FAC589",
}
</script>

4.3 动态更新toast

const toast = Toast.loading({
  duration: 0,
  forbidClick: true,
  message: '倒计时 3 秒',
});

let second = 3;
const timer = setInterval(() => {
  second--;
  if (second) {
    toast.message = `倒计时 ${second} 秒`;
  } else {
    clearInterval(timer);
    Toast.clear();
  }
}, 1000);