1.Vue3篇
1.17 父组件传递给子组件的属性虽然不能修改,但是传递的属性值是引用值的话,可以修改引用值的某个属性
1.16 vue3组件属性值为数组时,赋初始值的方式:
ts
withDefaults(defineProps<{ data: Item[] }>(), {
data: () => {
return [] as Item[];
},
});
1.15 没拿到渲染数据前,模板显示{{}}的处理方法
html
[v-cloak] {
display: none;
}
<div v-cloak>
{{ message }}
</div>
1.14 defineProps,defineEmit,defineExpose
这三个api,使用时都不用导入到vue文件
1.13 vue报这种错误 Failed to execute 'insertBefore' on 'Node' ,解决方法是给v-if 组件加上key
1.12 vue有时候报错信息是 xx资源加载不到,查看的时候,发现存在,实际这种错误是别的代码片段引起的,而非提示的代码文件,另外vue模板报渲染错误,一般大概率是取了属性不存在的值
1.11 Vue3 plugin写法, 要定义一个install方法,另外install方法有一个传入参数是应用全局对象。
loadSDK.ts
js
import { App, Plugin } from 'vue';
import { getEnv, WECHAT_ENV } from '@/utils';
const qyWxSDKUrl = 'https://res.wx.qq.com/wwopen/js/jsapi/jweixin-1.0.0.js';
const wxSDK = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js';
export const loadSDK: Plugin = {
install(app: App, options: {}) {
getEnv() === WECHAT_ENV.qyWechat ? loadJS(qyWxSDKUrl) : loadJS(wxSDK);
},
};
function loadJS(jsUrl: string) {
const script = document.createElement('script');
// @ts-ignore
script.crossorigin = 'anonymous';
script.src = jsUrl;
script.onerror = () => {
console.log('qy-wx-sdk:loadError');
};
script.onload = async () => {
console.log(import.meta.en);
if (import.meta.env.DEV)
await apis.mockLogin({ userid: 'liudongjie' });
}
};
document.body.appendChild(script);
}
ts
import { createApp } from 'vue';
import App from '@/App.vue';
import { loadSDK } from './loadSDK';
const app = createApp(App);
app.use(loadSDK);
1.10 watch和watchEffect应用场景区别,watch监听单个变量, watchEffect监听多个变量,默认会执行一次。
ts
// watch监听对象某个属性值的写法
watch(()=>props.A,(newVal,oldVal)=>{ console.log(newVal,oldVal})
// watchEffect监听多个值的写法
watchEffect(()=>{
console.log(props.A,props.B);
})
1.9 Vue3中如何优雅地在控制台打印Proxy对象,在Chrome调试控制台,设置中--勾选启用自定义格式设置工具
1.8 Vue 设置style,template,script标签顶层缩进两个空格的设置方法,在eslint的配置文件中定义规则
json
{
"prettier/prettier": ["error", { vueIndentScriptAndStyle: true }],
}
1.7 Vue3全局变量或方法类型定义
ts
// symbols.ts
import { InjectionKey } from 'vue';
interface Product {
name: string;
price: number;
}
export const ProductKey: InjectionKey<Product> = Symbol('Product');
定义注入常量
ts
import { provide } from 'vue';
import { ProductKey } from '@/symbols';
provide(ProductKey, {
name: 'Amazing T-Shirt',
price: 100,
});
获取注入常量
ts
import { inject } from 'vue';
import { ProductKey } from '@/symbols';
const product = inject(ProductKey); // typed as Product or undefined
console.log(product);
1.5 vue3中,不要在reactive中将属性值定义成对象,否则在视图部分,引用的时候会写得很长。
1.4 在template部分写style样式,保存代码时,代码自动格式化在style引号中结尾加分号引起eslint告警的解决方案,用模板字符串。
1.3 子组件接收父组件的属性,3.2.25以后,属性可以解构,不会丧失响应式,t另外oRefs解构的变量,不能赋默认值。
1.2 @click事件回调,如果函数存在参数的话,书写时要用箭头函数包起来,否则第一个参数是dom事件参数event
1.1 import导入内容必须放在const变量定义之前,否则会提示找不到模块名或对应的模块声明
2.Vant篇
2.7 在Dialog.Confirm组件之前使用Toast组件,会造成Dialog.Confirm被遮住, Dialog.Confirm组件上的按钮无法点击。
2.6 Vant Picker选项是对象数组的处理方法,值要用value-key定义,这样就能获取到选择项完整的对象属性,而不仅是选择的展示名称和value.
html
<van-picker
title="请选择所在地区"
show-toolbar
:columns="columns"
value-key="name"
@confirm="onConfirm"
@cancel="show = false"
/>
2.5 Vant 父组件修改子组件样式的方法
加上scoped声明之后,父组件的样式就不会在子组件中生效,用:deep(需要修改stylelint的校验规则,stylelint才对:deep这种语法不报错),否则深层的样式无法修改。要给Vant组件加一个自定义样式名,否则会引起全局命名污染。
css
<style lang="less" scoped>
// 新增一个的样式名,如my-popup
:deep(.my-popup){
// 定义样式属性,覆盖van-popup样式值
}
</style>
2.4 Vant iPhone手机底部和顶部安全设置
html
<!-- 开启顶部安全区适配 -->
<van-nav-bar safe-area-inset-top />
<!-- 开启底部安全区适配 -->
<van-number-keyboard safe-area-inset-bottom />
2.3 van-field表单项类型定义
ts
// 表单项ref的类型定义是
import type {FieldInstance} from 'vant';
// 校验器书写规则
:rules=[{validator:(value, rule) => boolean | string | Promise}],
2.2 Vant的PullRefresh组件是顶部下拉刷新,没有底部上拉刷新,需要自己封装一个
2.1 <van-dialog />
有个大bug,弹窗关不了。
3.Vue-Router篇
3.7 router的跳转的路由必须有name属性,否则跳转会报错, 另外前进后退的api,与react-router名称不一样,前进是 router.forward(),后退是router.back()
3.6 页面动态路由缓存设置和keep-alive功能
html
<template>
<RouterView v-if="initReady" v-slot="{ Component }">
<template v-if="Component">
<!-- 通过name匹配keepAlive -->
<KeepAlive :include="keepAliveComponents">
<component :is="Component" :key="route.name" />
</KeepAlive>
</template>
</RouterView>
</template>
<script setup>
import { computed } from 'vue';
import { useKeepAliveStore } from '@shared/store';
const keepAliveStore = useKeepAliveStore();
const keepAliveComponents = computed(() => keepAliveStore.list);
</script>
js
import { App } from 'vue';
import type { Router } from 'vue-router';
import { createPinia } from 'pinia';
import { useKeepAliveStore } from '@shared/store';
const redirect = location.href;
export function createTaskAuthRouterGuard(router: Router, app: App) {
app.use(createPinia());
const keepAliveStore = useKeepAliveStore();
router.beforeEach((to, from, next) => {
// 进入任务下发页--缓存
if (to.name === 'task/taskPublish') {
keepAliveStore.add('task/taskPublish');
}
// 离开任务下发页,前往的不是任务确认页面,清空缓存
if (from.name === 'task/taskPublish' && to.name !== 'task/taskStaffList') {
keepAliveStore.remove('task/taskPublish');
}
// 未进行任务权限认证
return next();
});
}
js
import { defineStore } from 'pinia';
interface KeepAliveState {
/** 需要缓存的路由组件名称列表 */
list: string[];
}
export const useKeepAliveStore = defineStore({
id: 'keep-alive',
state: (): KeepAliveState => ({
list: [],
}),
actions: {
add(name: string | string[]) {
if (typeof name === 'string') {
!this.list.includes(name) && this.list.push(name);
} else {
name.map((v) => {
v && !this.list.includes(v) && this.list.push(v);
});
}
},
remove(name: string | string[]) {
if (typeof name === 'string') {
this.list = this.list.filter((v) => {
return v !== name;
});
} else {
this.list = this.list.filter((v) => {
return !name.includes(v);
});
}
},
clear() {
this.list = [];
},
},
});
3.5 Vue-Router4 + History模式 Nginx配置
root和alias的区别,参见此文
bash
location /admin {
index index.html;
alias D:/admin/dist;
try_files $uri $uri/ /index.html;
}
3.4 Vue-Router+History 模式 Apache 服务器配置
bash
RewriteEngine On
# 如果请求的文件或目录存在,则不重写,直接访问
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]
# 开发环境下文件找不到的跳转规则
RewriteCond %{REQUEST_URI} ^/h5_dev/
RewriteRule ^ /h5_dev/index.html [L]
# 测试环境下文件找不到的跳转规则
RewriteCond %{REQUEST_URI} ^/h5_test4/
RewriteRule ^ /h5_test4/index.html [L]
# 生产环境下文件找不到的跳转规则
# 如果请求基础路径为h5的资源不存在, 跳转到/h5/index.html
RewriteCond %{REQUEST_URI} ^/h5/
RewriteRule ^ /h5/index.html [L]
# 如果输入的网址是服务器上的一个目录,此目录下没有DirectoryIndex指令中配置的索引文件
# 那么服务器会返回一个格式化后的目录列表,并列出该目录下的所有文件
Options -Indexes
3.3 router.beforeEach中的next参数,如果没传递路由参数,就是执行跳转,如果传递了路由,仅是改变路由,但不跳转。
js
router.beforeEach((to, from, next) => {
// 仅仅改变跳转目的路由,但不跳转
next({
path: '/Login',
query: {
redirect: to.fullPath
}
});
// 这种情况下才会跳转
next();
})
3.2 在url地址栏隐藏传递参数,用params传参写法
js
router.push({
name:'taskUrge',
params:{
test:1,
}
})
3.1 路由报错,一般不是路由配置出了问题,是路由中配置的页面出了问题。
4. 使用Pinia时大概率会遇到的报错
vbnet
Uncaught Error: [🍍]: getActivePinia was called with no active Pinia. Did you forget to install pinia?
const pinia = createPinia()
app.use(pinia)
This will fail in production.
原因分析:调用store之前,Pinia还没有初始化。
场景1:在vue-router的beforeEach中调用store,不好的做法是在beforeEach外调用,好的做法是在beforeEach中调用
不好的做法
js
import { createRouter } from 'vue-router'
const router = createRouter({
// ...
})
// 依赖导入顺序,可能Pinia还没安装好
const store = useStore()
router.beforeEach((to, from, next) => {
// we wanted to use the store here
if (store.isLoggedIn) next()
else next('/login')
})
好的做法:
js
router.beforeEach((to) => {
// 路由开始导航时,Pinia肯定已经安装好了
const store = useStore()
if (to.meta.requiresAuth && !store.isLoggedIn) return '/login'
})
场景2:在store中调用另一个store仓库。
不好的做法: 在defineStore之前调用store, 被调用的store的实例可能还未生成。
js
import { defineStore } from 'pinia'
import { useUserStore } from '~/store/user'
const userStore = useUserStore()
export const useThemeStore = defineStore('theme', {
state: () => ({
name: 'theme-name'
}),
actions: {
setTheme() {
this.name += userStore.name
}
}
})
好的做法--等defineStore执行完之后,在actions中调用别的store
js
import { defineStore } from 'pinia'
import { useUserStore } from '~/store/user'
export const useThemeStore = defineStore('theme', {
state: () => ({
name: 'theme-name'
}),
actions: {
const userStore = useUserStore()
setTheme() {
this.name += userStore.name
}
}
})