路由参数解耦

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

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

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

正确的做法是通过 props 解耦

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

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

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

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

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

文档:https://router.vuejs.org/zh/guide/essentials/passing-props.html

函数式组件

函数式组件是无状态,它无法实例化,没有任何的生命周期和方法。创建函数式组件也很简单,只需要在模板添加 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>

父组件使用

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

文档:https://cn.vuejs.org/v2/guide/render-function.html#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6

样式穿透

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

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

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

watch高阶使用

立即执行

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

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

  1. export 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. }

深度监听

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

  1. export 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. }

触发监听执行多个方法

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

  1. export 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. }

文档:https://cn.vuejs.org/v2/api/#watch

watch监听多个变量

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

  1. export 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. }

事件参数$event

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

原生事件

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

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

自定义事件

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

my-item.vue :

  1. export default {
  2. methods: {
  3. customEvent() {
  4. this.$emit('custom-event', 'some value')
  5. }
  6. }
  7. }

App.vue

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

文档:https://cn.vuejs.org/v2/guide/events.html#%E5%86%85%E8%81%94%E5%A4%84%E7%90%86%E5%99%A8%E4%B8%AD%E7%9A%84%E6%96%B9%E6%B3%95

https://cn.vuejs.org/v2/guide/components.html#%E4%BD%BF%E7%94%A8%E4%BA%8B%E4%BB%B6%E6%8A%9B%E5%87%BA%E4%B8%80%E4%B8%AA%E5%80%BC

自定义组件双向绑定

组件 model 选项:

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

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

  1. <my-switch v-model="val"></my-switch>
  1. export 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. }

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

  1. <my-switch v-model="num" value="some value"></my-switch>
  1. export 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. }

文档:https://cn.vuejs.org/v2/api/#model

监听组件生命周期

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

子组件

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

父组件

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

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

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

程序化的事件侦听器

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

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

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

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

  1. export 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. }

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

文档:https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E7%A8%8B%E5%BA%8F%E5%8C%96%E7%9A%84%E4%BA%8B%E4%BB%B6%E4%BE%A6%E5%90%AC%E5%99%A8

手动挂载组件

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

先来个最简单的例子:

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

下面实现一个简易的 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>
  1. // 默认选项
  2. const DefaultOptions = {
  3. duration: 1500,
  4. type: 'info',
  5. content: '这是一条提示信息!',
  6. }
  7. let mid = 0
  8. export 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. this.notices.push(notice)
  23. setTimeout(() => {
  24. this.removeNotice(_name)
  25. }, notice.duration)
  26. },
  27. getName() {
  28. return 'msg_' + (mid++)
  29. },
  30. removeNotice(_name) {
  31. let index = this.notices.findIndex(item => item._name === _name)
  32. this.notices.splice(index, 1)
  33. }
  34. }
  35. }
  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. .message {
  11. --borderWidth: 3px;
  12. min-width: 240px;
  13. max-width: 500px;
  14. margin-bottom: 10px;
  15. border-radius: 3px;
  16. box-shadow: 0 0 8px #ddd;
  17. overflow: hidden;
  18. }
  19. .content {
  20. padding: 8px;
  21. line-height: 1.3;
  22. }
  23. .message.info {
  24. border-left: var(--borderWidth) solid #909399;
  25. background: #F4F4F5;
  26. }
  27. .message.success {
  28. border-left: var(--borderWidth) solid #67C23A;
  29. background: #F0F9EB;
  30. }
  31. .message.error {
  32. border-left: var(--borderWidth) solid #F56C6C;
  33. background: #FEF0F0;
  34. }
  35. .message.warning {
  36. border-left: var(--borderWidth) solid #E6A23C;
  37. background: #FDF6EC;
  38. }

Message/index.js

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

main.js

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

使用

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

文档:https://cn.vuejs.org/v2/api/#vm-mount

