基础

  • Vue.js 组件的三个 API:prop、event、slot

    prop

  • 如果你不希望组件的根元素继承特性,你可以在组件的选项中设置 inheritAttrs: false

  • https://zhuanlan.zhihu.com/p/195455758

slot

  • 默认插槽
  • 具名插槽

event

子:this.$emit('on-click', event);
父:<i-button @on-click="handleClick"></i-button>

组件引用

  • ref:给元素或组件注册引用信息;
  • $parent / $children:访问父 / 子实例。

组件通信1

provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。

  1. // A.vue
  2. export default {
  3. provide: {
  4. name: 'Aresn'
  5. }
  6. }
  7. // B.vue
  8. export default {
  9. inject: ['name'],
  10. mounted () {
  11. console.log(this.name); // Aresn
  12. }
  13. }

B 是 A 的子组件,A的所有子集都可以用inject获取

provide 和 inject 绑定并不是可响应的,A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的,仍然是 Aresn。

组件通信2

$on

监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数。

  1. vm.$on('test', function (msg) {
  2. console.log(msg)
  3. })
  4. vm.$emit('test', 'hi')
  5. // => "hi"

Vue.js 1.x 的 $dispatch 与 $broadcast

在 Vue.js 1.x 中,提供了两个方法:$dispatch 和 $broadcast ,前者用于向上级派发事件,只要是它的父级(一级或多级以上),都可以在组件内通过 $on (或 events,2.x 已废弃)监听到,后者相反,是由上级向下级广播事件的。
来看一个简单的示例:

  1. <!-- 注意:该示例为 Vue.js 1.x 版本 -->
  2. <!-- 子组件 -->
  3. <template>
  4. <button @click="handleDispatch">派发事件</button>
  5. </template>
  6. <script>
  7. export default {
  8. methods: {
  9. handleDispatch () {
  10. this.$dispatch('test', 'Hello, Vue.js');
  11. }
  12. }
  13. }
  14. </script>
  15. <!-- 父组件,部分代码省略 -->
  16. <template>
  17. <child-component></child-component>
  18. </template>
  19. <script>
  20. export default {
  21. mounted () {
  22. this.$on('test', (text) => {
  23. console.log(text); // Hello, Vue.js
  24. });
  25. }
  26. }
  27. </script>

自行实现

通过目前已知的信息,我们要实现的 dispatch 和 broadcast 方法,将具有以下功能:

  • 在子组件调用 dispatch 方法,向上级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该上级组件已预先通过 $on 监听了这个事件;
  • 相反,在父组件调用 broadcast 方法,向下级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该下级组件已预先通过 $on 监听了这个事件。 ```bash // 部分代码省略 import Emitter from ‘../mixins/emitter.js’

export default { mixins: [ Emitter ], methods: { handleDispatch () { this.dispatch(); // ① }, handleBroadcast () { this.broadcast(); // ② } } }

  1. emitter.js
  2. ```bash
  3. function broadcast(componentName, eventName, params) {
  4. this.$children.forEach(child => {
  5. const name = child.$options.name;
  6. if (name === componentName) {
  7. child.$emit.apply(child, [eventName].concat(params));
  8. } else {
  9. broadcast.apply(child, [componentName, eventName].concat([params]));
  10. }
  11. });
  12. }
  13. export default {
  14. methods: {
  15. dispatch(componentName, eventName, params) {
  16. let parent = this.$parent || this.$root;
  17. let name = parent.$options.name;
  18. while (parent && (!name || name !== componentName)) {
  19. parent = parent.$parent;
  20. if (parent) {
  21. name = parent.$options.name;
  22. }
  23. }
  24. if (parent) {
  25. parent.$emit.apply(parent, [eventName].concat(params));
  26. }
  27. },
  28. broadcast(componentName, eventName, params) {
  29. broadcast.call(this, componentName, eventName, params);
  30. }
  31. }
  32. };

可以看到,在 dispatch 里,通过 while 语句,不断向上遍历更新当前组件(即上下文为当前调用该方法的组件)的父组件实例(变量 parent 即为父组件实例),直到匹配到定义的 componentName 与某个上级组件的 name 选项一致时,结束循环,并在找到的组件实例上,调用 $emit 方法来触发自定义事件 eventName。broadcast 方法与之类似,只不过是向下遍历寻找。

来看一下具体的使用方法。有 A.vueB.vue 两个组件,其中 B 是 A 的子组件,中间可能跨多级,在 A 中向 B 通信:

  1. <!-- A.vue -->
  2. <template>
  3. <button @click="handleClick">触发事件</button>
  4. </template>
  5. <script>
  6. import Emitter from '../mixins/emitter.js';
  7. export default {
  8. name: 'componentA',
  9. mixins: [ Emitter ],
  10. methods: {
  11. handleClick () {
  12. this.broadcast('componentB', 'on-message', 'Hello Vue.js');
  13. }
  14. }
  15. }
  16. </script>
  1. // B.vue
  2. export default {
  3. name: 'componentB',
  4. created () {
  5. this.$on('on-message', this.showMessage);
  6. },
  7. methods: {
  8. showMessage (text) {
  9. window.alert(text);
  10. }
  11. }
  12. }

组件通信3

