10个略骚的 Vue 开发技巧

路由参数解耦

一般在组件内使用路由参数,大多数人会这样做:

1export default { 2 methods: { 3 getParamsId() { 4 return this.$route.params.id 5 } 6 } 7} 8

在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。

正确的做法是通过 props 解耦

1const router = new VueRouter({ 2 routes: [{ 3 path: /user/:id , 4 component: User, 5 props: true 6 }] 7}) 8

将路由的 props 属性设置为 true 后,组件内可通过 props 接收到 params 参数

1export default { 2 props: [ id ], 3 methods: { 4 getParamsId() { 5 return this.id 6 } 7 } 8} 9

另外你还可以通过函数模式来返回 props

1const router = new VueRouter({ 2 routes: [{ 3 path: /user/:id , 4 component: User, 5 props: (route) => ({ 6 id: route.query.id 7 }) 8 }] 9}) 10

函数式组件

函数式组件是无状态,它无法实例化,没有任何的生命周期和方法。创建函数式组件也很简单,只需要在模板添加 functional 声明即可。一般适合只依赖于外部数据的变化而变化的组件,因其轻量,渲染性能也会有所提高。

组件需要的一切都是通过 context 参数传递。它是一个上下文对象,具体属性查看文档。这里 props 是一个包含所有绑定属性的对象。

函数式组件

1<template functional> 2 <div class="list"> 3 <div class="item" v-for="item in props.list" :key="item.id" @click="props.itemClick(item)"> 4 <p>{{item.title}}</p> 5 <p>{{item.content}}</p> 6 </div> 7 </div> 8</template> 9

父组件使用

1<template> 2 <div> 3 <List :list="list" :itemClick="item => (currentItem = item)" /> 4 </div> 5</template> 6
1import List from @/components/List.vue 2export default { 3 components: { 4 List 5 }, 6 data() { 7 return { 8 list: [{ 9 title: title , 10 content: content 11 }], 12 currentItem: 13 } 14 } 15} 16

样式穿透

在开发中修改第三方组件样式是很常见,但由于 scoped 属性的样式隔离,可能需要去除 scoped 或是另起一个 style 。这些做法都会带来副作用(组件样式污染、不够优雅),样式穿透在css预处理器中使用才生效。

我们可以使用 >>>/deep/ 解决这一问题:

1<style scoped> 2外层 >>> .el-checkbox { 3 display: block; 4 font-size: 26px; 5 6 .el-checkbox__label { 7 font-size: 16px; 8 } 9} 10</style> 11
1<style scoped> 2/deep/ .el-checkbox { 3 display: block; 4 font-size: 26px; 5 6 .el-checkbox__label { 7 font-size: 16px; 8 } 9} 10</style> 11

watch高阶使用

立即执行

watch 是在监听属性改变时才会触发,有些时候,我们希望在组件创建后 watch 能够立即执行

可能想到的的方法就是在 create 生命周期中调用一次,但这样的写法不优雅,或许我们可以使用这样的方法

1export default { 2 data() { 3 return { 4 name: Joe 5 } 6 }, 7 watch: { 8 name: { 9 handler: sayName , 10 immediate: true 11 } 12 }, 13 methods: { 14 sayName() { 15 console.log(this.name) 16 } 17 } 18} 19

深度监听

在监听对象时,对象内部的属性被改变时无法触发 watch ,我们可以为其设置深度监听

1export default { 2 data: { 3 studen: { 4 name: Joe , 5 skill: { 6 run: { 7 speed: fast 8 } 9 } 10 } 11 }, 12 watch: { 13 studen: { 14 handler: sayName , 15 deep: true 16 } 17 }, 18 methods: { 19 sayName() { 20 console.log(this.studen) 21 } 22 } 23} 24

触发监听执行多个方法

使用数组可以设置多项,形式包括字符串、函数、对象

1export default { 2 data: { 3 name: Joe 4 }, 5 watch: { 6 name: [ 7 sayName1 , 8 function(newVal, oldVal) { 9 this.sayName2() 10 }, 11 { 12 handler: sayName3 , 13 immaediate: true 14 } 15 ] 16 }, 17 methods: { 18 sayName1() { 19 console.log( sayName1==> , this.name) 20 }, 21 sayName2() { 22 console.log( sayName2==> , this.name) 23 }, 24 sayName3() { 25 console.log( sayName3==> , this.name) 26 } 27 } 28} 29

