学会这4个高级技巧,晋升Vue高级打工人

前言

最近看了一篇谈及vue高级技巧的文章,给我看笑了,里面说什么自定义指令动态组件自定义过滤器` 等等都写在里面了,但凡在实际工作中写过几个中小型的项目,这些都是常用大家都熟知的烂大街的东西好吧,什么叫做真正的vue高级用法,今天来给大家盘点一下。

动态Watch

在vue中,watch可以监听数据的变化后重新执行函数,平常一般是这样定义Watch的。

javascript 复制代码
 export default {
     data() {
         return {
           a: 1,
         }
     },
     watch: {
         'a'(newVal, oldVal) {
           console.log('watch重新执行', oldVal, newVal);
         }
     }
 }

上面监听了a的值,在变化时会执行Watch,这个叫做静态Watch,不过vue其实是支持动态watch的,使用this.$watch API 可以实现对一个值的动态监听,用法如下:

javascript 复制代码
 export default {
     data() {
         return {
           a: 1,
           unwatch: null,
         }
     },
     mounted() {
         this.unwatch = this.$watch('a', (newVal, oldVal) => {
           console.log('watch执行', oldVal, newVal);
         }, {
           immediate: true,
         })
     }
     beforeDestroy() {
         if (this.unwatch) {
             // 主动销毁watch
             this.unwatch();
             this.unwatch = null;
         }
     }
 }

这里在组件中使用$watch对a进行监听,第一个参数可以传一个Function或者String,第二个参数是函数,第三个参数是WatchOptions,里面包括immediatedeep这些,然后返回一个销毁此watch的函数unwatch

这里要注意,对于静态watch来说,vue会组件初始化时会帮你初始化watch,在组件销毁时帮你销毁掉watch。

而对于动态watch则需要自己主动调用unwatch销毁,否则组件被销毁后会一直存在内存中,造成内存泄漏,而且下次初始化这个组件时,又会创建一个新的watch,这样就会造成watch越来越多,内存泄漏越来越严重。

那么,动态watch有什么应用场景呢?

假设你现在有一个价格price,在这个价格变化后需要执行一段逻辑。而这个价格是通过后端接口拿到数据后初始化的,而没初始化之前,我是不需要执行这段逻辑的,这时候如果用静态watch,肯定会让watch执行一次 ,如果在watch执行中有一些副作用 ,那就还要多加一些逻辑判断,而且这次执行是毫无意义的,应当避免它执行,这时候用动态watch就再合适不过。

javascript 复制代码
 export default {
      return {
        a: 1,
        unwatch: null,
     }
     async mounted() {
         this.a = await fetch('./xxx');
         this.unwatch = this.$watch('a', (newVal, oldVal) => {
           console.log('watch执行', oldVal, newVal);
         }, {
           immediate: true,
         })
     },
     beforeDestroy() {
         if (this.unwatch) {
             // 主动销毁watch
             this.unwatch();
             this.unwatch = null;
         }
     }
 }

@hook

在vue中可以通过@hook监听vue的生命周期钩子,来举个例子:

xml 复制代码
 // 父组件 Home.vue
 <template>
   <div>
        <Common @hook:mounted="commonMounted" />
     </div>
 </template>
 ​
 <script>
 export default {
     methods: {
         commonMounted() {
             console.log('listen common mounted');
         }
     }
 }
 </script>
 ​
 // 子组件 Common.vue
 ​
 <script>
 export default {
     mounted() {
       console.log('common mounted');
     }
 }
 </script>
 ​
 打印结果如下:
 ​
 common mounted
 listen common mounted

这里在父组件Home.vue中用@hook:mounted给子组件增加监听,然后在子组件Common.vuemounted钩子调用完后,会调用@hook:mounted的监听函数commonMounted,这时候你就可以通过 $ref.childCompoent.$el 拿到子组件的操作了。

不仅如此,@hook还可以实现动态监听,实现程序化的监听器

就拿我们上面讲的$watch动态监听来说,有没有发现我们销毁watch的流程存在一点问题?

  1. 问题一:需要把创建出来的@watch保存在组件的一个变量unwatch上,最好只有生命周期钩子可以访问到它。
  2. 问题二:创建watch的代码独立于销毁watch的代码,维护性差,比如我们需要删除这个watch,还需要把unwatch,以及beforeDestory里面的代码一起删除,最好创建和销毁的代码能放在一起。
javascript 复制代码
 export default {
      return {
        a: 1,
        unwatch: null,
     }
     mounted() {
         this.unwatch = this.$watch('a', (newVal, oldVal) => {
           console.log('watch执行', oldVal, newVal);
         }, {
           immediate: true,
         })
     },
     beforeDestroy() {
         if (this.unwatch) {
             // 主动销毁watch
             this.unwatch();
             this.unwatch = null;
         }
     }
 }

@hook就能轻松解决这两个问题,来看如下代码:

javascript 复制代码
 export default {
     mounted() {
         const unwatch = this.$watch('a', (newVal, oldVal) => {
           console.log('watch执行', oldVal, newVal);
         }, {
           immediate: true,
         })
         // 这里使用 $once,当回调执行后便释放监听
         this.$once('hook:beforeDestroy', function () {
           unwatch();
         })
     },
 }

直接用程序监听当前组件的beforeDestory钩子,既省去了unwatch变量,又讲创建和清理的代码聚合在一起,方便维护。

Vue.mixin 全局混入

在平时项目开发中,为了提取一些公共逻辑,会使用到mixin进行混入,它可以将一些定义的methoddatacomputed生命周期钩子created、mounted等注入到组件中。

javascript 复制代码
 // mixin.js
 export default {
     data() {
         return {
             name: 'zs'
         }
     },
     computed: {
         finalName() {
             return 'final ' + this.name;
         }
     },
     created() {
         console.log('mixin created');
     },
     methods: {
         getName() {
             return this.name;
         }
     }
 }
 // App.vue
 import mixin from './mixin';
 export default {
   mixins: [mixin],
   created() {
     console.log(this.finalName);
     console.log(this.getName());
   },
 }
 ​
 /**
 输出结果:
 mixin created
 final zs
 zs
 */

这是组件级别的混入,但其实Vue还提供了全局混入的能力,也就是Vue.mixin API。它可以向在Vue注册的所有实例注入自己自定义的行为,我们知道每个vue组件就是一个vue的实例,也就是说它会向每个vue组件都进行混入。

javascript 复制代码
 // main.js
 Vue.mixin({
   beforeCreate() {
     console.log("beforeCreate");
     this.owner = 'vue';
   }
 })

这样会向每个组件都注入一个ower属性,并且其值为vue。我们在每个组件中都可以使用这个值了。

xml 复制代码
 <template>
   <div>
     {{ owner }}
   </div>
 </template>
 ​
 <script>
 // APP.vue
 export default {
   beforeCreate() {
     // 如果定义了同名的钩子,Vue.mixin的钩子会先于组件的执行
     console.log('app beforeCreate');
   },
   created() {;
     console.log(this.owner)
   },
 }
   
 /**
 打印结果:
 ​
 beforeCreate
 app beforeCreate
 vue
 ​
 */
 </script>

这个功能在写vue插件的时候非常好用,比如vuex用它向每个组件注入了$store属性,vue-router用它向每个组件注入了$router$route实现,我们在写自定义一个vue插件,也可以使用这个方法进行注入。

Vue.util.defineReactive

vue提供了一个工具函数defineReactive,可以把一个数据变成响应式的,也就是数据变化了会刷新页面。

xml 复制代码
 <template>
   <div>
     {{ owner }}
   </div>
 </template>
 ​
 <script>
 import Vue from "vue";
 // APP.vue
 export default {
   created() {
     Vue.util.defineReactive(this, 'owner', 'vue');
     setTimeout(() => {
       this.owner = 'vue1';
     }, 1000)
   },
 }
 </script>

页面会先渲染vue,1秒后变成vue1,你可能会问,这不就跟this.$set一样么?

在这个例子中,由于是给组件实例this定义一个新属性,所以跟this.$set效果一样,但它是可以给任意对象定义的。

javascript 复制代码
 // util.js
 import Vue from 'vue';
 ​
 export const obj = {
     nickname: 'zs',
 }
 ​
 // defineReactive重新定义一下
 Vue.util.defineReactive(obj, 'nickname', 'zs');
 ​
 // 1秒后更新 obj.nickname
 setTimeout(() => {
     obj.nickname = 'ls';
 }, 1000)
 ​
xml 复制代码
 <template>
   <div>
     {{ nickname }}
   </div>
 </template>
 ​
 <script>
 // APP.vue
 import Vue from "vue";
 import { obj } from './util'
 ​
 export default {
   computed: {
     nickname() {
       return obj.nickname;
     }
   },
 }
 </script>

页面上会先渲染zs,1秒后更新成ls。这样我们可以就把响应式数据从组件中抽离出去了。

不过这个API不是vue暴露给用户使用的,官方文档上也没有它的说明,要谨慎使用

简单实战:实现全局主题样式控制

我们可以将defineReactiveVue.mixin配合使用,实现响应式的全局混入

先在main.js中给每个组件混入beforeCreate,并使用defineReactive给每个组件定义响应式的$theme属性。

javascript 复制代码
 // main.js
 Vue.mixin({
   beforeCreate() {
    Vue.util.defineReactive(this, "$theme", 'light');
   }
 })
xml 复制代码
 <template>
   <div :class="$theme">
     {{ $theme }}
   </div>
 </template>
 ​
 <script>
 // APP.vue
 export default {
   created() {
     setTimeout(() => {
       this.$theme = 'dark';
     }, 1000)
   },
 }
 </script>
 <style>
   .light {
     // ...
   }
   .dark {
     // ...
   }
 </style>

这样就可以通过$theme不仅实现全局的主题样式切换,而且可以拿$theme这个值去在computedwatch中使用,实现动态控制,非常灵活。

当然,这只是简单实现主题切换,实际项目用的方案会更加完善和复杂。

小结

上面介绍了vue的一些高级用法:动态watch@hookVue.mixindefineReactive,并用Vue.mixindefineReactive简单实现了一个主题样式切换的功能,希望能帮助到大家。学会这些高级用法,晋升vue高级打工人,哈哈。

对于这些用法,大家在实际开发中有使用过么?

相关推荐
烂蜻蜓9 分钟前
深入理解 Uniapp 中的 px 与 rpx
前端·css·vue.js·uni-app·html
Ama_tor11 分钟前
网页制作06-html,css,javascript初认识のhtml如何建立超链接
javascript·css·html
木亦Sam25 分钟前
响应式网页设计中媒体查询的进阶运用
前端·响应式设计
diemeng111929 分钟前
2024系统编程语言风云变幻:Rust持续领跑,Zig与Ada异军突起
开发语言·前端·后端·rust
烂蜻蜓31 分钟前
Uniapp 中布局魔法:display 属性
前端·javascript·css·vue.js·uni-app·html
视觉CG1 小时前
【Viewer.js】vue3封装图片查看器
开发语言·javascript·vue.js
java1234_小锋1 小时前
一周学会Flask3 Python Web开发-redirect重定向
前端·python·flask·flask3
GDAL1 小时前
UniApp SelectorQuery 讲解
vue.js
琑951 小时前
nextjs项目搭建——头部导航
开发语言·前端·javascript
light多学一点1 小时前
视频的分片上传
前端