findComponents 系列方法最终都是返回组件的实例,进而可以读取或调用该组件的数据和方法。
它适用于以下场景:

  • 由一个组件,向上找到最近的指定组件;
  • 由一个组件,向上找到所有的指定组件;
  • 由一个组件,向下找到最近的指定组件;
  • 由一个组件,向下找到所有指定的组件;
  • 由一个组件,找到指定组件的兄弟组件。

    向上找到最近的指定组件——findComponentUpward

    1. // assist.js
    2. // 由一个组件,向上找到最近的指定组件
    3. function findComponentUpward (context, componentName) {
    4. let parent = context.$parent;
    5. let name = parent.$options.name;
    6. while (parent && (!name || [componentName].indexOf(name) < 0)) {
    7. parent = parent.$parent;
    8. if (parent) name = parent.$options.name;
    9. }
    10. return parent;
    11. }
    12. export { findComponentUpward };

    向上找到所有的指定组件——findComponentsUpward

    1. // assist.js
    2. // 由一个组件,向上找到所有的指定组件
    3. function findComponentsUpward (context, componentName) {
    4. let parents = [];
    5. const parent = context.$parent;
    6. if (parent) {
    7. if (parent.$options.name === componentName) parents.push(parent);
    8. return parents.concat(findComponentsUpward(parent, componentName));
    9. } else {
    10. return [];
    11. }
    12. }
    13. export { findComponentsUpward };

    向下找到最近的指定组件——findComponentDownward

    1. // assist.js
    2. // 由一个组件,向下找到最近的指定组件
    3. function findComponentDownward (context, componentName) {
    4. const childrens = context.$children;
    5. let children = null;
    6. if (childrens.length) {
    7. for (const child of childrens) {
    8. const name = child.$options.name;
    9. if (name === componentName) {
    10. children = child;
    11. break;
    12. } else {
    13. children = findComponentDownward(child, componentName);
    14. if (children) break;
    15. }
    16. }
    17. }
    18. return children;
    19. }
    20. export { findComponentDownward };

    向下找到所有指定的组件——findComponentsDownward

    1. // assist.js
    2. // 由一个组件,向下找到所有指定的组件
    3. function findComponentsDownward (context, componentName) {
    4. return context.$children.reduce((components, child) => {
    5. if (child.$options.name === componentName) components.push(child);
    6. const foundChilds = findComponentsDownward(child, componentName);
    7. return components.concat(foundChilds);
    8. }, []);
    9. }
    10. export { findComponentsDownward };

    找到指定组件的兄弟组件——findBrothersComponents

    1. // assist.js
    2. // 由一个组件,找到指定组件的兄弟组件
    3. function findBrothersComponents (context, componentName, exceptMe = true) {
    4. let res = context.$parent.$children.filter(item => {
    5. return item.$options.name === componentName;
    6. });
    7. let index = res.findIndex(item => item._uid === context._uid);
    8. if (exceptMe) res.splice(index, 1);
    9. return res;
    10. }
    11. export { findBrothersComponents };

    创建组件

    创建一个 Vue 实例时,都会有一个选项 el,来指定实例的根节点,如果不写 el选项,那组件就处于未挂载状态。Vue.extend的作用,就是基于 Vue 构造器,创建一个“子类”,它的参数跟 new Vue的基本一样,但 data要跟组件一样,是个函数,再配合 $mount ,就可以让组件渲染,并且挂载到任意指定的节点上,比如 body。 ```javascript import Vue from ‘vue’;

const AlertComponent = Vue.extend({ template: ‘

{{ message }}
‘, data () { return { message: ‘Hello, Aresn’ }; }, });

  1. 这一步,我们调用了 `$mount `方法对组件进行了手动渲染,但它仅仅是被渲染好了,并没有挂载到节点上,也就显示不了组件。此时的 `component `已经是一个标准的 Vue 组件实例,因此它的`$el`属性也可以被访问:
  2. ```javascript
  3. const component = new AlertComponent().$mount();
  1. document.body.appendChild(component.$el);

也可以直接挂载

  1. // 在 $mount 里写参数来指定挂载的节点
  2. new AlertComponent().$mount('#app');
  3. // 不用 $mount,直接在创建实例时指定 el 选项
  4. new AlertComponent({ el: '#app' });

也可以直接创建 Vue 实例,并且用一个 Render 函数来渲染一个 .vue 文件

  1. import Vue from 'vue';
  2. import Notification from './notification.vue';
  3. const props = {}; // 这里可以传入一些组件的 props 选项
  4. const Instance = new Vue({
  5. render (h) {
  6. return h(Notification, {
  7. props: props
  8. });
  9. }
  10. });
  11. const component = Instance.$mount();
  12. document.body.appendChild(component.$el);

这样既可以使用 .vue 来写复杂的组件(毕竟在 template 里堆字符串很痛苦),还可以根据需要传入适当的 props。渲染后,如果想操作 Render 的 Notification 实例,也是很简单的:

  1. const notification = Instance.$children[0];

因为 Instance 下只 Render 了 Notification 一个子组件,所以可以用 $children[0] 访问到。

需要注意的是,我们是用 $mount 手动渲染的组件,如果要销毁,也要用 $destroy 来手动销毁实例,必要时,也可以用 removeChild 把节点从 DOM 中移除。

Render