Vue中,不同的选项有不同的合并策略,比如 data,props,methods是同名属性覆盖合并,其他直接合并,而生命周期钩子函数则是将同名的函数放到一个数组中,在调用的时候依次调用

Vue中,提供了一个api, Vue.config.optionMergeStrategies,可以通过这个api去自定义选项的合并策略。

在代码中打印

console.log(Vue.config.optionMergeStrategies)

  

通过合并策略自定义生命周期函数

背景

最近客户给领导反馈,我们的系统用一段时间,浏览器就变得有点卡,不知道为什么。问题出来了,本来想甩锅到后端,但是浏览器问题,没法甩锅啊,那就排查吧。

后来发现页面有许多定时器,ajax轮询还有动画,打开一个浏览器页签没法问题,打开多了,浏览器就变得卡了,这时候我就想如果能在用户切换页签时候将这些都停掉,不久解决了。百度里面上下检索,找到了一个事件visibilitychange,可以用来判断浏览器页签是否显示。

有方法了,就写呗

export default {
created() {
window.addEventListener('visibilitychange', this.$_hanldeVisiblityChange)
// 此处用了hookEvent,可以参考小编前一篇文章
this.$on('hook:beforeDestroy', () => {
window.removeEventListener(
'visibilitychange',
this.$_hanldeVisiblityChange
)
})
},
methods: {
$_hanldeVisiblityChange() {
if (document.visibilityState === 'hidden') {
// 停掉那一堆东西
}
if (document.visibilityState === 'visible') {
// 开启那一堆东西
}
}
}
}

通过上面的代码,可以看到在每一个需要监听处理的文件都要写一堆事件监听,判断页面是否显示的代码,一处两处还可以,文件多了就头疼了,这时候小编突发奇想,定义一个页面显示隐藏的生命周期钩子,把这些判断都封装起来

自定义生命周期钩子函数

定义生命周期函数 pageHidden 与 pageVisible

import Vue from 'vue'

// 通知所有组件页面状态发生了变化
const notifyVisibilityChange = (lifeCycleName, vm) => {
// 生命周期函数会存在$options中,通过$options[lifeCycleName]获取生命周期
const lifeCycles = vm.$options[lifeCycleName]
// 因为使用了created的合并策略,所以是一个数组
if (lifeCycles && lifeCycles.length) {
// 遍历 lifeCycleName对应的生命周期函数列表,依次执行
lifeCycles.forEach(lifecycle => {
lifecycle.call(vm)
})
}
// 遍历所有的子组件,然后依次递归执行
if (vm.$children && vm.$children.length) {
vm.$children.forEach(child => {
notifyVisibilityChange(lifeCycleName, child)
})
}
} // 添加生命周期函数
export function init() {
const optionMergeStrategies = Vue.config.optionMergeStrategies
// 定义了两个生命周期函数 pageVisible, pageHidden
// 为什么要赋值为 optionMergeStrategies.created呢
// 这个相当于指定 pageVisible, pageHidden 的合并策略与 created的相同(其他生命周期函数都一样)
optionMergeStrategies.pageVisible = optionMergeStrategies.beforeCreate
optionMergeStrategies.pageHidden = optionMergeStrategies.created
} // 将事件变化绑定到根节点上面
// rootVm vue根节点实例
export function bind(rootVm) {
window.addEventListener('visibilitychange', () => {
// 判断调用哪个生命周期函数
let lifeCycleName = undefined
if (document.visibilityState === 'hidden') {
lifeCycleName = 'pageHidden'
} else if (document.visibilityState === 'visible') {
lifeCycleName = 'pageVisible'
}
if (lifeCycleName) {
// 通过所有组件生命周期发生变化了
notifyVisibilityChange(lifeCycleName, rootVm)
}
})
}

应用

  1. main.js主入口文件引入
import { init, bind } from './utils/custom-life-cycle'

// 初始化生命周期函数, 必须在Vue实例化之前确定合并策略
init() const vm = new Vue({
router,
render: h => h(App)
}).$mount('#app') // 将rootVm 绑定到生命周期函数监听里面
bind(vm)

  2. 在需要的地方监听生命周期函数

export default {
pageVisible() {
console.log('页面显示出来了')
},
pageHidden() {
console.log('页面隐藏了')
}
}

  

provideinject,不止父子传值,祖宗传值也可以

Vue相关的面试经常会被面试官问道,Vue父子之间传值的方式有哪些,通常我们会回答,props传值,$emit事件传值,vuex传值,还有eventbus传值等等,今天再加一种provideinject传值,离offer又近了一步。(对了,下一节还有一种)

