1.require.context()
1.场景:如页面需要导入多个组件,原始写法:
import titleCom from '@/components/home/titleCom' import bannerCom from '@/components/home/bannerCom' import cellCom from '@/components/home/cellCom' components:{titleCom,bannerCom,cellCom}
2.这样就写了大量重复的代码,利用 require.context 可以写成
const path = require('path') const files = require.context('@/components/home', false, /.vue$/) const modules = {} files.keys().forEach(key => { const name = path.basename(key, '.vue') modules[name] = files(key).default || files(key) }) components:modules
这样不管页面引入多少组件,都可以使用这个方法
3.API 方法
实际上是 webpack 的方法,vue 工程一般基于 webpack,所以可以使用 require.context(directory,useSubdirectories,regExp) 接收三个参数: directory:说明需要检索的目录 useSubdirectories:是否检索子目录 regExp: 匹配文件的正则表达式,一般是文件名
2.优雅更新props
更新 prop
在业务中是很常见的需求,但在子组件中不允许直接修改 prop
,因为这种做法不符合单向数据流的原则,在开发模式下还会报出警告。因此大多数人会通过 $emit
触发自定义事件,在父组件中接收该事件的传值来更新 prop
。
child.vue:
export defalut { props: { title: String }, methods: { changeTitle(){ this.$emit('change-title', 'hello') } } }
parent.vue:
<child :title="title" @change-title="changeTitle"></child> export default { data(){ return { title: 'title' } }, methods: { changeTitle(title){ this.title = title } } }
这种做法没有问题,我也常用这种手段来更新 prop
。但如果你只是想单纯的更新 prop
,没有其他的操作。那么 sync
修饰符能够让这一切都变得特别简单。
parent.vue:
<child :title.sync="title"></child>
child.vue:
export defalut { props: { title: String }, methods: { changeTitle(){ this.$emit('update:title', 'hello') } } }
只需要在绑定属性上添加 .sync
,在子组件内部就可以触发 update:属性名
来更新 prop
。可以看到这种手段确实简洁且优雅,这让父组件的代码中减少一个“没必要的函数”。
自定义组件双向绑定
默认情况下,v-model
是 @input
事件侦听器和 :value
属性上的语法糖。但是,你可以在你的Vue组件中指定一个模型属性来定义使用什么事件和value属性——非常棒!
组件 model 选项: 允许一个自定义组件在使用 v-model 时定制 prop 和 event。默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event,但是一些输入类型比如单选框和复选框按钮可能想使用 value prop 来达到不同的目的。使用 model 选项可以回避这些情况产生的冲突。 input 默认作为双向绑定的更新事件,通过 $emit 可以更新绑定的值 <my-switch v-model="val"></my-switch> export default { props: { value: { type: Boolean, default: false } }, methods: { switchChange(val) { this.$emit('input', val) } } } 复制代码 修改组件的 model 选项,自定义绑定的变量和事件 <my-switch v-model="num" value="some value"></my-switch> export default { model: { prop: 'num', event: 'update' }, props: { value: { type: String, default: '' }, num: { type: Number, default: 0 } }, methods: { numChange() { this.$emit('update', num++) } } }
3.provide/inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
简单来说,一个组件将自己的属性通过 provide
暴露出去,其下面的子孙组件 inject
即可接收到暴露的属性。
App.vue:
export default { provide() { return { app: this } } }
child.vue:
export default { inject: ['app'], created() { console.log(this.app) // App.vue实例 } }
在 2.5.0+ 版本可以通过设置默认值使其变成可选项:
export default { inject: { app: { default: () => ({}) } }, created() { console.log(this.app) } }
如果你想为 inject
的属性变更名称,可以使用 from
来表示其来源:
export default { inject: { myApp: { // from的值和provide的属性名保持一致 from: 'app', default: () => ({}) } }, created() { console.log(this.myApp) } }
需要注意的是 provide
和 inject
主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。但是某些时候,或许它能帮助到我们。
4.巧用template
相信 v-if
在开发中是用得最多的指令,那么你一定遇到过这样的场景,多个元素需要切换,而且切换条件都一样,一般都会使用一个元素包裹起来,在这个元素上做切换。
<div v-if="status==='ok'"> <h1>Title</h1> <p>Paragraph 1</p> <p>Paragraph 2</p> </div>
如果像上面的 div 只是为了切换条件而存在,还导致元素层级嵌套多一层,那么它没有“存在的意义”。
我们都知道在声明页面模板时,所有元素需要放在 <template>
元素内。除此之外,它还能在模板内使用,<template>
元素作为不可见的包裹元素,只是在运行时做处理,最终的渲染结果并不包含它。
<template> <div> <template v-if="status==='ok'"> <h1>Title</h1> <p>Paragraph 1</p> <p>Paragraph 2</p> </template> </div> </template>
同样的,我们也可以在 <template>
上使用 v-for
指令,这种方式还能解决 v-for
和 v-if
同时使用报出的警告问题。
<template v-for="item in 10"> <div v-if="item % 2 == 0" :key="item">{{item}}</div> </template>
template使用v-if,
template使用v-for
5.小型状态管理器 Vue.observable
2.6.0 新增
用法:让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象;
返回的对象可以直接用于渲染函数和计算属性内,并且会在发生改变时触发相应的更新;
也可以作为最小化的跨组件状态存储器,用于简单的场景。
通讯原理实质上是利用Vue.observable实现一个简易的 vuex
// 文件路径 - /store/store.js import Vue from 'vue' export const store = Vue.observable({ count: 0 }) export const mutations = { setCount (count) { store.count = count } } //使用 <template> <div> <label for="bookNum">数 量</label> <button @click="setCount(count+1)">+</button> <span>{{count}}</span> <button @click="setCount(count-1)">-</button> </div> </template> <script> import { store, mutations } from '../store/store' // Vue2.6新增API Observable export default { name: 'Add', computed: { count () { return store.count } }, methods: { setCount: mutations.setCount } } </script>
6.卸载watch监听
通常定义数据观察,会使用选项的方式在 watch
中配置:
export default { data() { return { count: 1 } }, watch: { count(newVal) { console.log('count 新值:'+newVal) } } } // 或则 @Watch("visible") clearSearch(visible: boolean) { if (visible) { this.searchValue = ""; } }
除此之外,数据观察还有另一种函数式定义的方式:
export default { data() { return { count: 1 } }, created() { this.$watch('count', function(){ console.log('count 新值:'+newVal) }) } }
它和前者的作用一样,但这种方式使定义数据观察更灵活,而且 $watch
会返回一个取消观察函数,用来停止触发回调:
let unwatchFn = this.$watch('count', function(){ console.log('count 新值:'+newVal) }) this.count = 2 // log: count 新值:2 unwatchFn() this.count = 3 // 什么都没有发生... $watch 第三个参数接收一个配置选项: this.$watch('count', function(){ console.log('count 新值:'+newVal) }, { immediate: true // 立即执行watch })
7.$on(‘hook:’)删除事件监听器
删除事件监听器是一种常见的最佳实践,因为它有助于避免内存泄露并防止事件冲突。
如果你想在 created
或 mounted
的钩子中定义自定义事件监听器或第三方插件,并且需要在 beforeDestroy
钩子中删除它以避免引起任何内存泄漏,那么这是一个很好的特性。下面是一个典型的设置:
mounted () { window.addEventListener('resize', this.resizeHandler); }, beforeDestroy () { window.removeEventListener('resize', this.resizeHandler); }
使用 $on('hook:')
方法,你可以仅使用一种生命周期方法(而不是两种)来定义/删除事件。
mounted () { window.addEventListener('resize', this.resizeHandler); this.$on("hook:beforeDestroy", () => { window.removeEventListener('resize', this.resizeHandler); }) }
8.@hook:【event】父组件监听子组件的生命周期钩子函数
比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:
1 // Parent.vue 2 <Child @mounted="doSomething"/> 3 4 // Child.vue 5 mounted() { 6 this.$emit("mounted"); 7 }
以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:
1 // Parent.vue 2 <Child @hook:mounted="doSomething" ></Child> 3 4 doSomething() { 5 console.log('父组件监听到 mounted 钩子函数 ...'); 6 }, 7 8 // Child.vue 9 mounted(){ 10 console.log('子组件触发 mounted 钩子函数 ...'); 11 }, 12 13 // 以上输出顺序为: 14 // 子组件触发 mounted 钩子函数 ... 15 // 父组件监听到 mounted 钩子函数 ...
当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。
9.动态指令参数
Vue 2.6的最酷功能之一是可以将指令参数动态传递给组件。假设你有一个按钮组件,并且在某些情况下想监听单击事件,而在其他情况下想监听双击事件。这就是这些指令派上用场的地方:
<template> ... <aButton @[someEvent]="handleSomeEvent()" />... </template> <script> ... data(){ return{ ... someEvent: someCondition ? "click" : "dbclick" } }, methods: { handleSomeEvent(){ // handle some event } } </script>
场景 1.为组件添加loading效果 2.按钮级别权限控制 v-permission 3.代码埋点,根据操作类型定义指令 4.input 输入框自动获取焦点 注意事项 注意: 1.自定义指令名称,不能使用驼峰规则,而应该使用"my-dir" 或 “my_dir” 或 “mydir” 2.使用时,必须加v- 如:<p v-my-dir="xxxx"></p> 指令的生命周期 1.bind 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。 2.inserted 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。 3update 所在组件的 VNode 更新时调用,但是可能发生在其孩子的 VNode 更新之前。指令的值可能发生了改变也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新。 3.componentUpdated 所在组件的 VNode 及其孩子的 VNode 全部更新时调用。 4.unbind 只调用一次, 指令与元素解绑时调用。 钩子函数的参数 1.el 指令所绑定的元素,可以用来直接操作 DOM。 2.binding一个对象,包含以下属性: name: 指令名,不包括 v- 前缀。 value: 指令的绑定值, 例如: v-my-directive="1 + 1", value 的值是 2。 oldValue: 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。 expression: 绑定值的字符串形式。 例如 v-my-directive="1 + 1" , expression 的值是 "1 + 1"。 arg: 传给指令的参数。例如 v-my-directive:foo, arg 的值是 "foo"。 modifiers: 一个包含修饰符的对象。 例如: v-my-directive.foo.bar, 修饰符对象 modifiers 的值是 { foo: true, bar: true }。 3.vnode 编译生成的虚拟节点。 4.oldVnode】 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。 示例: bind: function (el, binding, vnode) {} [注意]除了 el 之外,其它参数都是只读的,尽量不要修改他们。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。 声明局部指令 <template> <div class="hello"> <div v-test='name'></div> </div> </template> <script> export default { data () { return { name:'我是名字', } }, directives:{ test:{ inserted: function (el,binding) {// 指令的定义 el.style.position = 'fixed' el.style.top = binding.value + 'px' }, bind: function (el, binding, vnode) { } } } } 全局声明指令 main.js 示例: import Vue from 'vue'; Vue.directive('focus',{ bind:function(e,v){ console.log('bind指令') }, inserted:function(e,v){ console.log('inserted指令') console.log(this) // 指令内部this指向window e.focus(); }, update:function(){ console.log('update指令') } }) 动态指令参数 指令的传参类型有两种: 1. v-xxxx="参数" 通过binding.value接收 2. v-xxx:参数1="参数2" 通过binding.arg 场景:我们需要把元素需要动态的固定在左或者顶部? 使用: <div v-for="(item, index) in list" :key="index"> <div v-zoom:{direction:item.direcition}="{ item.width, height: item,height}"></div> </div> <script> data () { return { list: [ { 100, height: 200, direction: 'left'}, { 140, height: 240, direction: 'top'} ] } } </script> 声明指令 directive('pin', { bind: function (el, binding, vnode) { el.style.position = 'fixed' var s = (binding.arg.direction == 'left' ? 'left' : 'top') el.style[s] = binding.value + 'px' } })
Vue.directive('role', { inserted: function (el, binding, vnode) { let role = binding.value if(role){ const applist = sessionStorage.getItem("applist") const hasPermission = role.some(item => applist.includes(item)) // 是否拥有权限 if(!hasPermission){ el.remove() //没有权限则删除模块节点 } } } }) Vue.directive('role', { inserted: function (el, binding, vnode) { let role = binding.value if(role){ // vnode.context 为当前实例 const applist = vnode.context.$store.state.applist const hasPermission = role.some(item => applist.includes(item)) if(!hasPermission){ el.remove() } } } })
10.解耦
一般在组件内使用路由参数,大多数人会这样做:
export default { methods: { getParamsId() { return this.$route.params.id } } }
在组件中使用 $route
会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。
正确的做法是通过 props
解耦
const router = new VueRouter({ routes: [{ path: '/user/:id', component: User, props: true }] })
将路由的 props
属性设置为 true
后,组件内可通过 props
接收到 params
参数
export default { props: ['id'], methods: { getParamsId() { return this.id } } }
另外你还可以通过函数模式来返回 props
const router = new VueRouter({ routes: [{ path: '/user/:id', component: User, props: (route) => ({ id: route.query.id }) }] })
重用相同路由的组件
开发人员经常遇到的情况是,多个路由解析为同一个Vue组件。问题是,Vue出于性能原因,默认情况下共享组件将不会重新渲染,如果你尝试在使用相同组件的路由之间进行切换,则不会发生任何变化。
const routes = [ { path: "/a", component: MyComponent }, { path: "/b", component: MyComponent }, ];
如果你仍然希望重新渲染这些组件,则可以通过在 router-view
组件中提供 :key
属性来实现。
<template> <router-view :key="$route.path"></router-view> </template>
把所有Props传到子组件很容易
这是一个非常酷的功能,可让你将所有 props 从父组件传递到子组件。如果你有另一个组件的包装组件,这将特别方便。所以,与其把所有的 props 一个一个传下去,你可以利用这个,把所有的 props 一次传下去:
<template> <childComponent v-bind="$props" /> </template>
代替:
<template> <childComponent :prop1="prop1" :prop2="prop2" :prop="prop3" :prop4="prop4" ... /> </template>
把所有事件监听传到子组件很容易
如果子组件不在父组件的根目录下,则可以将所有事件侦听器从父组件传递到子组件,如下所示:
<template> <div> ... <childComponentv-on="$listeners" />... <div> </template>
如果子组件位于其父组件的根目录,则默认情况下它将获得这些组件,因此不需要使用这个小技巧。
11.样式穿透
在开发中修改第三方组件样式是很常见,但由于 scoped
属性的样式隔离,可能需要去除 scoped
或是另起一个 style
。这些做法都会带来副作用(组件样式污染、不够优雅),样式穿透在css预处理器中使用才生效。
我们可以使用 >>>
或 /deep/
解决这一问题:
<style scoped> 外层 >>> .el-checkbox { display: block; font-size: 26px; .el-checkbox__label { font-size: 16px; } } </style>
<style scoped> /deep/ .el-checkbox { display: block; font-size: 26px; .el-checkbox__label { font-size: 16px; } } </style>
12.watch高阶使用
立即执行:immediate:true
watch
是在监听属性改变时才会触发,有些时候,我们希望在组件创建后 watch
能够立即执行
可能想到的的方法就是在 create
生命周期中调用一次,但这样的写法不优雅,或许我们可以使用这样的方法
深度监听 deep:true
在监听对象时,对象内部的属性被改变时无法触发 watch
,我们可以为其设置深度监听
触发监听执行多个方法
使用数组可以设置多项,形式包括字符串、函数、对象
export default { data: { name: 'Joe' }, watch: { name: [ 'sayName1', function(newVal, oldVal) { this.sayName2() }, { handler: 'sayName3', immaediate: true } ] }, methods: { sayName1() { console.log('sayName1==>', this.name) }, sayName2() { console.log('sayName2==>', this.name) }, sayName3() { console.log('sayName3==>', this.name) } } }
watch监听多个变量
watch本身无法监听多个变量。但我们可以将需要监听的多个变量通过计算属性返回对象,再监听这个对象来实现“监听多个变量”
export default { data() { return { msg1: 'apple', msg2: 'banana' } }, compouted: { msgObj() { const { msg1, msg2 } = this return { msg1, msg2 } } }, watch: { msgObj: { handler(newVal, oldVal) { if (newVal.msg1 != oldVal.msg1) { console.log('msg1 is change') } if (newVal.msg2 != oldVal.msg2) { console.log('msg2 is change') } }, deep: true } } }
13.自定义验证 Props
你可能已经知道可以将props验证为原始类型,例如字符串,数字甚至对象。你也可以使用自定义验证器——例如,如果你想验证一个字符串列表:
props: { status: { type: String, required: true, validator: function (value) { return [ 'syncing', 'synced', 'version-conflict', 'error' ].indexOf(value) !== -1 } } }