Vue: 进阶构造属性

之前在Vue: 构造选项 里罗列了options 的五类属性,今天就把剩下的关于资源和组合的属性说一说。

directives、 mixins、 extends、 provide、 inject

directives - 指令

自定义指令

  • 我们已经学了一些内置指令

v-ifv-forv-showv-html

  • 今天学习如何自己造一个指令

目标: 造出v-x, 点击即出现一个x

两种写法

  • 声明一个全局指令
Vue.directive("x", directiveOptions);

这样你就可以在任何组件里用v-x了,动手试试

  • 声明一个局部指令
new Vue({
	...,
	directives: {
		"x": directiveOptions
	}
})

注意,v-x只能用在该实例中,动手试试

directiveOptions

上面例子中的directiveOptions里有哪些属性?

五个函数属性

  • bind(el, info, vnode, oldVnode) - 类似created
  • inserted(参数同上) - 类似mounted
  • updated(参数同上) - 类似updated
  • componentUpdated(参数同上) - 用得不多,见文档
  • unbind(参数同上) - 类似destroyed

举例

main.js

import Vue from "vue/dist/vue.js"; // 使用完整版

Vue.config.productionTip = false;

new Vue({
  directives: {
    on2: {
      // bind 可以改为 inserted
      bind(el, info) {
        el.addEventListener(info.arg, info.value);
        // Vue 自带的 v-on 并不是这样实现的,它更复杂,用了事件委托
      },
      unbind(el, info) {
        el.removeEventListener(info.arg, info.value);
      },
    },
  },
  template: `
    <button v-on2:click="hi">点我</button>
  `,
  methods: {
    hi() {
      console.log("hi");
    },
  },
}).$mount("#app");

缩写

  • directiveOptions在某些条件下可以缩写为函数,用得不多,可以自行看文档

directives - 指令 的作用

主要用于 DOM 操作

  • Vue 实例/组件用于数据绑定、事件监听、DOM更新
  • Vue 指令主要目的就是原生DOM操作

减少重复

  • 如果某个DOM操作你经常使用,就可以封装为指令
  • 如果某个DOM操作比较复杂,也可以封装为指令

mixins - 混入

混入其实就是复制

减少重复

类比

  • directives的作用是减少DOM操作的重复
  • mixins的作用是减少datamethods、 钩子的重复

所有在构造选项里放的东西都可以放到mixins里来,真香

场景描述

假设我们需要再每个组件上添加nametime,在createddestroyed时,打出提示,并报出存活时间。一共有五个组件,请问你怎么做?

  • 给每个组件添加data和钩子,共五次
  • 或者使用mixins减少重复
  • 完整代码:

log.js

const log = {
  data() {
    return {
      name: undefined,
      time: undefined,
    };
  },
  created() {
    if (!this.name) {
      throw new Error("need name");
    }
    this.time = new Date();
    console.log(`${this.name}出生了`);
  },
  beforeDestroy() {
    const now = new Date();

    console.log(`${this.name}死亡了,共生存了 ${now - this.time} ms`);
  },
};

export default log;

Child1.vue

<template>
  <div>Child1</div>
</template>

<script>
  import log from "../mixins/log.js";
  export default {
    data() {
      return {
        name: "Child1",
      };
    },
    created() {
      console.log("Child 1 的 created");
    },
    mixins: [log],
  };
</script>

log.jsChild1.vue会智能合并,所以叫mixins

mixins 技巧

  • 选项智能合并

见文档

  • Vue.mixin

见文档,不推荐使用,因为容易出现范围过大的问题

extends - 继承

减少重复

  • 还是与mixins同样的需求

Q: 这次我不想要在每个组件上都写一个mixins,有什么办法吗?

A: 你可以使用Vue.extendoptions.extends

MyVue.js

import Vue from "vue";
const MyVue = Vue.extend({
  data() {
    return {
      name: undefined,
      time: undefined,
    };
  },
  created() {
    if (!this.name) {
      throw new Error("need name");
    }
    this.time = new Date();
    console.log(`${this.name}出生了`);
  },
  beforeDestroy() {
    const now = new Date();

    console.log(`${this.name}死亡了,共生存了 ${now - this.time} ms`);
  },
});

export default MyVue;

Child1.vue

<template>
  <div>Child1</div>
</template>

<script>
  import MyVue from "../MyVue.js";

  export default {
    extends: MyVue,
    data() {
      return {
        name: "Child1",
      };
    },
  };
</script>

extends是比mixins更抽象一点的封装

如果你嫌写五次mixins麻烦,可以考虑extends一次,不过实际工作中用得很少

平时我还是基本用mixinsextends很少用到

provide 和 inject - 提供和注入

使用举例

需求

App.vue

<template>
  <div :class="`app theme-${themeName} fontSize-${fontSizeName}`">
    <Child1 />
    <button>x</button>
  </div>
</template>

<script>
  import Child1 from "./components/Child1.vue";
  export default {
    name: "App",
    provide() {
      return {
        themeName: this.themeName,
        fontSizeName: this.fontSizeName,
        changeTheme: this.changeTheme,
        changeFontSize: this.changeFontSize,
      };
    },
    data() {
      return {
        themeName: "blue", // 'red'
        fontSizeName: "normal", // 'big' | 'small'
      };
    },
    methods: {
      changeTheme() {
        if (this.themeName === "blue") {
          this.themeName = "red";
        } else {
          this.themeName = "blue";
        }
      },
      changeFontSize(size) {
        if (["normal", "big", "small"].indexOf(size) === -1) {
          throw new Error(`wront size: ${size}`);
        }
        this.fontSizeName = size;
      },
    },
    components: {
      Child1,
    },
  };
</script>

<style></style>

ChangeThemeButton.vue

<template>
  <div>
    <button @click="changeTheme">换肤</button>
    <button @click="changeFontSize('big')">大字</button>
    <button @click="changeFontSize('small')">小字</button>
    <button @click="changeFontSize('normal')">正常字</button>
  </div>
</template>
<script>
  export default {
    inject: ["themeName", "changeTheme", "changeFontSize"],
  };
</script>

Child1.vue

<template>
  <div>
    Child 1
    <change-theme-button />
    <!-- 这样写是可以的,也可以找到ChangeThemeButton这个组件 -->
  </div>
</template>

<script>
  import ChangeThemeButton from "./ChangeThemeButton.vue";
  export default {
    components: {
      ChangeThemeButton,
    },
  };
</script>
  • 祖先栽树(provide),后人乘凉(inject)

总结

  • 作用: 大范围的 datamethod 等共用
  • 注意: 不能只传 themeName 不传 changeTheme,因为 themeName 的值是被复制给 provide

思考题: 传引用可以吗?

答: 可以,但是不推荐,因为容易失控

总结

directives 指令

  • 全局用Vue.directive('x', {...})
  • 局部用options.directives
  • 作用是减少DOM操作相关重复代码

mixins 混入

  • 全局用Vue.mixin({...})
  • 局部用options.mixins:[mixin1, mixin2]
  • 作用是减少options里的重复

extends 继承/扩展

  • 全局用Vue.extend({...})
  • 局部用options.extends:{...}
  • 作用跟mixins差不多,只是形式不同

provide / inject 提供和注入

  • 祖先提供东西,后代注入东西
  • 作用是大范围、隔 N 代共享信息
comments powered by Disqus