使用过React的同学都知道,在React中有一个上下文Context,组件可以通过Context向任意后代传值,而Vueprovideinject的作用于Context的作用基本一样

先举一个例子

使用过elemment-ui的同学一定对下面的代码感到熟悉

<template>
<el-form :model="formData" size="small">
<el-form-item label="姓名" prop="name">
<el-input v-model="formData.name" />
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input-number v-model="formData.age" />
</el-form-item>
<el-button>提交</el-button>
</el-form>
</template>
<script>
export default {
data() {
return {
formData: {
name: '',
age: 0
}
}
}
}
</script>

  

看了上面的代码,貌似没啥特殊的,天天写啊。在el-form上面我们指定了一个属性size="small",然后有没有发现表单里面的所有表单元素以及按钮的 size都变成了small,这个是怎么做到的?接下来我们自己手写一个表单模拟一下

自己手写一个表单

自定义表单custom-form.vue

<template>
<form class="custom-form">
<slot></slot>
</form>
</template>
<script>
export default {
props: {
// 控制表单元素的大小
size: {
type: String,
default: 'default',
// size 只能是下面的四个值
validator(value) {
return ['default', 'large', 'small', 'mini'].includes(value)
}
},
// 控制表单元素的禁用状态
disabled: {
type: Boolean,
default: false
}
},
// 通过provide将当前表单实例传递到所有后代组件中
provide() {
return {
customForm: this
}
}
}
</script>

  

在上面代码中,我们通过provide将当前组件的实例传递到后代组件中,provide是一个函数,函数返回的是一个对象

自定义表单项custom-form-item.vue

没有什么特殊的,只是加了一个label,element-ui更复杂一些

<template>
<div class="custom-form-item">
<label class="custom-form-item__label">{{ label }}</label>
<div class="custom-form-item__content">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
label: {
type: String,
default: ''
}
}
}
</script>

自定义输入框 custom-input.vue

<template>
<div
class="custom-input"
:class="[
`custom-input--${getSize}`,
getDisabled && `custom-input--disabled`
]"
>
<input class="custom-input__input" :value="value" @input="$_handleChange" />
</div>
</template>
<script>
/* eslint-disable vue/require-default-prop */
export default {
props: {
// 这里用了自定义v-model
value: {
type: String,
default: ''
},
size: {
type: String
},
disabled: {
type: Boolean
}
},
// 通过inject 将form组件注入的实例添加进来
inject: ['customForm'],
computed: {
// 通过计算组件获取组件的size, 如果当前组件传入,则使用当前组件的,否则是否form组件的
getSize() {
return this.size || this.customForm.size
},
// 组件是否禁用
getDisabled() {
const { disabled } = this
if (disabled !== undefined) {
return disabled
}
return this.customForm.disabled
}
},
methods: {
// 自定义v-model
$_handleChange(e) {
this.$emit('input', e.target.value)
}
}
}
</script>

  

form中,我们通过provide返回了一个对象,在input中,我们可以通过inject获取form中返回对象中的项,如上代码inject:['customForm']所示,然后就可以在组件内通过this.customForm调用form实例上面的属性与方法了

在项目中使用

<template>
<custom-form size="small">
<custom-form-item label="姓名">
<custom-input v-model="formData.name" />
</custom-form-item>
</custom-form>
</template>
<script>
import CustomForm from '../components/custom-form'
import CustomFormItem from '../components/custom-form-item'
import CustomInput from '../components/custom-input'
export default {
components: {
CustomForm,
CustomFormItem,
CustomInput
},
data() {
return {
formData: {
name: '',
age: 0
}
}
}
}
</script>

  执行上面代码,运行结果为:

<form class="custom-form">
<div class="custom-form-item">
<label class="custom-form-item__label">姓名</label>
<div class="custom-form-item__content">
<!--size=small已经添加到指定的位置了-->
<div class="custom-input custom-input--small">
<input class="custom-input__input">
</div>
</div>
</div>
</form>

  

通过上面的代码可以看到,input组件已经设置组件样式为custom-input--small

inject格式说明

除了上面代码中所使用的inject:['customForm']写法之外,inject还可以是一个对象。且可以指定默认值

修改上例,如果custom-input外部没有custom-form,则不会注入customForm,此时为customForm指定默认值

{
inject: {
customForm: {
// 对于非原始值,和props一样,需要提供一个工厂方法
default: () => ({
size: 'default'
})
}
}
}

  

使用限制