watch监听多个变量

watch本身无法监听多个变量。但我们可以将需要监听的多个变量通过计算属性返回对象,再监听这个对象来实现“监听多个变量”

1export default { 2 data() { 3 return { 4 msg1: apple , 5 msg2: banana 6 } 7 }, 8 compouted: { 9 msgObj() { 10 const { msg1, msg2 } = this 11 return { 12 msg1, 13 msg2 14 } 15 } 16 }, 17 watch: { 18 msgObj: { 19 handler(newVal, oldVal) { 20 if (newVal.msg1 != oldVal.msg1) { 21 console.log( msg1 is change ) 22 } 23 if (newVal.msg2 != oldVal.msg2) { 24 console.log( msg2 is change ) 25 } 26 }, 27 deep: true 28 } 29 } 30} 31 32

事件参数$event

$event 是事件对象的特殊变量,在一些场景能给我们实现复杂功能提供更多可用的参数

原生事件

在原生事件中表现和默认的事件对象相同

1<template> 2 <div> 3 <input type="text" @input="inputHandler( hello , $event)" /> 4 </div> 5</template> 6
1export default { 2 methods: { 3 inputHandler(msg, e) { 4 console.log(e.target.value) 5 } 6 } 7} 8

自定义事件

在自定义事件中表现为捕获从子组件抛出的值

my-item.vue :

1export default { 2 methods: { 3 customEvent() { 4 this.$emit( custom-event , some value ) 5 } 6 } 7} 8复制代码 9

App.vue

