【vue学习】处理边界情况

2019-07-26  本文已影响0人  前端菜篮子
image

访问元素 & 组件

在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。不过也确实在一些情况下做这些事情是合适的。

$root

$parent

<!-- parent -->
<template>
    <div>
        <h3>我是爹爹,哈哈哈</h3>
        <slot></slot>
    </div>
</template>
<script>
export default {
    data(){
        return {
            parentText:'text999999999'
        }
    },
    mounted(){
        console.log(this.$children, "children")
    }
}
</script>

<!-- child -->
<template>
    <div>
        我是娃...
    </div>
</template>
<script>
export default {
    data(){
        return {
            childText:'childtttttttt'
        }
    },
    mounted(){
        console.log(this.$parent, "parent")
    }
}
</script>

child组件被parent组件包裹

image
打印结果:
image
image
child组件被div包裹(div包裹几层都一样:应该原生的都不算吧?)
image
image
image

在一些可能适当的时候,你需要特别地共享一些组件库。举个例子,在和 JavaScript API 进行交互而不渲染 HTML 的抽象组件内,诸如这些假设性的 Google 地图组件一样:

<google-map>
  <google-map-markers v-bind:places="iceCreamShops">
  </google-map-markers>
</google-map>

这个 <google-map> 组件可以定义一个 map 属性,所有的子组件都需要访问它。在这种情况下 <google-map-markers> 可能想要通过类似 this.$parent.getMap 的方式访问那个地图,以便为其添加一组标记。

哈哈哈,然后可能有小伙伴就会用到this.$parent.$parent.map(孙子访问爷爷),然后就失控了

$refs

我们自己的项目中,这个貌似用的比较多

image

$refs 只会在组件渲染完成之后生效(后面$nextTick单独一篇介绍下),并且它们不是响应式的。这仅作为一个用于直接操作子组件的【逃生舱】—— 你应该避免在模板或计算属性中访问 $refs

provide&inject

上面$parent一节讲到过this.$parent.$parent.map(孙子访问爷爷),失控了。
那如果真有这种需求呢?这就是依赖注入的用武之地,它用到了两个新的实例选项:provideinject

provide: function () {
  return {
    getMap: this.getMap
  }
}
// 或者
provide: {
    foo: 'bar'
}
一个字符串数组,或
一个对象,对象的 key 是本地的绑定名,value 是:
    在可用的注入内容中搜索用的 key (字符串或 Symbol),或
    一个对象,该对象的:
        from 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol)
        default 属性是降级情况下使用的 value
        
inject: ['getMap']
//或
inject: {
    simpleIndex:{from:'simpleIndex', default:1000 }
}
//父组件 provide
@Provide()
public componentActivity = this.getProvide()
private getProvide() {
    return 'aaaaaa'
}
// 或者
@Provide()
public componentActivity = {name:'aaaaaa'}


//后代组件 Inject
@Inject()
private componentActivity: string
private created() {
   console.info(this.componentActivity)   // 'aaaaaa'
}

程序化的事件侦听器

你通常不会用到这些,但是当你需要在一个组件实例上手动侦听事件时,它们是派得上用场的

官方案例

  1. 你可能经常看到这种集成一个第三方库的模式:
// 一次性将这个日期选择器附加到一个输入框上
// 它会被挂载到 DOM 上。
mounted: function () {
  // Pikaday 是一个第三方日期选择器的库
  this.picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })
},
// 在组件被销毁之前,
// 也销毁这个日期选择器。
beforeDestroy: function () {
  this.picker.destroy()
}
  1. 这里有两个潜在的问题:
mounted: function () {
  var picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })

  this.$once('hook:beforeDestroy', function () {
    picker.destroy()
  })
}
  1. 使用了这个策略,我甚至可以让多个输入框元素同时使用不同的 Pikaday,每个新的实例都程序化地在后期清理它自己:
mounted: function () {
  this.attachDatepicker('startDateInput')
  this.attachDatepicker('endDateInput')
},
methods: {
  attachDatepicker: function (refName) {
    var picker = new Pikaday({
      field: this.$refs[refName],
      format: 'YYYY-MM-DD'
    })

    this.$once('hook:beforeDestroy', function () {
      picker.destroy()
    })
  }
}