1.provideinject的绑定不是可响应式的。但是,如果你传入的是一个可监听的对象,如上面的customForm: this,那么其对象的属性还是可响应的。

2.Vue官网建议provideinject 主要在开发高阶插件/组件库时使用。不推荐用于普通应用程序代码中。因为provideinject在代码中是不可追溯的(ctrl + f可以搜),建议可以使用Vuex代替。 但是,也不是说不能用,在局部功能有时候用了作用还是比较大的。

插槽,我要钻到你的怀里

插槽,相信每一位Vue都有使用过,但是如何更好的去理解插槽,如何去自定义插槽,今天小编为你带来更形象的说明。

默认插槽

<template>
<!--这是一个一居室-->
<div class="one-bedroom">
<!--添加一个默认插槽,用户可以在外部随意定义这个一居室的内容-->
<slot></slot>
</div>
</template>

  

<template>
<!--这里一居室-->
<one-bedroom>
<!--将家具放到房间里面,组件内部就是上面提供的默认插槽的空间-->
<span>先放一个小床,反正没有女朋友</span>
<span>再放一个电脑桌,在家还要加班写bug</span>
</one-bedroom>
</template>
<script>
import OneBedroom from '../components/one-bedroom'
export default {
components: {
OneBedroom
}
}
</script>

具名插槽

<template>
<div class="two-bedroom">
<!--这是主卧-->
<div class="master-bedroom">
<!---主卧使用默认插槽-->
<slot></slot>
</div>
<!--这是次卧-->
<div class="secondary-bedroom">
<!--次卧使用具名插槽-->
<slot name="secondard"></slot>
</div>
</div>
</template>

  

<template>
<two-bedroom>
<!--主卧使用默认插槽-->
<div>
<span>放一个大床,要结婚了,嘿嘿嘿</span>
<span>放一个衣柜,老婆的衣服太多了</span>
<span>算了,还是放一个电脑桌吧,还要写bug</span>
</div>
<!--次卧,通过v-slot:secondard 可以指定使用哪一个具名插槽, v-slot:secondard 也可以简写为 #secondard-->
<template v-slot:secondard>
<div>
<span>父母要住,放一个硬一点的床,软床对腰不好</span>
<span>放一个衣柜</span>
</div>
</template>
</two-bedroom>
</template>
<script>
import TwoBedroom from '../components/slot/two-bedroom'
export default {
components: {
TwoBedroom
}
}
</script>

作用域插槽

<template>
<div class="two-bedroom">
<!--其他内容省略-->
<div class="toilet">
<!--通过v-bind 可以向外传递参数, 告诉外面卫生间可以放洗衣机-->
<slot name="toilet" v-bind="{ washer: true }"></slot>
</div>
</div>
</template>

  

<template>
<two-bedroom>
<!--其他省略-->
<!--卫生间插槽,通过v-slot="scope"可以获取组件内部通过v-bind传的值-->
<template v-slot:toilet="scope">
<!--判断是否可以放洗衣机-->
<span v-if="scope.washer">这里放洗衣机</span>
</template>
</two-bedroom>
</template>  

插槽默认值

<template>
<div class="second-hand-house">
<div class="master-bedroom">
<!--插槽可以指定默认值,如果外部调用组件时没有修改插槽内容,则使用默认插槽-->
<slot>
<span>这里有一张水床,玩的够嗨</span>
<span>还有一个衣柜,有点旧了</span>
</slot>
</div>
<!--这是次卧-->
<div class="secondary-bedroom">
<!--次卧使用具名插槽-->
<slot name="secondard">
<span>这里有一张婴儿床</span>
</slot>
</div>
</div>
</template>

  

<second-hand-house>
<!--主卧使用默认插槽,只装修主卧-->
<div>
<span>放一个大床,要结婚了,嘿嘿嘿</span>
<span>放一个衣柜,老婆的衣服太多了</span>
<span>算了,还是放一个电脑桌吧,还要写bug</span>
</div>
</second-hand-house>

dispatchbroadcast,这是一种有历史的组件通信方式

dispatchbroadcast是一种有历史的组件通信方式,为什么是有历史的,因为他们是Vue1.0提供的一种方式,在Vue2.0中废弃了。但是废弃了不代表我们不能自己手动实现,像许多UI库内部都有实现。本文以element-ui实现为基础进行介绍。同时看完本节,你会对组件的$parent,$children,$options有所了解。

方法介绍

$dispatch: $dispatch会向上触发一个事件,同时传递要触发的祖先组件的名称与参数,当事件向上传递到对应的组件上时会触发组件上的事件侦听器,同时传播会停止。

$broadcast: $broadcast会向所有的后代组件传播一个事件,同时传递要触发的后代组件的名称与参数,当事件传递到对应的后代组件时,会触发组件上的事件侦听器,同时传播会停止(因为向下传递是树形的,所以只会停止其中一个叶子分支的传递)。

$dispatch实现与应用

1. 代码实现

 // 向上传播事件
// @param {*} eventName 事件名称
// @param {*} componentName 接收事件的组件名称
// @param {...any} params 传递的参数,可以有多个 function dispatch(eventName, componentName, ...params) {
// 如果没有$parent, 则取$root
let parent = this.$parent || this.$root
while (parent) {
// 组件的name存储在组件的$options.componentName 上面
const name = parent.$options.name
// 如果接收事件的组件是当前组件
if (name === componentName) {
// 通过当前组件上面的$emit触发事件,同事传递事件名称与参数
parent.$emit.apply(parent, [eventName, ...params])
break
} else {
// 否则继续向上判断
parent = parent.$parent
}
}
} // 导出一个对象,然后在需要用到的地方通过混入添加
export default {
methods: {
$dispatch: dispatch
}
}  

2. 代码应用

  • 在子组件中通过$dispatch向上触发事件

    import emitter from '../mixins/emitter'
    export default {
    name: 'Chart',
    // 通过混入将$dispatch加入进来
    mixins: [emitter],
    mounted() {
    // 在组件渲染完之后,将组件通过$dispatch将自己注册到Board组件上
    this.$dispatch('register', 'Board', this)
    }
    }
  • Board组件上通过$on监听要注册的事件

$broadcast实现与应用

1. 代码实现

  //向下传播事件
// @param {*} eventName 事件名称
// @param {*} componentName 要触发组件的名称
// @param {...any} params 传递的参数 function broadcast(eventName, componentName, ...params) {
this.$children.forEach(child => {
const name = child.$options.name
if (name === componentName) {
child.$emit.apply(child, [eventName, ...params])
} else {
broadcast.apply(child, [eventName, componentName, ...params])
}
})
} // 导出一个对象,然后在需要用到的地方通过混入添加
export default {
methods: {
$broadcast: broadcast
}
}  

2. 代码应用

在父组件中通过$broadcast向下触发事件

import emitter from '../mixins/emitter'
export default {
name: 'Board',
// 通过混入将$dispatch加入进来
mixins: [emitter],
methods:{
//在需要的时候,刷新组件
$_refreshChildren(params) {
this.$broadcast('refresh', 'Chart', params)
}
}
}

在后代组件中通过$on监听刷新事件

export default {
name: 'Chart',
created() {
this.$on('refresh',(params) => {
// 刷新事件
})
}
}

总结

通过上面的例子,同学们应该都能对$dispatch$broadcast有所了解,但是为什么Vue2.0要放弃这两个方法呢?官方给出的解释是:”因为基于组件树结构的事件流方式实在是让人难以理解,并且在组件结构扩展的过程中会变得越来越脆弱。这种事件方式确实不太好,我们也不希望在以后让开发者们太痛苦。并且 $dispatch$broadcast 也没有解决兄弟组件间的通信问题。“

确实如官网所说,这种事件流的方式确实不容易让人理解,而且后期维护成本比较高。但是在小编看来,不管黑猫白猫,能抓老鼠的都是好猫,在许多特定的业务场景中,因为业务的复杂性,很有可能使用到这样的通信方式。但是使用归使用,但是不能滥用,小编一直就在项目中有使用。

最新的一波Vue实战技巧,不用则已,一用惊人的更多相关文章

  1. 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 二十一║Vue实战:开发环境搭建【详细版】

    缘起 哈喽大家好,兜兜转转终于来到了Vue实战环节,前边的 6 篇关于Vue基础文章我刚刚简单看了看,感觉写的还是不行呀,不是很系统,所以大家可能看上去比较累,还是得抽时间去润润色,修改修改语句和样式 ...

  2. vue的技巧代码

    转载:https://segmentfault.com/a/1190000014085613 第一招:化繁为简的Watchers 场景还原: created(){ this.fetchPostList ...

  3. Android实战技巧:深入解析AsyncTask

    AsyncTask的介绍及基本使用方法 关于AsyncTask的介绍和基本使用方法可以参考官方文档和Android实战技巧:多线程AsyncTask这里就不重复. AsyncTask引发的一个问题 上 ...

  4. webpack快速入门——实战技巧:优雅打包第三方类库

    下面说两种方法: 一. 1.引入jQuery,首先安装: cnpm install --save jquery 2.安装好后,在我们的entry.js中引入: import $ from 'jquer ...

  5. webpack快速入门——实战技巧:webpack优化黑技能

    1.抽离jquery,vue(多个第三方类库抽离) 修改入口文件(webpack.config.js中) entry: { entry: './src/entry.js', jquery:'jquer ...

  6. Unit04: 实战技巧 、 登录

    Unit04: 实战技巧 . 登录 使用注解方式简化Unit04的小程序. 1. 基于注解的springmvc应用程序的开发 (1)编程步骤 step1. 导包. step2. 添加spring配置文 ...

  7. Unity User Group 北京站图文报道:《Unity虚拟现实实战技巧》

    时间来到了盛夏,北京UUG活动也来到了第八期.本次活动的主题为<Unity虚拟现实实战技巧>,为此我们邀请了4位资深的行业大神.这次我们仍然在北京市海淀区丹棱街5号微软大厦举行活动,在这里 ...

  8. vue实战记录(五)- vue实现购物车功能之商品总金额计算和单选全选删除功能

    vue实战,一步步实现vue购物车功能的过程记录,课程与素材来自慕课网,自己搭建了express本地服务器来请求数据 作者:狐狸家的鱼 本文链接:vue实战-实现购物车功能(五) GitHub:sue ...

  9. vue实战记录(六)- vue实现购物车功能之地址列表选配

    vue实战,一步步实现vue购物车功能的过程记录,课程与素材来自慕课网,自己搭建了express本地服务器来请求数据 作者:狐狸家的鱼 本文链接:vue实战-实现购物车功能(六) GitHub:sue ...

随机推荐

  1. 树莓派4B获取IP地址的几种简易方法

    首先声明一下,使用的是Paspbian系统,其实其他系统和本文说的获取IP地址关系也不大. 1.当你有路由器,有PC客户端的情况,你把你的树莓派用网线将其连接起来.你可以借助这个软件,advanced ...

  2. Rocket - tilelink - AtomicAutomata

    https://mp.weixin.qq.com/s/O7VTHqpCFNJQi3EpucXkIw   简单介绍AtomicAutomata的实现.(细节问题太多,恕不完全表述.)   ​​   1. ...

  3. jchdl - GSL实例 - Mul(无符号数的乘法)

      这里实现最原始的阵列乘法,逐位相乘然后加到一起.   参考链接 https://github.com/wjcdx/jchdl/blob/edcc3e098d4f1cb21677e86e87a114 ...

  4. 洞悉MySQL底层架构:游走在缓冲与磁盘之间

    提起MySQL,其实网上已经有一大把教程了,为什么我还要写这篇文章呢,大概是因为网上很多网站都是比较零散,而且描述不够直观,不能系统对MySQL相关知识有一个系统的学习,导致不能形成知识体系.为此我撰 ...

  5. MyBatis(一) 概述与SQL定制、对象映射

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.MyBatis概述 1.mybatis简介 MyBatis 是支持定制化 SQL.存储过程以及高级映 ...

  6. Java实现蓝桥杯VIP 算法训练 阶乘末尾

    试题 算法训练 阶乘末尾 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 给定n和len,输出n!末尾len位. 输入格式 一行两个正整数n和len. 输出格式 一行一个字符串,表示 ...

  7. Java实现 蓝桥杯VIP 算法提高 交换Easy

    算法提高 交换Easy 时间限制:1.0s 内存限制:512.0MB 问题描述 给定N个整数组成的序列,每次交换当前第x个与第y个整数,要求输出最终的序列. 输入格式 第一行为序列的大小N(1< ...

  8. java实现手机尾号评分

    30年的改革开放,给中国带来了翻天覆地的变化.2011全年中国手机产量约为11.72亿部.手机已经成为百姓的基本日用品! 给手机选个好听又好记的号码可能是许多人的心愿.但号源有限,只能辅以有偿选号的方 ...

  9. Java实现花朵数

    一个N位的十进制正整数,如果它的每个位上的数字的N次方的和等于这个数本身,则称其为花朵数. 例如: 当N=3时,153就满足条件,因为 1^3 + 5^3 + 3^3 = 153,这样的数字也被称为水 ...

  10. PAT 旧键盘打字

    旧键盘上坏了几个键,于是在敲一段文字的时候,对应的字符就不会出现.现在给出应该输入的一段文字.以及坏掉的那些键,打出的结果文字会是怎样? 输入格式: 输入在 2 行中分别给出坏掉的那些键.以及应该输入 ...