1x5 精读Vue官方文档 - 处理边界情况

发布于 2022年 01月 11日 19:18

精读 Vue 官方文档系列 🎉

访问元素 & 组件

$root

访问根组件实例

$parent

获取父组件实例,支持多次调用,获取连续多层父级的实例。

ref & $refs

获取子组件的实例

<children ref="child" />

获取 DOM 元素的对象引用。

<input type="text" ref="input" />

最终,我们可以在组件的 $refs 属性中访问这些 ref 的对应的引用。

mountd(){
    this.$refs.child.input.foucs();
}

refv-for 一起使用的时候,你得到的 ref 将会是一个包含了对应数据源的这些子组件的数组。

依赖注入

使用 provide 提供依赖,再使用 inject 注入依赖。

Provider

provide: function(){
    return {
        getMapData: this.getData
    }
}

Consumer

inject:['getMapData']

这对组件选项必须要一起使用。以允许一个祖先组件向其所有后代组件注入一个依赖,不论组件的层级有多深。

provide 的值是一个对象或者是返回一个对象的函数。对象的 key 便是向子孙组件中注入的依赖。 inject 的值是一个字符串数组,或者是一个对象选项。这些数组元素或者是对象的 key 都是对应的都是 provide 注入的依赖。

最基本的形式:

Vue.extend({
  name: "Parent",
  provide: {
    foo: "bar",
  },
});

Vue.extend({
  name: "Son",
  inject: ["foo"],
  created() {
    console.log(this.foo);
  },
});

provide 的值为一个函数,inject 的值是一个对象选项:

Vue.extend({
  name: "Parent",
  provide() {
    return {
      foo: "bar",
    };
  },
});

Vue.extend({
  name: "Son",
  inject: {
    bar: {
      from: "foo",
      default: "new bar",
    },
  },
});

如果 inject 的值是一个对象,那么from 便可以指定来源,default 用来设置依赖的默认值。但是当你的依赖默认值不是一个基本类型时,必须要使用一个工厂方法来返回这个值。

{
  inject: {
    foo: {
      from: 'bar',
      default: () => [1, 2, 3]
    }
  }
}

最后,因为 provide/inject 的初始化优先于 props,data ,所以我们便可以用依赖注入来初始化它们的默认值。

Vue.extend({
  inject: ["foo"],
  props: {
    bar: {
      type: String,
      default: () => this.foo,
    },
  },
  data() {
    return {
      copy_bar: this.foo,
    };
  },
});

依赖注入解决了什么问题?

  1. 解决了 $parent 无法很好的扩展到更深层级的嵌套组件上。
  2. 给任意的后代提供数据或方法,免去了多层的 Prop 传递。
  3. 任意的后代也不需要关心 property 是从何处注入。

依赖注入的应用场景:

  1. 编写一个固定组件集,存在一个根组件和多层的子组件,且结构固定。
  2. 一个局部的数据中心化。

依赖注入的负面影响:

  1. 注入的数据或方法基于设计的考量不具有响应式的特性。
  2. 注入的数据虽然不会被处理为响应式数据,但是并不阻止依赖注入一个本是响应式的数据。
  3. 会将应用程序中相关的组件(使用依赖注入的组件)紧密的耦合在一起,使得重构变得比较困难(这是依赖注入的本质所决定的)。

相对于依赖注入的银弹 —— vuex,提供一个第三方独立的数据中心,并具有响应式更新功能。

程序化的事件监听器

程序化的事件监听器是建立在 Vue 系统上的,它区别于浏览器的事件系统(EventTarget API)。 通常我们使用 v-on 在组件上监听事件,使用 $emit() 在组件内触发事件,而 “程序化的事件监听器” 提供了一个可以在组件实例上监听事件的功能。

  • $on('eventName', eventHandler) 绑定/侦听一个事件。
  • $off('eventName, eventHandler') 解绑/停止侦听一个事件。
  • $once(eventName, eventHandler) 一次性绑定/监听一个事件。

善用 $once 可以提高我们解决组件事件清理的麻烦。

Bad

// 一次性将这个日期选择器附加到一个输入框上
// 它会被挂载到 DOM 上。
mounted: function () {
  // Pikaday 是一个第三方日期选择器的库
  this.picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })
},
// 在组件被销毁之前,
// 也销毁这个日期选择器。
beforeDestroy: function () {
  this.picker.destroy()
}

Good

mounted: function () {
  var picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })

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

我们还可以把这些操作封装成方法,便于多次执行相同的操作。

递归组件

所谓“递归”就是自己调自己,同理组件递归就是组件不断的调用自身。

递归组件的入口:

<x-menu :menus="menus"></x-menu>

递归组件的定义:

Vue.component('x-menu', {
    props:{
        menus:{
            requried:true,
            type:Array
        }
    },
    template:`
        <ul>
            <li v-for="menu in menus" :key="menu.key">
                <div>{{ menu.name }}</div>
                <!--核心所在-->
                <x-menus v-if="menu.children"></x-menus>
            </li>
        </ul>
    `
});

循环引用

与递归组件不同的是,递归是通过不断调用自己来实现所需的功能。而循环引用只会存在两个组件之间,A 组件使用了 B 组件,B 组件也使用了 A 组件。

如果你是通过 Vue.component 注册的全局组件,则不存在此种情况。

通常这种问题只会出现在使用模块化系统来引入具有相互引用关系的组件。

解决的办法有两种:

1. 手动注册其中的一个组件

beforeCreate: function(){
    this.$options.components.ComponetB = import('./components/componentB').default;
}

2. 使用异步组件

{
    components:{
        'component-b': ()=>import('./components/componentB')
    }
}

模板定义的替代品

我们更多的推荐使用单文件组件 SFC 的方式定义模板或者在 template 属性中编写字符串模板,并不建议将组件的模板与逻辑相分离。

内联模板

inline-template 这个特殊的 attribute 出现在一个子组件上时,这个组件将会使用其里面的内容作为模板,而不是将其作为被分发的内容(不在是理解中 slot 的性质)。这使得模板的撰写工作更加灵活。

内联模板需要定义在 Vue 所属的 DOM 元素内。

<my-component inline-template>
  <div>
    <p>These are compiled as the component's own template.</p>
    <p>Not parent's transclusion content.</p>
  </div>
</my-component>

其实就是自定义组件起始与结束标签的内容将会作为该自定义组件的模板,因此不会传递给 <slot> 元素,这会导致组件的模板与逻辑分离,通常不建议使用。

X-Template

与内联模板相比就是使用具有特殊 type 值与 id 属性的 <script> 标签来存储模板内容。

x-template 需要定义在 Vue 所属的 DOM 元素外。

<script type="text/x-template" id="hello-world-template">
  <p>Hello hello hello</p>
</script>

id 的作用就是将模板内容与模板逻辑进行连接的凭据。

Vue.component('hello-world', {
  template: '#hello-world-template'
})

X-Template 的方式也会导致组件的模板与逻辑相分离,所以也不建议使用。

强制更新

调用 this.$forceUpdate() 可以强制更新组件。

通过 v-once 创建低开销的静态组件

当组件包含了大量静态内容时。我们可以在根元素上添加 v-once attribute 以确保这些内容只计算一次然后缓存起来,提供组件的渲染速度。

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

推荐文章