$on & $off &once【vue事件总线EventBus了解下】

  1. 该章节学习自: vue篇之事件总线--简书:程序汪

  2. EventBus(事件总线)的简介:Vue中可以来作为事件的沟通桥梁,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件;但也就是太方便所以若使用不慎,就会造成难以维护的灾难,因此才需要更完善的Vuex作为状态管理中心,将通知的概念上升到共享状态层次。

  3. 如何使用EventBus

/**单独一个js文件event-bus.js,
   定义一个变量EventBus
   局部定义
*/
/**实质上它是一个不具备 DOM 的组件,
   它具有的仅仅只是它实例方法而已,
   因此它非常的轻便
*/
import Vue from 'vue'
export const EventBus = new Vue()
/**或者直接在在main.js中初始化也可以
(这是个全局的定义,后面的具体案例先不管这个)
*/
Vue.prototype.$EventBus = new Vue()
<!--在这两个方法中,通过 
EventBus.$emit(channel: string, callback(payload1,…)) 
监听 decreased (和 incremented )频道。
-->
<!-- DecreaseCount.vue -->
<template>
    <button @click="decrease()">-</button>
</template>
<script> import { EventBus } from "../event-bus.js";
    export default {
        name: "DecreaseCount",
        data() {
            return {
                num: 1,
                deg:180
            };
        },
        methods: {
            decrease() {
                EventBus.$emit("decreased", {
                    num:this.num,
                    deg:this.deg
                });
            }
        }
    }; 
</script>

<!-- IncrementCount.vue -->
<template>
    <button @click="increment()">+</button>
</template>
<script> import { EventBus } from "../event-bus.js";
    export default {
        name: "IncrementCount",
        data() {
            return {
                num: 1,
                deg:180
            };
        },
        methods: {
            increment() {
                EventBus.$emit("incremented", {
                    num:this.num,
                    deg:this.deg
                });
            }
        }
    };
 </script>
 <!-- 
 上面的示例,在 DecreaseCount 和 IncrementCount 
 分别发送出了 decreased 和 incremented频道。
 接下来,我们需要在另一个组件中接收这两个事件,
 保持数据在各组件之间的通讯。
 -->
<!-- App.vue -->
<template>
    <div id="app">
        <div class="container" :style="{transform: 'rotateY(' + degValue + 'deg)'}">
            <div class="front">
                <div class="increment">
                    <IncrementCount />
                </div>
                <div class="show-front"> {{fontCount}} </div>
                <div class="decrement">
                    <DecreaseCount />
                </div>
            </div>

            <div class="back">
                <div class="increment">
                    <IncrementCount />
                </div>
                <div class="show-back"> {{backCount}} </div>
                <div class="decrement">
                    <DecreaseCount />
                </div>
            </div> 
        </div>
    </div>
</template>

<script>
    import IncrementCount from "./components/IncrementCount";
    import DecreaseCount from "./components/DecreaseCount";
    import { EventBus } from "./event-bus.js";
    export default {
        name: "App",
        components: {
            IncrementCount,
            DecreaseCount
        },
        data() {
            return {
                degValue:0,
                fontCount:0,
                backCount:0
            };
        },
        mounted() {
            EventBus.$on("incremented", ({num,deg}) => {
                this.fontCount += num
                this.$nextTick(()=>{
                    this.backCount += num
                    this.degValue += deg;
                })
            });
            EventBus.$on("decreased", ({num,deg}) => {
                this.fontCount -= num
                this.$nextTick(()=>{
                    this.backCount -= num
                    this.degValue -= deg;
                })
            });
        }
    }; 
</script>
import { eventBus } from './event-bus.js'
EventBus.$off('decreased', {})
  1. 全局的 EventBus

全局EventBus,虽然在某些示例中不提倡使用,但它是一种非常漂亮且简单的方法,可以跨组件之间共享数据。它的工作原理是发布/订阅方法,通常称为 Pub/Sub

image
我们从上图中可以得出以下几点:
a.有一个全局EventBus
b.所有事件都订阅它
c.所有组件也发布到它,订阅组件获得更新

总结一下:
a.所有组件都能够将事件发布到总线,
b.然后总线由另一个组件订阅,
c.然后订阅它的组件将得到更新
var EventBus = new Vue();
Object.defineProperties(Vue.prototype, {
    $bus: {
        get: function () {
            return EventBus
        }
    }
})
/**
现在,这个特定的总线使用两个方法 $on 和 $emit 一个用于创建发出的事件,它就是$emit 
另一个用于订阅 $on 
*/
this.$bus.$emit('nameOfEvent',{ ... pass some event data ...});

