1. 项目准备
1.1 文档vue3、vite、vant
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的一些对比
- vue3对tree shaking支持更友好,各种方法通过import按需导入
- vue2的
new Vue()变成了import { createApp } from 'vue' createApp() - vue2的
Vue.prototype变成了vue3的app.config.globalProperties - Vue2中的beforeDestroy、destroyed变成了在Vue3的 beforeUnmount、unmounted
- vue2中mixins代码在vue3中改成hooks写法
- vue3深度选择器使用:deep()伪类
- vue3的template可以有多个根元素
2.2 自定义hook注意事项:
- 命名推荐使用use开头;
- 只能在最顶层使用,不能在if,for等代码块中使用
- vue中所有Hook(包括自定义hook)只能在setup中使用;
- 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>
- 或者使用vite-plugin-vue-setup-extend 插件实现
<script setup lang="ts" name="myApp">
- 或者另写一个script标签
<script>
export default {
name: "myApp"
}
</script>
2.12 选项式和组合式生命周期
| 选项式api | 组合式api |
|---|---|
| beforeCreate | 无 |
| created | 无 |
| beforeMount | oonBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount | onBeforeUnmount |
| unmounted | onUnmounted |
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.
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().
使用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',启动开发服务后不会暴漏给局域网
- 设置server.host的值为
0.0.0.0或者true将监听所有地址,包括局域网和公网地址。
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 怎么访问环境变量?
- 访问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: "./"
}
});
分别执行命令看输出
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();
// }
// },
},
}
3.11 vite怎么压缩静态资源?
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 为打包后的文件提供传统浏览器兼容性支持
import legacy from "@vitejs/plugin-legacy";
plugins: [
legacy({
// 为打包后的文件提供传统浏览器兼容性支持
targets: ["> 1%, last 1 version, ie >= 11"],// 需要兼容的浏览器列表
additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
}),
],
打包会生成名字含有legacy的文件
html引用有module和nomodule两种
3.13 异步import怎么自定义文件名?
使用vite-plugin-webpackchunkname插件
{
name: "404",
path: "/404",
component: () =>
import(/* webpackChunkName: "404" */ "./views/notFound/index.vue"),
}
3.14 资源部署在非根目录访问页面空白?
排查发现是html访问不到js资源,vite.config.js中base属性默认值为/,base是
开发或生产环境服务的公共基础路径。合法的值包括以下几种:
- 绝对 URL 路径名,例如
/foo/ - 完整的 URL,例如
https://foo.com/ - 空字符串或
./(用于开发环境)
- 设置为
/path/,html中引用资源路径增加/path/前缀 - 设置为
./,html中引用资源使用相对路径
3.15 vite怎么支持多页应用?
使用vite-plugin-html插件
3.16 每个组件都需要从vue中引入react、ref等方法,可不可以自动按需导入?
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组件怎么自定义样式
<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);