基础
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 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
// A.vueexport default {provide: {name: 'Aresn'}}// B.vueexport default {inject: ['name'],mounted () {console.log(this.name); // Aresn}}
B 是 A 的子组件,A的所有子集都可以用inject获取
provide 和 inject 绑定并不是可响应的,A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的,仍然是 Aresn。
组件通信2
$on
监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数。
vm.$on('test', function (msg) {console.log(msg)})vm.$emit('test', 'hi')// => "hi"
Vue.js 1.x 的 $dispatch 与 $broadcast
在 Vue.js 1.x 中,提供了两个方法:$dispatch 和 $broadcast ,前者用于向上级派发事件,只要是它的父级(一级或多级以上),都可以在组件内通过 $on (或 events,2.x 已废弃)监听到,后者相反,是由上级向下级广播事件的。
来看一个简单的示例:
<!-- 注意:该示例为 Vue.js 1.x 版本 --><!-- 子组件 --><template><button @click="handleDispatch">派发事件</button></template><script>export default {methods: {handleDispatch () {this.$dispatch('test', 'Hello, Vue.js');}}}</script><!-- 父组件,部分代码省略 --><template><child-component></child-component></template><script>export default {mounted () {this.$on('test', (text) => {console.log(text); // Hello, Vue.js});}}</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(); // ② } } }
emitter.js```bashfunction broadcast(componentName, eventName, params) {this.$children.forEach(child => {const name = child.$options.name;if (name === componentName) {child.$emit.apply(child, [eventName].concat(params));} else {broadcast.apply(child, [componentName, eventName].concat([params]));}});}export default {methods: {dispatch(componentName, eventName, params) {let parent = this.$parent || this.$root;let name = parent.$options.name;while (parent && (!name || name !== componentName)) {parent = parent.$parent;if (parent) {name = parent.$options.name;}}if (parent) {parent.$emit.apply(parent, [eventName].concat(params));}},broadcast(componentName, eventName, params) {broadcast.call(this, componentName, eventName, params);}}};
可以看到,在 dispatch 里,通过 while 语句,不断向上遍历更新当前组件(即上下文为当前调用该方法的组件)的父组件实例(变量 parent 即为父组件实例),直到匹配到定义的 componentName 与某个上级组件的 name 选项一致时,结束循环,并在找到的组件实例上,调用 $emit 方法来触发自定义事件 eventName。broadcast 方法与之类似,只不过是向下遍历寻找。
来看一下具体的使用方法。有 A.vue 和 B.vue 两个组件,其中 B 是 A 的子组件,中间可能跨多级,在 A 中向 B 通信:
<!-- A.vue --><template><button @click="handleClick">触发事件</button></template><script>import Emitter from '../mixins/emitter.js';export default {name: 'componentA',mixins: [ Emitter ],methods: {handleClick () {this.broadcast('componentB', 'on-message', 'Hello Vue.js');}}}</script>
// B.vueexport default {name: 'componentB',created () {this.$on('on-message', this.showMessage);},methods: {showMessage (text) {window.alert(text);}}}
组件通信3
findComponents 系列方法最终都是返回组件的实例,进而可以读取或调用该组件的数据和方法。
它适用于以下场景:
- 由一个组件,向上找到最近的指定组件;
- 由一个组件,向上找到所有的指定组件;
- 由一个组件,向下找到最近的指定组件;
- 由一个组件,向下找到所有指定的组件;
-
向上找到最近的指定组件——findComponentUpward
// assist.js// 由一个组件,向上找到最近的指定组件function findComponentUpward (context, componentName) {let parent = context.$parent;let name = parent.$options.name;while (parent && (!name || [componentName].indexOf(name) < 0)) {parent = parent.$parent;if (parent) name = parent.$options.name;}return parent;}export { findComponentUpward };
向上找到所有的指定组件——findComponentsUpward
// assist.js// 由一个组件,向上找到所有的指定组件function findComponentsUpward (context, componentName) {let parents = [];const parent = context.$parent;if (parent) {if (parent.$options.name === componentName) parents.push(parent);return parents.concat(findComponentsUpward(parent, componentName));} else {return [];}}export { findComponentsUpward };
向下找到最近的指定组件——findComponentDownward
// assist.js// 由一个组件,向下找到最近的指定组件function findComponentDownward (context, componentName) {const childrens = context.$children;let children = null;if (childrens.length) {for (const child of childrens) {const name = child.$options.name;if (name === componentName) {children = child;break;} else {children = findComponentDownward(child, componentName);if (children) break;}}}return children;}export { findComponentDownward };
向下找到所有指定的组件——findComponentsDownward
// assist.js// 由一个组件,向下找到所有指定的组件function findComponentsDownward (context, componentName) {return context.$children.reduce((components, child) => {if (child.$options.name === componentName) components.push(child);const foundChilds = findComponentsDownward(child, componentName);return components.concat(foundChilds);}, []);}export { findComponentsDownward };
找到指定组件的兄弟组件——findBrothersComponents
// assist.js// 由一个组件,找到指定组件的兄弟组件function findBrothersComponents (context, componentName, exceptMe = true) {let res = context.$parent.$children.filter(item => {return item.$options.name === componentName;});let index = res.findIndex(item => item._uid === context._uid);if (exceptMe) res.splice(index, 1);return res;}export { findBrothersComponents };
创建组件
创建一个 Vue 实例时,都会有一个选项
el,来指定实例的根节点,如果不写el选项,那组件就处于未挂载状态。Vue.extend的作用,就是基于 Vue 构造器,创建一个“子类”,它的参数跟new Vue的基本一样,但data要跟组件一样,是个函数,再配合$mount,就可以让组件渲染,并且挂载到任意指定的节点上,比如body。 ```javascript import Vue from ‘vue’;
const AlertComponent = Vue.extend({ template: ‘
这一步,我们调用了 `$mount `方法对组件进行了手动渲染,但它仅仅是被渲染好了,并没有挂载到节点上,也就显示不了组件。此时的 `component `已经是一个标准的 Vue 组件实例,因此它的`$el`属性也可以被访问:```javascriptconst component = new AlertComponent().$mount();
document.body.appendChild(component.$el);
也可以直接挂载
// 在 $mount 里写参数来指定挂载的节点new AlertComponent().$mount('#app');// 不用 $mount,直接在创建实例时指定 el 选项new AlertComponent({ el: '#app' });
也可以直接创建 Vue 实例,并且用一个 Render 函数来渲染一个 .vue 文件
import Vue from 'vue';import Notification from './notification.vue';const props = {}; // 这里可以传入一些组件的 props 选项const Instance = new Vue({render (h) {return h(Notification, {props: props});}});const component = Instance.$mount();document.body.appendChild(component.$el);
这样既可以使用 .vue 来写复杂的组件(毕竟在 template 里堆字符串很痛苦),还可以根据需要传入适当的 props。渲染后,如果想操作 Render 的 Notification 实例,也是很简单的:
const notification = Instance.$children[0];
因为 Instance 下只 Render 了 Notification 一个子组件,所以可以用 $children[0] 访问到。
需要注意的是,我们是用 $mount 手动渲染的组件,如果要销毁,也要用 $destroy 来手动销毁实例,必要时,也可以用 removeChild 把节点从 DOM 中移除。