作为一位Vue工程师,这些开发技巧你都会吗?的更多相关文章

  1. vue、element-ui开发技巧

    1.vue下input文本框获得光标 html: <el-input size="mini" clearable v-model.trim="addOrEditDa ...

  2. 作为一名Java开发工程师需要掌握的专业技能

    在学习Java编程完之后,学员们面临的就是就业问题.作为一名Java开发工程师,企业在招聘的时候,也是有一定的标准的. 为了帮助大家更好的找到适合自己的工作,在这里分享了作为一名Java开发工程师需要 ...

  3. 一位ML工程师构建深度神经网络的实用技巧

    一位ML工程师构建深度神经网络的实用技巧 https://mp.weixin.qq.com/s/2gKYtona0Z6szsjaj8c9Vg 作者| Matt H/Daniel R 译者| 婉清 编辑 ...

  4. 作为一名Python开发,我谈Linux和mac的使用体验

    我是一名Python开发,在2018.7~2021.6使用的是Linux系统 Deepin OS 作为自己的开发系统:在2022.7-至今使用的是 mac OS 系统作为开发系统. Deepin OS ...

  5. WebApp开发技巧大全 看了就明白了

    [转载]阅读原文 自Iphone和Android这两个牛逼的手机操作系统发布以来,在互联网界从此就多了一个新的名词-WebApp(意为基于WEB形式的应用程 序,运行在高端的移动终端设备).开发者们都 ...

  6. Android开发技巧——自定义控件之自定义属性

    Android开发技巧--自定义控件之自定义属性 掌握自定义控件是很重要的,因为通过自定义控件,能够:解决UI问题,优化布局性能,简化布局代码. 上一篇讲了如何通过xml把几个控件组织起来,并继承某个 ...

  7. ES6 Javascript 实用开发技巧

    ES6 实用开发技巧 定义变量/常量 ES6 中新增加了 let 和 const 两个命令,let 用于定义变量,const 用于定义常量 两个命令与原有的 var 命令所不同的地方在于,let, c ...

  8. 作为一名SAP从业人员,需要专门学习数学么

    最近和SAP成都研究院的开发同事聊到过这个话题,Jerry来说说自己的看法. 先回忆回忆自己本科和研究生学过的数学课程.Jerry的大一生活是在电子科技大学的九里堤校区度过的,本科第一门数学课就是微积 ...

  9. 经典收藏 50个jQuery Mobile开发技巧集萃

    http://www.cnblogs.com/chu888chu888/archive/2011/11/10/2244181.html 1.Backbone移动实例 这是在Safari中运行的一款Ba ...

随机推荐

  1. <JZOJ5941>乘

    emmm还挺妙 不过我没想到qwq 考场上瞎写的还mle了心碎 把b分两..预处理下 O1询问qwq #include<cstdio> #include<iostream> # ...

  2. kNN算法 Demo

    项目链接: https://github.com/WES6/kNN

  3. 使用itchat发送天气信息

    微信发送当日天气情况 念头萌生 之前在浏览网站的时候发现了篇文章「玩转树莓派」为女朋友打造一款智能语音闹钟,文章中介绍了使用树莓派打造一款语音播报天气的闹钟. 当时就想照着来,也自己做个闹钟.因为一直 ...

  4. 码海拾遗:基于MySQL Connector/C++的MySQL操作(连接池)

    1.MySQL安装及简单设置 (1)安装:在OSX系统下,可以使用万能的“brew install”命令来进行安装:brew isntall mysql(默认安装最新版的MySQL) (2)启动:br ...

  5. kibana增加验证

    Kibana从5.5开始不提供认证功能,想用官方的认证X-Pack收费 ... 自己动手吧,用nginx的代理加apache生成的密码认证文件.环境:ubuntu16.04 安装nginxapt-ge ...

  6. PAT B1080 MOOC期终成绩(C++)

    PAT甲级目录 | PAT乙级目录 题目描述 B1080 MOOC期终成绩 解题思路 可利用 map 将字符串型的学号转换为整型的序号,方便查找.输入全部成绩后,遍历每个学生同时计算最终成绩,然后将成 ...

  7. Mybatis: 插件及分页

    Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的). Mybatis支持对Executor.StatementHa ...

  8. Picaso完美兼容OkHttp3.3,缓存优化两不误 - Tamic Developer"s Blog

    为何在Fresco,Glide这么强大的背景下,我又想起了当初的Picasso,又为何写这篇文章?是因为最近项目采用了square公司的RxAndroid,Retrfit和OKhttp, 不得不联想到 ...

  9. Ueditor富文本编辑器--Ctrl V 粘贴后原有图片显示错误

    最近负责将公司官网从静态网站改版成动态网站,方便公司推广营销人员修改增加文案,避免官网文案维护过于依赖技术人员.在做后台管理系统时用到了富文本编辑器Ueditor,因为公司有一个阿里云文件资源服务器, ...

  10. 07.深入浅出 Spring Boot - 数据访问之Mybatis(附代码下载)

    MyBatis 在Spring Boot应用非常广,非常强大的一个半自动的ORM框架. 代码下载:https://github.com/Jackson0714/study-spring-boot.gi ...