1<template> 2 <div> 3 <my-item v-for="(item, index) in list" @custom-event="customEvent(index, $event)"> 4 </my-list> 5 </div> 6</template> 7
1export default { 2 methods: { 3 customEvent(index, e) { 4 console.log(e) // some value 5 } 6 } 7} 8 9

自定义组件双向绑定

组件 model 选项:

允许一个自定义组件在使用 v-model 时定制 prop 和 event。默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event,但是一些输入类型比如单选框和复选框按钮可能想使用 value prop 来达到不同的目的。使用 model 选项可以回避这些情况产生的冲突。

input 默认作为双向绑定的更新事件,通过 $emit 可以更新绑定的值

1<my-switch v-model="val"></my-switch> 2
1export default { 2 props: { 3 value: { 4 type: Boolean, 5 default: false 6 } 7 }, 8 methods: { 9 switchChange(val) { 10 this.$emit( input , val) 11 } 12 } 13} 14

修改组件的 model 选项,自定义绑定的变量和事件

1<my-switch v-model="num" value="some value"></my-switch> 2
1export default { 2 model: { 3 prop: num , 4 event: update 5 }, 6 props: { 7 value: { 8 type: String, 9 default: 10 }, 11 num: { 12 type: Number, 13 default: 0 14 } 15 }, 16 methods: { 17 numChange() { 18 this.$emit( update , this.num++) 19 } 20 } 21} 22 23

监听组件生命周期

通常我们监听组件生命周期会使用 $emit ,父组件接收事件来进行通知

子组件

1export default { 2 mounted() { 3 this.$emit( listenMounted ) 4 } 5} 6

父组件

1<template> 2 <div> 3 <List @listenMounted="listenMounted" /> 4 </div> 5</template> 6

其实还有一种简洁的方法,使用 @hook 即可监听组件生命周期,组件内无需做任何改变。同样的, createdupdated 等也可以使用此方法。

1<template> 2 <List @hook:mounted="listenMounted" /> 3</template> 4

程序化的事件侦听器

比如,在页面挂载时定义计时器,需要在页面销毁时清除定时器。这看起来没什么问题。但仔细一看 this.timer 唯一的作用只是为了能够在 beforeDestroy 内取到计时器序号,除此之外没有任何用处。

1export default { 2 mounted() { 3 this.timer = setInterval(() => { 4 console.log(Date.now()) 5 }, 1000) 6 }, 7 beforeDestroy() { 8 clearInterval(this.timer) 9 } 10} 11

如果可以的话最好只有生命周期钩子可以访问到它。这并不算严重的问题,但是它可以被视为杂物。

我们可以通过 $on$once 监听页面生命周期销毁来解决这个问题:

1export default { 2 mounted() { 3 this.creatInterval( hello ) 4 this.creatInterval( world ) 5 }, 6 creatInterval(msg) { 7 let timer = setInterval(() => { 8 console.log(msg) 9 }, 1000) 10 this.$once( hook:beforeDestroy , function() { 11 clearInterval(timer) 12 }) 13 } 14} 15

使用这个方法后,即使我们同时创建多个计时器,也不影响效果。因为它们会在页面销毁后程序化的自主清除。

手动挂载组件

在一些需求中,手动挂载组件能够让我们实现起来更加优雅。比如一个弹窗组件,最理想的用法是通过命令式调用,就像 elementUIthis.$message 。而不是在模板中通过状态切换,这种实现真的很糟糕。

先来个最简单的例子:

1import Vue from vue 2import Message from ./Message.vue 3 4// 构造子类 5let MessageConstructor = Vue.extend(Message) 6// 实例化组件 7let messageInstance = new MessageConstructor() 8// $mount可以传入选择器字符串,表示挂载到该选择器 9// 如果不传入选择器,将渲染为文档之外的的元素,你可以想象成 document.createElement()在内存中生成dom 10messageInstance.$mount() 11// messageInstance.$el获取的是dom元素 12document.body.appendChild(messageInstance.$el) 13

下面实现一个简易的 message 弹窗组件

Message/index.vue

1<template> 2 <div class="wrap"> 3 <div class="message" :class="item.type" v-for="item in notices" :key="item._name"> 4 <div class="content">{{item.content}}</div> 5 </div> 6 </div> 7</template> 8
1// 默认选项 2const DefaultOptions = { 3 duration: 1500, 4 type: info , 5 content: 这是一条提示信息!, 6} 7let mid = 0 8export default { 9 data() { 10 return { 11 notices: [] 12 } 13 }, 14 methods: { 15 add(notice = {}) { 16 // name标识 用于移除弹窗 17 let _name = this.getName() 18 // 合并选项 19 notice = Object.assign({ 20 _name 21 }, DefaultOptions, notice) 22 23 this.notices.push(notice) 24 25 setTimeout(() => { 26 this.removeNotice(_name) 27 }, notice.duration) 28 }, 29 getName() { 30 return msg_ + (mid++) 31 }, 32 removeNotice(_name) { 33 let index = this.notices.findIndex(item => item._name === _name) 34 this.notices.splice(index, 1) 35 } 36 } 37} 38
1.wrap { 2 position: fixed; 3 top: 50px; 4 left: 50%; 5 display: flex; 6 flex-direction: column; 7 align-items: center; 8 transform: translateX(-50%); 9} 10 11.message { 12 --borderWidth: 3px; 13 min-width: 240px; 14 max-width: 500px; 15 margin-bottom: 10px; 16 border-radius: 3px; 17 box-shadow: 0 0 8px #ddd; 18 overflow: hidden; 19} 20 21.content { 22 padding: 8px; 23 line-height: 1.3; 24} 25 26.message.info { 27 border-left: var(--borderWidth) solid #909399; 28 background: #F4F4F5; 29} 30 31.message.success { 32 border-left: var(--borderWidth) solid #67C23A; 33 background: #F0F9EB; 34} 35 36.message.error { 37 border-left: var(--borderWidth) solid #F56C6C; 38 background: #FEF0F0; 39} 40 41.message.warning { 42 border-left: var(--borderWidth) solid #E6A23C; 43 background: #FDF6EC; 44} 45

Message/index.js

1import Vue from vue 2import Index from ./index.vue 3 4let messageInstance = null 5let MessageConstructor = Vue.extend(Index) 6 7let init = () => { 8 messageInstance = new MessageConstructor() 9 messageInstance.$mount() 10 document.body.appendChild(messageInstance.$el) 11} 12 13let caller = (options) => { 14 if (!messageInstance) { 15 init(options) 16 } 17 messageInstance.add(options) 18} 19 20export default { 21 // 返回 install 函数 用于 Vue.use 注册 22 install(vue) { 23 vue.prototype.$message = caller 24 } 25} 26

main.js

1import Message from @/components/Message/index.js 2 3Vue.use(Message) 4

使用

1this.$message({ 2 type: success , 3 content: 成功信息提示 , 4 duration: 3000 5}) 6
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )