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

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

在代码中打印

  1. console.log(Vue.config.optionMergeStrategies)

  

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

有方法了,就写呗

  1. export default {
  2. created() {
  3. window.addEventListener(\'visibilitychange\', this.$_hanldeVisiblityChange)
  4. // 此处用了hookEvent,可以参考小编前一篇文章
  5. this.$on(\'hook:beforeDestroy\', () => {
  6. window.removeEventListener(
  7. \'visibilitychange\',
  8. this.$_hanldeVisiblityChange
  9. )
  10. })
  11. },
  12. methods: {
  13. $_hanldeVisiblityChange() {
  14. if (document.visibilityState === \'hidden\') {
  15. // 停掉那一堆东西
  16. }
  17. if (document.visibilityState === \'visible\') {
  18. // 开启那一堆东西
  19. }
  20. }
  21. }
  22. }

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

定义生命周期函数 pageHidden 与 pageVisible

  1. import Vue from \'vue\'
  2.  
  3. // 通知所有组件页面状态发生了变化
  4. const notifyVisibilityChange = (lifeCycleName, vm) => {
  5. // 生命周期函数会存在$options中,通过$options[lifeCycleName]获取生命周期
  6. const lifeCycles = vm.$options[lifeCycleName]
  7. // 因为使用了created的合并策略,所以是一个数组
  8. if (lifeCycles && lifeCycles.length) {
  9. // 遍历 lifeCycleName对应的生命周期函数列表,依次执行
  10. lifeCycles.forEach(lifecycle => {
  11. lifecycle.call(vm)
  12. })
  13. }
  14. // 遍历所有的子组件,然后依次递归执行
  15. if (vm.$children && vm.$children.length) {
  16. vm.$children.forEach(child => {
  17. notifyVisibilityChange(lifeCycleName, child)
  18. })
  19. }
  20. }
  21.  
  22. // 添加生命周期函数
  23. export function init() {
  24. const optionMergeStrategies = Vue.config.optionMergeStrategies
  25. // 定义了两个生命周期函数 pageVisible, pageHidden
  26. // 为什么要赋值为 optionMergeStrategies.created呢
  27. // 这个相当于指定 pageVisible, pageHidden 的合并策略与 created的相同(其他生命周期函数都一样)
  28. optionMergeStrategies.pageVisible = optionMergeStrategies.beforeCreate
  29. optionMergeStrategies.pageHidden = optionMergeStrategies.created
  30. }
  31.  
  32.  
  33. // 将事件变化绑定到根节点上面
  34. // rootVm vue根节点实例
  35. export function bind(rootVm) {
  36. window.addEventListener(\'visibilitychange\', () => {
  37. // 判断调用哪个生命周期函数
  38. let lifeCycleName = undefined
  39. if (document.visibilityState === \'hidden\') {
  40. lifeCycleName = \'pageHidden\'
  41. } else if (document.visibilityState === \'visible\') {
  42. lifeCycleName = \'pageVisible\'
  43. }
  44. if (lifeCycleName) {
  45. // 通过所有组件生命周期发生变化了
  46. notifyVisibilityChange(lifeCycleName, rootVm)
  47. }
  48. })
  49. }
  1. main.js主入口文件引入
  1. import { init, bind } from \'./utils/custom-life-cycle\'
  2.  
  3. // 初始化生命周期函数, 必须在Vue实例化之前确定合并策略
  4. init()
  5.  
  6. const vm = new Vue({
  7. router,
  8. render: h => h(App)
  9. }).$mount(\'#app\')
  10.  
  11. // 将rootVm 绑定到生命周期函数监听里面
  12. bind(vm)

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

  1. export default {
  2. pageVisible() {
  3. console.log(\'页面显示出来了\')
  4. },
  5. pageHidden() {
  6. console.log(\'页面隐藏了\')
  7. }
  8. }

  

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

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

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

  1. <template>
  2. <el-form :model="formData" size="small">
  3. <el-form-item label="姓名" prop="name">
  4. <el-input v-model="formData.name" />
  5. </el-form-item>
  6. <el-form-item label="年龄" prop="age">
  7. <el-input-number v-model="formData.age" />
  8. </el-form-item>
  9. <el-button>提交</el-button>
  10. </el-form>
  11. </template>
  12. <script>
  13. export default {
  14. data() {
  15. return {
  16. formData: {
  17. name: \'\',
  18. age: 0
  19. }
  20. }
  21. }
  22. }
  23. </script>

  

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

  1. <template>
  2. <form class="custom-form">
  3. <slot></slot>
  4. </form>
  5. </template>
  6. <script>
  7. export default {
  8. props: {
  9. // 控制表单元素的大小
  10. size: {
  11. type: String,
  12. default: \'default\',
  13. // size 只能是下面的四个值
  14. validator(value) {
  15. return [\'default\', \'large\', \'small\', \'mini\'].includes(value)
  16. }
  17. },
  18. // 控制表单元素的禁用状态
  19. disabled: {
  20. type: Boolean,
  21. default: false
  22. }
  23. },
  24. // 通过provide将当前表单实例传递到所有后代组件中
  25. provide() {
  26. return {
  27. customForm: this
  28. }
  29. }
  30. }
  31. </script>

  

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

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

  1. <template>
  2. <div class="custom-form-item">
  3. <label class="custom-form-item__label">{{ label }}</label>
  4. <div class="custom-form-item__content">
  5. <slot></slot>
  6. </div>
  7. </div>
  8. </template>
  9. <script>
  10. export default {
  11. props: {
  12. label: {
  13. type: String,
  14. default: \'\'
  15. }
  16. }
  17. }
  18. </script>
  1. <template>
  2. <div
  3. class="custom-input"
  4. :class="[
  5. `custom-input--${getSize}`,
  6. getDisabled && `custom-input--disabled`
  7. ]"
  8. >
  9. <input class="custom-input__input" :value="value" @input="$_handleChange" />
  10. </div>
  11. </template>
  12. <script>
  13. /* eslint-disable vue/require-default-prop */
  14. export default {
  15. props: {
  16. // 这里用了自定义v-model
  17. value: {
  18. type: String,
  19. default: \'\'
  20. },
  21. size: {
  22. type: String
  23. },
  24. disabled: {
  25. type: Boolean
  26. }
  27. },
  28. // 通过inject 将form组件注入的实例添加进来
  29. inject: [\'customForm\'],
  30. computed: {
  31. // 通过计算组件获取组件的size, 如果当前组件传入,则使用当前组件的,否则是否form组件的
  32. getSize() {
  33. return this.size || this.customForm.size
  34. },
  35. // 组件是否禁用
  36. getDisabled() {
  37. const { disabled } = this
  38. if (disabled !== undefined) {
  39. return disabled
  40. }
  41. return this.customForm.disabled
  42. }
  43. },
  44. methods: {
  45. // 自定义v-model
  46. $_handleChange(e) {
  47. this.$emit(\'input\', e.target.value)
  48. }
  49. }
  50. }
  51. </script>

  

form中,我们通过provide返回了一个对象,在input中,我们可以通过inject获取form中返回对象中的项,如上代码inject:[\'customForm\']所示,然后就可以在组件内通过this.customForm调用form实例上面的属性与方法了
  1. <template>
  2. <custom-form size="small">
  3. <custom-form-item label="姓名">
  4. <custom-input v-model="formData.name" />
  5. </custom-form-item>
  6. </custom-form>
  7. </template>
  8. <script>
  9. import CustomForm from \'../components/custom-form\'
  10. import CustomFormItem from \'../components/custom-form-item\'
  11. import CustomInput from \'../components/custom-input\'
  12. export default {
  13. components: {
  14. CustomForm,
  15. CustomFormItem,
  16. CustomInput
  17. },
  18. data() {
  19. return {
  20. formData: {
  21. name: \'\',
  22. age: 0
  23. }
  24. }
  25. }
  26. }
  27. </script>

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

  1. <form class="custom-form">
  2. <div class="custom-form-item">
  3. <label class="custom-form-item__label">姓名</label>
  4. <div class="custom-form-item__content">
  5. <!--size=small已经添加到指定的位置了-->
  6. <div class="custom-input custom-input--small">
  7. <input class="custom-input__input">
  8. </div>
  9. </div>
  10. </div>
  11. </form>

  

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

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

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

  1. {
  2. inject: {
  3. customForm: {
  4. // 对于非原始值,和props一样,需要提供一个工厂方法
  5. default: () => ({
  6. size: \'default\'
  7. })
  8. }
  9. }
  10. }

  

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

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

  

  1. <template>
  2. <!--这里一居室-->
  3. <one-bedroom>
  4. <!--将家具放到房间里面,组件内部就是上面提供的默认插槽的空间-->
  5. <span>先放一个小床,反正没有女朋友</span>
  6. <span>再放一个电脑桌,在家还要加班写bug</span>
  7. </one-bedroom>
  8. </template>
  9. <script>
  10. import OneBedroom from \'../components/one-bedroom\'
  11. export default {
  12. components: {
  13. OneBedroom
  14. }
  15. }
  16. </script>
  1. <template>
  2. <div class="two-bedroom">
  3. <!--这是主卧-->
  4. <div class="master-bedroom">
  5. <!---主卧使用默认插槽-->
  6. <slot></slot>
  7. </div>
  8. <!--这是次卧-->
  9. <div class="secondary-bedroom">
  10. <!--次卧使用具名插槽-->
  11. <slot name="secondard"></slot>
  12. </div>
  13. </div>
  14. </template>

  

  1. <template>
  2. <two-bedroom>
  3. <!--主卧使用默认插槽-->
  4. <div>
  5. <span>放一个大床,要结婚了,嘿嘿嘿</span>
  6. <span>放一个衣柜,老婆的衣服太多了</span>
  7. <span>算了,还是放一个电脑桌吧,还要写bug</span>
  8. </div>
  9. <!--次卧,通过v-slot:secondard 可以指定使用哪一个具名插槽, v-slot:secondard 也可以简写为 #secondard-->
  10. <template v-slot:secondard>
  11. <div>
  12. <span>父母要住,放一个硬一点的床,软床对腰不好</span>
  13. <span>放一个衣柜</span>
  14. </div>
  15. </template>
  16. </two-bedroom>
  17. </template>
  18. <script>
  19. import TwoBedroom from \'../components/slot/two-bedroom\'
  20. export default {
  21. components: {
  22. TwoBedroom
  23. }
  24. }
  25. </script>
  1. <template>
  2. <div class="two-bedroom">
  3. <!--其他内容省略-->
  4. <div class="toilet">
  5. <!--通过v-bind 可以向外传递参数, 告诉外面卫生间可以放洗衣机-->
  6. <slot name="toilet" v-bind="{ washer: true }"></slot>
  7. </div>
  8. </div>
  9. </template>

  

  1. <template>
  2. <two-bedroom>
  3. <!--其他省略-->
  4. <!--卫生间插槽,通过v-slot="scope"可以获取组件内部通过v-bind传的值-->
  5. <template v-slot:toilet="scope">
  6. <!--判断是否可以放洗衣机-->
  7. <span v-if="scope.washer">这里放洗衣机</span>
  8. </template>
  9. </two-bedroom>
  10. </template>  
  1. <template>
  2. <div class="second-hand-house">
  3. <div class="master-bedroom">
  4. <!--插槽可以指定默认值,如果外部调用组件时没有修改插槽内容,则使用默认插槽-->
  5. <slot>
  6. <span>这里有一张水床,玩的够嗨</span>
  7. <span>还有一个衣柜,有点旧了</span>
  8. </slot>
  9. </div>
  10. <!--这是次卧-->
  11. <div class="secondary-bedroom">
  12. <!--次卧使用具名插槽-->
  13. <slot name="secondard">
  14. <span>这里有一张婴儿床</span>
  15. </slot>
  16. </div>
  17. </div>
  18. </template>

  

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

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

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

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

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

  1. // 向上传播事件
  2. // @param {*} eventName 事件名称
  3. // @param {*} componentName 接收事件的组件名称
  4. // @param {...any} params 传递的参数,可以有多个
  5. function dispatch(eventName, componentName, ...params) {
  6. // 如果没有$parent, 则取$root
  7. let parent = this.$parent || this.$root
  8. while (parent) {
  9. // 组件的name存储在组件的$options.componentName 上面
  10. const name = parent.$options.name
  11. // 如果接收事件的组件是当前组件
  12. if (name === componentName) {
  13. // 通过当前组件上面的$emit触发事件,同事传递事件名称与参数
  14. parent.$emit.apply(parent, [eventName, ...params])
  15. break
  16. } else {
  17. // 否则继续向上判断
  18. parent = parent.$parent
  19. }
  20. }
  21. }
  22.  
  23. // 导出一个对象,然后在需要用到的地方通过混入添加
  24. export default {
  25. methods: {
  26. $dispatch: dispatch
  27. }
  28. }  
  • 在子组件中通过$dispatch向上触发事件

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

  1. //向下传播事件
  2. // @param {*} eventName 事件名称
  3. // @param {*} componentName 要触发组件的名称
  4. // @param {...any} params 传递的参数
  5. function broadcast(eventName, componentName, ...params) {
  6. this.$children.forEach(child => {
  7. const name = child.$options.name
  8. if (name === componentName) {
  9. child.$emit.apply(child, [eventName, ...params])
  10. } else {
  11. broadcast.apply(child, [eventName, componentName, ...params])
  12. }
  13. })
  14. }
  15.  
  16. // 导出一个对象,然后在需要用到的地方通过混入添加
  17. export default {
  18. methods: {
  19. $broadcast: broadcast
  20. }
  21. }  

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

  1. import emitter from \'../mixins/emitter\'
  2. export default {
  3. name: \'Board\',
  4. // 通过混入将$dispatch加入进来
  5. mixins: [emitter],
  6. methods:{
  7. //在需要的时候,刷新组件
  8. $_refreshChildren(params) {
  9. this.$broadcast(\'refresh\', \'Chart\', params)
  10. }
  11. }
  12. }

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

  1. export default {
  2. name: \'Chart\',
  3. created() {
  4. this.$on(\'refresh\',(params) => {
  5. // 刷新事件
  6. })
  7. }
  8. }

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

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

 

版权声明:本文为songyao666原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/songyao666/p/13201784.html