Vue最佳实践和实用技巧
文章目录
- 内自定义限制 props: { size: { // 自定义验证函数 validator: (val) => { return ["small", "medium", "large"].includes(val); }, }}复制代码 这个验证函数接受一个 prop值,验证并返回 true 或 false透传属性 <!-- 此组件为二次封装的中间组件 --><template> <MyComponent v-bind="$attrs"/></template><script> export default { // $attrs 中的所有属性不自动继承到组件的根元素上 inheritAttrs: false, }</script>复制代码 $attrs包含所有透传过来的对象,除显式声明接受的props、emits、slots 如不希望透传下去某些属性,可使用useAttrs() const attrs = useAttrs();const filteredAttrs = computed(() => { return { ...attrs, style: undefined };});复制代码 $attrs还可与listeners`包含了父组件传递的事件(不包含.native修饰器),它可以通过v-on="$listeners"转发传入内部组件,进行对事件的监听处理 <MyComponent v-bind="$attrs" v-on="$listeners" />复制代码 注意: attrs属性上单个slot透传 <About> <template #about> <slot name="about" /> </template></About>复制代码 多个slot透传 <template #[slotName] v-for="(slot, slotName) in $slots" > <slot :name="slotName"/></template>复制代码 多个slot透传作用域插槽 <template #[slotName]="slotProps" v-for="(slot, slotName) in $slots" > <slot :name="slotName" v-bind="slotProps"/></template><!-- Vue2则需要将v-for里面循环的$slots改成$scopedSlots -->复制代码
- webpack统一导入相同路径下的多个组件的方法 const path = require("path");// 参数一:说明需要检索的目录,参数二:是否检索子目录,参数三::指定匹配文件名的正则表达式const files = require.context("./components", false, /\.vue$/);const modules = {};files.keys().forEach((key) => {const name = path.basename(key, ".vue");modules[name] = files(key).default || files(key);});复制代码 Vite 支持使用特殊的 import.meta.glob 函数从文件系统导入多个模块: const modules = import.meta.glob('./src/*.js');// vite 转译上面后生成的代码const modules = { './src/foo.js': () => import('./src/foo.js'), './src/bar.js': () => import('./src/bar.js')}复制代码
- 组件都有一个特殊的$slots对象,包含所有的插槽对象,结构如下: const $slots = {"default": [{...}],"slotA": [{...}],"slotB": [{...}]}复制代码 我们可以使用v-if有条件的渲染slot更加合理,并且我们封装通用组件的时候最好预留个slot更好扩展 <template> <div> <div v-if="$slots.default"> <slot /> </div> <div v-if="$slots.slotA"> <slot name="slotA"/> </div> </div></template>复制代码
- window.addEventListener('mousedown', e => { // 获取被点击的元素 const clickedEl = e.target; // `targetEl` 为检测的元素 if (targetEl.contains(clickedEl)) { // 在"targetEl"内部点击 } else { // 在"targetEl"之外点击 }});复制代码
- 动态组件:tab切换的时候可使用动态组件动态加载并缓存提供动效 <transition> <keep-alive> <!-- :is值必须是全局或者局部注册过的组件 --> <component :is="currentTab"></component> </keep-alive></transition>复制代码 递归组件:模板里面自己自己,注意需要设置条件退出,不然会无限渲染,适合嵌套菜单,树形控件等 <div v-if="item.children"> {{ tree.label }} <!-- 递归调用自身 --> <tree v-for="(item, index) in tree.children" :tree="item" :key="index"></tree></div><script> export default { // 定义name,以使组件内部递归调用 name: 'tree', // 接收外部传入的值 props: { tree: { type:Array, default: () => [] } } }</script>复制代码
- 在下次 DOM 更新循环结束之后执行延迟回调 我们可以在修改数据后立即使用此方法获取最新的DOM mounted(){ this.$nextTick(() => { this.$refs.inputs.focus(); //通过 $refs 获取dom 并绑定 focus 方法 })}复制代码
- <div :class="$route.name === 'Home' ||$route.name === 'Gallery' ||$route.name === 'Profile'? 'classOnlyOnThesePages': ''"></div>复制代码 直接最佳写法如下: <div :class="{classOnlyOnThesePages: ['Home', 'Gallery', 'Profile'].includes($route.name),}"></div>复制代码
- 遇到全局可重用的工具方法,例如 class Utils {// 复制一段文字到剪切板copyToClipboard(text) {let copyText = document.createElement("input");document.body.appendChild(copyText);copyText.value = text;copyText.select();document.execCommand("copy");document.body.removeChild(copyText);}}export default new Utils();复制代码 我们可以抽离出来放在整个应用程序都能访问的地方Vue2: import Utils from "./utils/utils.js";// 设置全局方法Vue.prototype.$utils = Utils;复制代码 Vue3: import Utils from "./utils/utils.js"; const app = createApp(App);// 设置全局方法app.config.globalProperties.$utils = Utils; app.mount("#app");复制代码接下来任何地方都能愉快的访问啦this.$utils.copyToClipboard(text);// Vue3 setupconst { proxy } = getCurrentInstance();proxy.$utils.copyToClipboard(text);复制代码
- 使用 v-if 方法来控制 router-view 的显示隐藏 <template><div id="app"><router-view v-if="isActive" /></div></template><script>export default {name: "App", // provider给下层组件重刷的方法provide() {return {reload: this.reload,};},data: { isActive: true,},method: {reload() {this.isActive = false;this.$nextTick(() => {this.isActive = true;});},},};</script>复制代码 需要的页面可注入该方法使用 <script>export default {inject: ["reload"],methods: {refresh() {this.reload();},},};</script>复制代码 或者直接使用v-if操作该组件 <template><div v-if="isShow"></div></template><script>export default {data() {return {isShow: true,};},method: {refresh() {this.isShow = false;this.$nextTick(() => {this.isShow = true;});},},};</script>复制代码 或者借助Vue的diff算法,我们给元素设置一个唯一的Key值然后去改变它 <template><div :key="keyValue"></div></template><script>export default {data() {return {keyValue: 0,};},method: {refresh() {this.keyValue++;},},};</script>复制代码
- Vue组件的API主要包含三部分:props、event、slot props 为组件接收的参数,最好用对象的写法,可更好设置类型默认值和自定义校验 event用于子组件向父组件传递消息 slot可以给组件动态插入一些内容或组件,是实现高阶组件的重要途径 组件封装最好还应遵循单向数据流,传递的props仅仅做展示,如需修改,则应该重新初始化一份全新的响应式数据并将props深拷贝后作为初始值
- 为 Vue 中的错误和警告提供自定义处理程序 // Vue 3const app = createApp(App);app.config.errorHandler = (err) => { console.error(err);};// Vue 2Vue.config.errorHandler = (err) => { console.error(err);};复制代码
- template 标签可以在模板内的任何地方使用,可以减少实际嵌套层级,更简化代码逻辑 <template> <div class="card"> <h3> {{ title }} </h3> <h4 v-if="isShow" class="card-desc"> {{ description }} </h4> <div v-if="isShow"> <slot /> </div> <Home v-if="isShow" /> </div></template>复制代码 上面代码v-if逻辑分组后: <template> <div class="card"> <h3> {{ title }} </h3> <template v-if="isShow"> <h4 class="card-desc"> {{ description }} </h4> <div> <slot /> </div> <Home /> </template> </div></template>复制代码
- 在模板中使用 v-for 遍历输出数据,可以使用解构语法 <divv-for="{ id, user } in [{ id: 1, user: 'yun' },{ id: 2, user: 'mu' },]":key="id">{{ user }}</div>复制代码
- <style> /* 全局有效 */ .content p { font-size: 12px; }</style> <style scoped> /* 只在该组件内有效 */ .content { background: green; }</style>复制代码 有时候我们想跳出scoped这个限定作用域,更改子组件的样式但不会影响全局样式,我们就可以使用深度选择器来完成 <style scoped>:deep(.ant-card-head-title){ background: green; }</style>复制代码 上面代码会被解析为 [data-v-e44d851a] .ant-card-head-title { background: green;}复制代码 注意:vue版本和预处理不同,深度选择器的写法也会不同,灵活使用即可15.借用props类型当我们当前组件使用到了Card组件 <Card :type="Mytype" :color="Mycolor">复制代码 其中的Mytype和Mycolor是我们通过props接收而来的 import Card from './Card.vue';export default { components: { Card }, props: { Mytype: { type: String, required: true, }, Mycolor: { type: String, default: "green", } },};复制代码 我们可以简写为 import Card from './Card.vue';const cardProps = {};Object.entries(Card.props).forEach(([key, value]) => {cardProps[`My${key}`] = value;});export default { components: { Card }, props: { ...cardProps },}; 完整附件点此下载
内自定义限制
props: {
size: {
// 自定义验证函数
validator: (val) => {
return ["small", "medium", "large"].includes(val);
},
}
}
复制代码
这个验证函数接受一个 prop值,验证并返回 true 或 false
透传属性
<!-- 此组件为二次封装的中间组件 -->
<template>
<MyComponent v-bind="$attrs"/>
</template>
<script>
export default {
// $attrs 中的所有属性不自动继承到组件的根元素上
inheritAttrs: false,
}
</script>
复制代码
- $attrs包含所有透传过来的对象,除显式声明接受的props、emits、slots
- 如不希望透传下去某些属性,可使用useAttrs()
const attrs = useAttrs();
const filteredAttrs = computed(() => {
return { ...attrs, style: undefined };
});
复制代码
$attrs还可与
listeners`包含了父组件传递的事件(不包含.native修饰器),它可以通过v-on="$listeners"转发传入内部组件,进行对事件的监听处理
<MyComponent v-bind="$attrs" v-on="$listeners" />
复制代码
注意:
attrs属性上
单个slot透传
<About>
<template #about>
<slot name="about" />
</template>
</About>
复制代码
多个slot透传
<template #[slotName] v-for="(slot, slotName) in $slots" >
<slot :name="slotName"/>
</template>
复制代码
多个slot透传作用域插槽
<template #[slotName]="slotProps" v-for="(slot, slotName) in $slots" >
<slot :name="slotName" v-bind="slotProps"/>
</template>
<!-- Vue2则需要将v-for里面循环的$slots改成$scopedSlots -->
复制代码
- webpack统一导入相同路径下的多个组件的方法
const path = require("path");
// 参数一:说明需要检索的目录,参数二:是否检索子目录,参数三::指定匹配文件名的正则表达式
const files = require.context("./components", false, /\.vue$/);
const modules = {};
files.keys().forEach((key) => {const name = path.basename(key, ".vue");modules[name] = files(key).default || files(key);
});
复制代码
- Vite 支持使用特殊的 import.meta.glob 函数从文件系统导入多个模块:
const modules = import.meta.glob('./src/*.js');
// vite 转译上面后生成的代码
const modules = {
'./src/foo.js': () => import('./src/foo.js'),
'./src/bar.js': () => import('./src/bar.js')
}
复制代码
// 参数一:说明需要检索的目录,参数二:是否检索子目录,参数三::指定匹配文件名的正则表达式
const files = require.context("./components", false, /\.vue$/);
const modules = {};
files.keys().forEach((key) => {const name = path.basename(key, ".vue");modules[name] = files(key).default || files(key);
});
复制代码
// vite 转译上面后生成的代码
const modules = {
'./src/foo.js': () => import('./src/foo.js'),
'./src/bar.js': () => import('./src/bar.js')
}
复制代码
- 组件都有一个特殊的$slots对象,包含所有的插槽对象,结构如下:
const $slots = {"default": [{...}],"slotA": [{...}],"slotB": [{...}]
}
复制代码
}
复制代码
我们可以使用v-if有条件的渲染slot更加合理,并且我们封装通用组件的时候最好预留个slot更好扩展
<template>
<div>
<div v-if="$slots.default">
<slot />
</div>
<div v-if="$slots.slotA">
<slot name="slotA"/>
</div>
</div>
</template>
复制代码
window.addEventListener('mousedown', e => {
// 获取被点击的元素
const clickedEl = e.target;
// `targetEl` 为检测的元素
if (targetEl.contains(clickedEl)) {
// 在"targetEl"内部点击
} else {
// 在"targetEl"之外点击
}
});
复制代码
// 获取被点击的元素
const clickedEl = e.target;
// `targetEl` 为检测的元素
if (targetEl.contains(clickedEl)) {
// 在"targetEl"内部点击
} else {
// 在"targetEl"之外点击
}
});
复制代码
动态组件:tab切换的时候可使用动态组件动态加载并缓存提供动效
<transition>
<keep-alive>
<!-- :is值必须是全局或者局部注册过的组件 -->
<component :is="currentTab"></component>
</keep-alive>
</transition>
复制代码
递归组件:模板里面自己自己,注意需要设置条件退出,不然会无限渲染,适合嵌套菜单,树形控件等
<div v-if="item.children">
{{ tree.label }}
<!-- 递归调用自身 -->
<tree v-for="(item, index) in tree.children" :tree="item" :key="index"></tree>
</div>
<script>
export default {
// 定义name,以使组件内部递归调用
name: 'tree',
// 接收外部传入的值
props: {
tree: {
type:Array,
default: () => []
}
}
}
</script>
复制代码
- 在下次 DOM 更新循环结束之后执行延迟回调
- 我们可以在修改数据后立即使用此方法获取最新的DOM
mounted(){
this.$nextTick(() => {
this.$refs.inputs.focus(); //通过 $refs 获取dom 并绑定 focus 方法
})
}
复制代码
this.$nextTick(() => {
this.$refs.inputs.focus(); //通过 $refs 获取dom 并绑定 focus 方法
})
}
复制代码
<div :class="$route.name === 'Home' ||$route.name === 'Gallery' ||$route.name === 'Profile'? 'classOnlyOnThesePages': ''"></div>
复制代码
复制代码
直接最佳写法如下:
<div :class="{classOnlyOnThesePages: ['Home', 'Gallery', 'Profile'].includes($route.name),}"></div>
复制代码
- 遇到全局可重用的工具方法,例如
class Utils {// 复制一段文字到剪切板copyToClipboard(text) {let copyText = document.createElement("input");document.body.appendChild(copyText);copyText.value = text;copyText.select();document.execCommand("copy");document.body.removeChild(copyText);}
}
export default new Utils();
复制代码
}
export default new Utils();
复制代码
我们可以抽离出来放在整个应用程序都能访问的地方
Vue2:
import Utils from "./utils/utils.js";
// 设置全局方法
Vue.prototype.$utils = Utils;
复制代码
Vue3:
import Utils from "./utils/utils.js";
const app = createApp(App);
// 设置全局方法
app.config.globalProperties.$utils = Utils;
app.mount("#app");
复制代码
接下来任何地方都能愉快的访问啦
this.$utils.copyToClipboard(text);
// Vue3 setup
const { proxy } = getCurrentInstance();
proxy.$utils.copyToClipboard(text);
复制代码
- 使用 v-if 方法来控制 router-view 的显示隐藏
<template><div id="app"><router-view v-if="isActive" /></div>
</template>
<script>
export default {name: "App",
// provider给下层组件重刷的方法provide() {return {reload: this.reload,};},data: {
isActive: true,},method: {reload() {this.isActive = false;this.$nextTick(() => {this.isActive = true;});},},
};
</script>
复制代码
</template>
<script>
export default {name: "App",
// provider给下层组件重刷的方法provide() {return {reload: this.reload,};},data: {
isActive: true,},method: {reload() {this.isActive = false;this.$nextTick(() => {this.isActive = true;});},},
};
</script>
复制代码
需要的页面可注入该方法使用
<script>
export default {inject: ["reload"],methods: {refresh() {this.reload();},},
};
</script>
复制代码
或者直接使用v-if操作该组件
<template><div v-if="isShow"></div>
</template>
<script>
export default {data() {return {isShow: true,};},method: {refresh() {this.isShow = false;this.$nextTick(() => {this.isShow = true;});},},
};
</script>
复制代码
或者借助Vue的diff算法,我们给元素设置一个唯一的Key值然后去改变它
<template><div :key="keyValue"></div>
</template>
<script>
export default {data() {return {keyValue: 0,};},method: {refresh() {this.keyValue++;},},
};
</script>
复制代码
Vue组件的API主要包含三部分:props、event、slot
- props 为组件接收的参数,最好用对象的写法,可更好设置类型默认值和自定义校验
- event用于子组件向父组件传递消息
- slot可以给组件动态插入一些内容或组件,是实现高阶组件的重要途径
组件封装最好还应遵循单向数据流,传递的props仅仅做展示,如需修改,则应该重新初始化一份全新的响应式数据并将props深拷贝后作为初始值
为 Vue 中的错误和警告提供自定义处理程序
// Vue 3
const app = createApp(App);
app.config.errorHandler = (err) => {
console.error(err);
};
// Vue 2
Vue.config.errorHandler = (err) => {
console.error(err);
};
复制代码
template 标签可以在模板内的任何地方使用,可以减少实际嵌套层级,更简化代码逻辑
<template>
<div class="card">
<h3>
{{ title }}
</h3>
<h4 v-if="isShow" class="card-desc">
{{ description }}
</h4>
<div v-if="isShow">
<slot />
</div>
<Home v-if="isShow" />
</div>
</template>
复制代码
上面代码v-if逻辑分组后:
<template>
<div class="card">
<h3>
{{ title }}
</h3>
<template v-if="isShow">
<h4 class="card-desc">
{{ description }}
</h4>
<div>
<slot />
</div>
<Home />
</template>
</div>
</template>
复制代码
在模板中使用 v-for 遍历输出数据,可以使用解构语法
<divv-for="{ id, user } in [{ id: 1, user: 'yun' },{ id: 2, user: 'mu' },]":key="id">{{ user }}
</div>
复制代码
<style>
/* 全局有效 */
.content p {
font-size: 12px;
}
</style>
<style scoped>
/* 只在该组件内有效 */
.content {
background: green;
}
</style>
复制代码
/* 全局有效 */
.content p {
font-size: 12px;
}
</style>
<style scoped>
/* 只在该组件内有效 */
.content {
background: green;
}
</style>
复制代码
有时候我们想跳出scoped这个限定作用域,更改子组件的样式但不会影响全局样式,我们就可以使用深度选择器来完成
<style scoped>
:deep(.ant-card-head-title){
background: green;
}
</style>
复制代码
上面代码会被解析为
[data-v-e44d851a] .ant-card-head-title {
background: green;
}
复制代码
注意:vue版本和预处理不同,深度选择器的写法也会不同,灵活使用即可
15.借用props类型
当我们当前组件使用到了Card组件
<Card :type="Mytype" :color="Mycolor">
复制代码
其中的Mytype和Mycolor是我们通过props接收而来的
import Card from './Card.vue';
export default {
components: { Card },
props: {
Mytype: {
type: String,
required: true,
},
Mycolor: {
type: String,
default: "green",
}
},
};
复制代码
我们可以简写为
import Card from './Card.vue';
const cardProps = {};
Object.entries(Card.props).forEach(([key, value]) => {cardProps[`My${key}`] = value;
});
export default {
components: { Card },
props: {
...cardProps
},
};
完整附件点此下载