this.$bus.$on('nameOfEvent',($event) => {
    // ...
})

现在我们创建两个简单的组件:一个 ShowMessage 的组件用来显示信息,另外创建一个 UpdateMessage 的组件,用来更新信息。

<!-- UpdateMessage.vue -->
<template>
    <div class="form">
        <div class="form-control">
            <input v-model="message" >
            <button @click="updateMessage()">更新消息</button>
        </div>
    </div>
</template>
<script>
export default {
        name: "UpdateMessage",
        data() {
            return {
                message: "这是一条消息"
            };
        },
        methods: {
            updateMessage() {
                this.$bus.$emit("updateMessage", this.message);
            }
        },
        beforeDestroy () {
            $this.$bus.$off('updateMessage')
        }
    };
 </script>
<!-- ShowMessage.vue -->
<template>
    <div class="message">
        <h1>{{ message }}</h1>
    </div>
</template>

<script> 
export default {
        name: "ShowMessage",
        data() {
            return {
                message: "我是一条消息"
            };
        },
        created() {
            var self = this
            this.$bus.$on('updateMessage', function(value) {
                self.updateMessage(value);
            })
        },
        methods: {
            updateMessage(value) {
                this.message = value
            }
        }
    }; 
</script>
  1. EventBus注册在全局上时,路由切换时会重复触发事件
created() {
    this.bus.$off('clickBus');
    //在每次创建事件之前,手动清除定义的事件
    //根据实际的业务需求也可以在beforeDestroy()和destroyed()
},
mounted(){
    this.bus.$on('clickBus', (e) => {})
}

$once【定时器销毁案例看下】

  1. 这个首先看下官方案例,即【程序化的事件侦听器】刚开始讲到的那个
  2. 方案1:


    image

    该方案有两点不好的地方,引用尤大的话来说就是:

  1. 方案2(同官网案例)


    image

循环引用

递归组件(自己调自己)

组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事(动态组件和异步组件一章中我们提起过name的作用):

name: 'unique-name-of-my-component'

当你使用 Vue.component 全局注册一个组件时,这个全局的 ID 会自动设置为该组件的 name 选项。

Vue.component('unique-name-of-my-component', {
  // ...
})

稍有不慎,递归组件就可能导致无限循环:

name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'

A组件与B组件相互调用

这个我们也在动态组件和异步组件的“异步组件”中讲到了
组件之间的循环引用

模板定义的替代品

A. 内联模板inline-template

Vue 学习笔记 — inline-template

定义一个私有子组件时,如果子组件的template过长会使得代码非常难以阅读


image

这时可以使用内联模版


image

不过,inline-template 会让模板的作用域变得更加难以理解。所以作为最佳实践,请在组件内优先选择 template 选项或 .vue 文件里的一个 <template> 元素来定义模板。

B. <script type="text/x-template">

这个的意义应该和inline-template差不多,用法如下图:

image

控制更新

Vue响应式系统,它始终知道何时进行更新 (如果你用对了的话)。不过还是有一些边界情况,你想要强制更新($forceUpdate),尽管表面上看响应式的数据没有发生改变。也有一些情况是你想阻止不必要的更新(v-once)。

强制更新$forceUpdate

如果你发现你自己需要在 Vue 中做一次强制更新,99.9% 的情况,是你在某个地方做错了事。

低开销静态组件v-once

渲染普通的 HTML 元素在 Vue 中是非常快速的,但有的时候你可能有一个组件,这个组件包含了大量静态内容。在这种情况下,你可以在根元素上添加 v-once 特性以确保这些内容只计算一次然后缓存起来,就像这样:

Vue.component('terms-of-service', {
  template: `
    <div v-once>
      <h1>Terms of Service</h1>
      ... a lot of static content ...
    </div>
  `
})

啥也不多说了:不要过度使用这个模式。例如,设想另一个开发者并不熟悉 v-once 或漏看了它在模板中,他们可能会花很多个小时去找出模板为什么无法正确更新

总结一句

边界情况这块内容挺多的,但是基本上实际开发应用中都不咋用

上一篇下一篇

猜你喜欢

热点阅读