混入
混入提供了一种非常灵活的方式,来分发 `vue` 组件中的可复用功能,一个混入对象可以包含任意组件选项,当组件使用混入对象时,所有混入对象的选项将被混入到组件本身的选项1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| var myMixin = { created: function () { this.hello() }, methods: { hello: function () { console.log('hello from mixin!') } } }
var Component = Vue.extend({ mixins: [myMixin] })
var component = new Component()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
<div id="app"></div>
<script> var Component = Vue.extend({ template: '<p>{{name}}</p>', data(){ return { name: '苏西' } }, mounted(){ console.log('佩奇') } })
new Component().$mount('#app') </script>
|
选项合并
当组件和混入对象含有同名选项时,这些选项将会以合适的方式进行合并,默认合并策略如下:
- 数据对象在内部进行递归合并,如果发生冲突以组件数据优先
- 同名钩子函数合并为一个数组,都会被调用,但混入对象的钩子函数会在组件钩子函数之前调用
- 值为对象的选项,如
methods、components 和 directives,将会被合并为一个对象,如果键名发生冲突,取组件对象的键值对 Vue.extend() 也使用同样的策略进行合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| <div id="app"> {{name}} </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <script> var myMixin = { data(){ name: '佩奇' }, created: function () { this.hello(); }, mounted(){ console.log('混入的钩子函数'); }, methods: { hello: function () { console.log('hello from mixin!') }, hi(){ console.log('hi mixin 混入') } } }
new Vue({ el: '#app', mixins: [myMixin], data(){ return { name: '苏西' } }, created(){ this.hi(); }, mounted(){ console.log('组件自身的钩子函数'); }, methods: { hello(){ console.log('hello from component'); } } }) </script>
|
全局混入
混入可以进行全局注册,使用全局混入,它将影响每一个之后创建的 vue 实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Vue.mixin({ created: function () { var myOption = this.$options.myOption if (myOption) { console.log(myOption) } } })
new Vue({ myOption: 'hello!' })
|
请谨慎使用全局混入,因为它会影响每个单独创建的 vue 实例 (包括第三方组件)。大多数情况下,只应当应用于自定义选项,推荐将其作为插件发布,以避免重复应用混入
自定义选项合并策略
自定义选项将使用默认策略,如果想让自定义选项以自定义逻辑合并,可以向 Vue.config.optionMergeStrategies 添加一个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Vue.config.optionMergeStrategies.myOption = function(toVal, fromVal){ console.log(toVal, fromVal);
if(!toVal){ return fromVal; }
if(!fromVal){ return toVal; }
return toVal + fromVal; }
var strategies = Vue.config.optionMergeStrategies strategies.myOption = strategies.methods
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| Vue.config.optionMergeStrategies.myOption = function(toVal, fromVal){ console.log(toVal, fromVal);
if(!toVal){ return fromVal; }
if(!fromVal){ return toVal; }
return toVal + fromVal; }
var myMixin = { myOption: 'suxi' }
new Vue({ el: '#app', myOption: 'peiqi', mixins: [myMixin], mounted(){ console.log(this.$options.myOption); } })
|
自定义指令
除了核心功能默认内置的指令(v-model 和 v-show),vue 允许注册自定义指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
Vue.directive('focus', { inserted: function (el) { el.focus() } })
directives: { focus: { inserted: function (el) { el.focus() } } }
|
钩子函数
一个指令定义对象可以提供下面几个钩子函数(可选)
bind:只调用一次,指令第一次绑定到元素时调用,这里可以进行一次性的初始化设置inserted:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入到文档中)update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前,指令的值可能发生了改变,也可能没有,但是可以通过比较更新前后的值来忽略不必要的模板更新componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用unbind:只调用一次,指令与元素解绑时调用
钩子函数的参数
指令钩子函数会被传入以下参数
el:指令所绑定的元素,可以直接操作 dombinding:一个对象,格式如下name:指令名,不包括 v- 前缀value:指令的绑定值,例如 v-my-directive="1 + 1",绑定值为 2oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用,无论是否改变都可用expression:字符串形式的指令表达式,例如 v-my-directive="1 + 1",表达式为 "1 + 1"arg:传给指令的参数,可选,例如 v-my-directive:foo 中,参数是 foomodifiers:一个包含修饰符的对象,例如 v-my-directive.foo.bar 中,修饰符对象为 {foo: true, bar: true}
vnode:vue 编译生成的虚拟节点oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用
除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行
1
| <div id="app" v-demo:foo.a.b="message"></div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Vue.directive('demo', { bind: function (el, binding, vnode) { var s = JSON.stringify el.innerHTML = 'name: ' + s(binding.name) + '<br>' + 'value: ' + s(binding.value) + '<br>' + 'expression: ' + s(binding.expression) + '<br>' + 'argument: ' + s(binding.arg) + '<br>' + 'modifiers: ' + s(binding.modifiers) + '<br>' + 'vnode keys: ' + Object.keys(vnode).join(', ') } })
new Vue({ el: '#app', data: { message: 'hello!' } })
|
动态指令参数
指令的参数可以是动态的,例如,在 v-mydirective:[argument] = "value" 中,可以根据组件实例数据进行更新
1 2 3 4
| <div id="app"> <h3>Scroll down inside this section ↓</h3> <p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Vue.directive('pin', { bind: function (el, binding, vnode) { el.style.position = 'fixed' var s = (binding.arg == 'left' ? 'left' : 'top') el.style[s] = binding.value + 'px' } })
new Vue({ el: '#app', data: function () { return { direction: 'left' } } })
|
函数简写
如果需要在 bind 和 update 触发相同的行为,而不关心其他钩子,可以写成下面这样
1 2 3
| Vue.directive('color-swatch', function (el, binding) { el.style.backgroundColor = binding.value })
|
对象字面量
如果指令需要多个值,可以传入一个 javascript 对象字面量,指令函数可以接收合法的 js 表达式
1
| <div v-demo="{ color: 'white', text: 'hello!' }"></div>
|
1 2 3 4
| Vue.directive('demo', function (el, binding) { console.log(binding.value.color) console.log(binding.value.text) })
|
渲染函数 & JSX
vue 推荐在绝大多数情况下使用模板来创建 html,然而在一些场景中,需要 js 的完全编程能力,可以使用渲染函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
<script type="text/x-template" id="anchored-heading-template"> <h1 v-if="level === 1"> <slot></slot> </h1> <h2 v-else-if="level === 2"> <slot></slot> </h2> <h3 v-else-if="level === 3"> <slot></slot> </h3> <h4 v-else-if="level === 4"> <slot></slot> </h4> <h5 v-else-if="level === 5"> <slot></slot> </h5> <h6 v-else-if="level === 6"> <slot></slot> </h6> </script>
<script> Vue.component('anchored-heading', { template: '#anchored-heading-template', props: { level: { type: Number, required: true } } }) </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| Vue.component('anchored-heading', { render(createElement){ return createElement('h' + this.level, this.$slots.default); }, props: { level: { type: Number, required: true } } })
|
createElement 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
|
createElement( 'div',
{
},
[ '先写一些文字', createElement('h1', '一则头条'), createElement(MyComponent, { props: { someProp: 'foobar' } }) ] )
{ class: { foo: true, bar: false }, style: { color: 'red', fontSize: '14px' }, attrs: { id: 'foo' }, props: { myProp: 'bar' }, domProps: { innerHTML: 'baz' }, on: { click: this.clickHandler }, nativeOn: { click: this.nativeClickHandler }, directives: [ { name: 'my-custom-directive', value: '2', expression: '1 + 1', arg: 'foo', modifiers: { bar: true } } ], scopedSlots: { default: props => createElement('span', props.text) }, slot: 'name-of-slot', key: 'myKey', ref: 'myRef', refInFor: true }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <div id="app"> <my-demo :level="1"><template v-slot:live><a href="www.baidu.com">百度一下</a></template></my-demo> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <script> let com = Vue.component('MyComponent', { template: '<h3>{{name}}</h3>', props: ['name'] })
console.log(com)
Vue.component('my-demo', { render(createElement){ return createElement('h' + this.level, { class: { foo: true, } },['wode', createElement('h2', {class: {too: true}},'一个标题'), createElement(com, {props: {name: '苏西'}}), ...this.$slots.live]); }, props: { level: { type: Number, required: true } }, mounted(){ console.log(this.$slots.live) } })
new Vue({ el: '#app' }) </script>
|
节点、树以及虚拟 dom
1 2 3 4 5
| <div> <h1>My title</h1> Some text content </div>
|
上面的 html 对应的 dom 节点树如下图

每个元素都是一个节点。每段文字也是一个节点。甚至注释也都是节点。一个节点就是页面的一个部分。就像家谱树一样,每个节点都可以有孩子节点
高效地更新所有这些节点会是比较困难的,然而 vue 可以在模板中或渲染函数自动保持页面的更新,即便数据发生变化
1
| <h1>{{ blogTitle }}</h1>
|
1 2 3
| render: function (createElement) { return createElement('h1', this.blogTitle) }
|
组件树中的所有 VNode 必须是唯一的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| render: function (createElement) { var myParagraphVNode = createElement('p', 'hi') return createElement('div', [ myParagraphVNode, myParagraphVNode ]) }
render: function (createElement) { return createElement('div', Array.apply(null, { length: 20 }).map(function () { return createElement('p', 'hi') }) ) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
let arr1 = Array(5); let arr2 = Array.apply(null, {length: 5});
console.log(arr1); console.log(arr2);
console.log(arr1[0]); console.log([...arr1]);
let arr3 = Array.apply(null, {'0': 1, '1': 2, length: 2}); console.log(arr3);
|
使用 javascript 代替模板功能
v-if 和 v-for
1 2 3 4 5
| <ul v-if="items.length"> <li v-for="item in items">{{ item.name }}</li> </ul> <p v-else>No items found.</p>
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| Vue.component('my-demo', { props: ['items'], render(createElement){ if(this.items.length){ return createElement('ul', this.items.map((item) => createElement('li', item.name))) }else{ return createElement('p', 'No items found'); } } })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <div id="app"> <my-demo :items="items"></my-demo> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <script> Vue.component('my-demo', { props: ['items'], render(h){ if(this.items.length){ return h('ul', this.items.map(item => h('li', item))); }else{ return h('p', '为空') } } })
new Vue({ el: '#app', data: { items: [1, 2, 3, 4, 5] } }) </script>
|
v-model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| data(){ return {value: '苏西'} }, render(h){ var self = this; return h('input', { domProps:{ value: self.value }, on: { input(event){ self.value = event.target.value; } } }) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| <div id="app"> <my-demo></my-demo> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <script> Vue.component('my-demo', { data(){ return { value: '苏西' } }, render(h){ var self = this; return h('div', [ h('input', { domProps: { value: self.value }, on: { input(e){ self.value = e.target.value } } }), h('span', self.value) ]) } })
new Vue().$mount('#app') </script>
<div id="app"> <my-demo :value="value" @input="value = $event"></my-demo> <span>{{value}}</span> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <script> Vue.component('my-demo', { props: ['value'], render(h){ var self = this; return h('input', { domProps: { value: self.value }, on: { input(e){ self.$emit('input', e.target.value) } } }) } })
new Vue({data: {value: '苏西'}}).$mount('#app') </script>
|
事件 & 按键修饰符
对于 .passive、.capture 和 .once 这些事件修饰符,vue 提供了相应的前缀可用于 on
| 修饰符 | 前缀 |
|---|
.passive | & |
.capture | ! |
.once | ~ |
.capture.once 或 .once.capture | ~! |
1 2 3 4 5
| on: { '!click': this.doThisInCapturingMode, '~keyup': this.doThisOnce, '~!mouseover': this.doThisOnceInCapturingMode }
|
对于所有其他的修饰符,私有前缀不是必须的,需要在事件处理函数中进行处理
| 修饰符 | 前缀 |
|---|
.stop | event.stopPropagation() |
.prevent | event.preventDefault() |
.self | if(event.target !== event.currentTarget) return |
按键 .enter、.13 | if(event.keyCode !== 13) return |
修饰键.ctrl .alt .shift .meta | if(!event.ctrlKey)return |
1 2 3 4 5 6 7 8 9 10 11
| on: { keyup: function(event){ if(event.target !== event.currentTarget) return;
if(!event.shiftKye || event.keyCode !== 13) return;
event.preventDefault(); event.stopPropagation(); } }
|
插槽
通过 this.$slots 访问静态插槽的内容,每个插槽都是一个 VNode 数组
1 2 3 4 5 6 7 8 9
| render: function(h){ return h('div', this.$slots.default); }
render: function(h){ return h('div', this.$slots.sh); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
<div id="app"> <my-demo> <template> <div style="background-color: red;"> hello </div> </template>
<template v-slot:sh> <div style="background-color: skyblue;"> world </div> </template> </my-demo> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <script> Vue.component('my-demo', { render(h){ var self = this; return h('div', [...this.$slots.default, ...this.$slots.sh]); } })
new Vue({data: {value: '苏西'}}).$mount('#app') </script>
|
也可以通过 this.$scopedSlots 访问作用域插槽,每个作用域插槽都是返回若干 VNode 的函数
1 2 3 4 5 6 7 8 9
| props: ['message'], render: function (createElement) { return createElement('div', [ this.$scopedSlots.default({ text: this.message }) ]) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <div id="app"> <my-demo> <template v-slot:default="{text}"> <div style="background-color: red;"> {{text}} </div> </template>
<template v-slot:sh="{sh}"> <div style="background-color: skyblue;"> {{sh}} </div> </template> </my-demo> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <script> Vue.component('my-demo', { data(){ return {text: 'suxi', sh: 'peiqi'} }, render(h){ var self = this; return h('div', [this.$scopedSlots.default({text: self.text}), this.$scopedSlots.sh({sh: self.sh})]) } })
new Vue({data: {value: '苏西'}}).$mount('#app') </script>
|
如果需要用渲染函数向子组件中传递作用域插槽,可以利用 VNode 数据对象中的 scopedSlots 属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| render: function (createElement) { return createElement('div', [ createElement('child', { scopedSlots: { default: function (props) { return createElement('span', props.text) } } }) ]) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <div id="app"> <my-demo></my-demo> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <script> Vue.component('child', { data(){ return {text: '苏西'} }, render(h){ let self = this; return h('div', [this.$scopedSlots.default({ text: self.text })]) } })
Vue.component('my-demo', { data(){ return {text: 'suxi', sh: 'peiqi'} }, render(h){ var self = this; return h('div', [h('child', { scopedSlots: { default: prop => h('span', prop.text) } })]) } })
new Vue({data: {value: '苏西'}}).$mount('#app') </script>
|
JSX
1 2 3 4 5 6 7 8 9 10 11 12
|
createElement( 'anchored-heading', { props: { level: 1 } }, [ createElement('span', 'Hello'), ' world!' ] )
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| import AnchoredHeading from './AnchoredHeading.vue'
new Vue({ el: '#demo', render: function (h) { return ( <AnchoredHeading level={1}> <span>Hello</span> world! </AnchoredHeading> ) } })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
<div id="app"></div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <script> Vue.component('child', { data(){ return {text: '苏西'} }, render(h){ let self = this; return h('div', [this.$scopedSlots.default({ text: self.text })]) } })
Vue.component('my-demo', { props: ['name'], render(h){ var self = this; return h('div', [h('child', { scopedSlots: { default: prop => h('span', prop.text) } }), h('span',{style: {color: 'red'}} self.name)]) } })
new Vue({ render(h){ return h('my-demo'); } }).$mount('#app') </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| Vue.component('child', { data(){ return {text: '苏西'} }, render(){ return ( <div> {this.$scopedSlots.default({text: this.text})} </div> ) } })
Vue.component('my-demo', { props: ['name'], render(){ return ( <div> <Child scopedSlots={{default: props => <span>{props.text}</span>}} /> <span style={{color: 'red'}}>{this.name}</span> </div> ) } })
new Vue({ render(){ return <MyDemo name={'佩奇'} />; } }).$mount('#app')
|
当然浏览器是无法直接运行 jsx 的所以还需要使用 babel 和对应的插件进行编译之后运行,下面给出一个栗子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| ├── src │ ├── components │ │ ├── Child.jsx │ │ └── MyDemo.jsx │ ├── App.jsx └── └── main.js
export default { data(){ return {text: '苏西'} }, render(){ return ( <div> {this.$scopedSlots.default({text: this.text})} </div> ) } }
import Child from './Child'; export default { props: ['name'], render(){ return ( <div> <Child scopedSlots={{ default: props => <span>{props.text}</span> }} /> <span style={{color: 'red'}}>{this.name}</span> </div> ) } }
import MyDemo from './components/MyDemo'
export default { render(){ return <MyDemo name={'佩奇'} /> } }
import Vue from 'vue' import App from './App'
Vue.config.productionTip = false
new Vue({ render: h => h(App), }).$mount('#app')
|
函数式组件
使用函数组件,将组件标记为 functional,这也意味着它是无状态的,也没有实例,一个函数组件就像这样
1 2 3 4 5 6 7 8 9 10 11 12
| Vue.component('my-component', { functional: true, props: { }, render(createElement, context){ } })
|
在 2.3.0 之前的版本,如果一个函数式组件要接收 prop,则 props 选项是必须的,在 2.3.0 或以上的版本,可以省略 props 选项,所有组件上的 attribute 都会被自动隐式解析为 prop,当使用函数式组件时,该引用将会是 HTMLElement 因为它们是无状态的也是无实例的
1 2 3 4
| <template functional>
</template>
|
组件需要的一切都是通过 context 参数传递,它是一个包括以下字段的对象
props:提供所有 prop 的对象children:VNode 子节点数组slots:一个函数,返回包含所有插槽的对象,scopedSlots:一个暴露传入的作用域插槽对象,也可以以函数形式暴露普通插槽data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件parent:对父组件的引用listeners:包含所有父组件当前组件注册的事件监听器的对象,这是 data.on 的一个别名injections:如果使用 inject 选项,则该对象包含了应当被注入的 property
函数式组件只是函数,所以渲染开销也会低很多,在作为包装组件时它们也同样非常有用,比如
- 程序化地在多个组件中选择一个来代为渲染
- 在将
children、props、data 传递给子组件之前操作它们
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| var EmptyList = { } var TableList = { } var OrderedList = { } var UnorderedList = { }
Vue.component('smart-list', { functional: true, props: { items: { type: Array, required: true }, isOrdered: Boolean }, render: function (createElement, context) { function appropriateListComponent () { var items = context.props.items
if (items.length === 0) return EmptyList if (typeof items[0] === 'object') return TableList if (context.props.isOrdered) return OrderedList
return UnorderedList }
return createElement( appropriateListComponent(), context.data, context.children ) } })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <div id="app"></div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <script> Vue.component('child', { functional: true, render(h, context){ console.log(context) return h('div', context.data, context.children) } })
new Vue({ render(h){ return h('child',{style: {color: 'red'}} '苏西'); } }).$mount('#app') </script>
|
向子组件或子元素传递属性和事件
在普通组件中,没有被定义为 prop 的属性会自动添加到组件的根元素上,将已有的同名属性进行替换或与其进行智能合并
1 2 3 4 5 6 7 8 9
| Vue.component('my-functional-button', { functional: true, render: function (createElement, context) { return createElement('button', context.data, context.children) } })
|
通过向 createElement 传入 context.data 作为第二个参数,我们就把 my-functional-button 上面所有的 attribute 和事件监听器都传递下去了。事实上这是非常透明的,以至于那些事件甚至并不要求 .native 修饰符
如果你使用基于模板的函数式组件,那么你还需要手动添加 attribute 和监听器。因为我们可以访问到其独立的上下文内容,所以我们可以使用 data.attrs 传递任何 HTML attribute,也可以使用 listeners (即 data.on 的别名) 传递任何事件监听器
1 2 3 4 5 6 7 8 9
| <template functional> <button class="btn btn-primary" v-bind="data.attrs" v-on="listeners" > <slot/> </button> </template>
|
slots() 和 children 对比
slots 是一个函数,返回所有插槽,children 只表示组件内部的元素
1 2 3 4 5 6
| <my-functional-component> <p v-slot:foo> first </p> <p>second</p> </my-functional-component>
|
对于上面的组件,children 会给你两个段落标签,而 slots().default 只会传递第二个匿名段落标签,slots().foo 会传递第一个具名段落标签,可以选择性让组件感知某个插槽机制,也可以简单的传递 children 移交给其他组件进行处理
插件
插件通常用来为 vue 添加全局功能,例如:
- 添加全局方法或属性
- 添加全局资源
- 通过全局混入来添加一些组件选项
- 添加
vue 实例方法,通过把它们添加到 Vue.prototype 上实现 - 一个库,提供自己的
API 同时提供上面的一个或几个功能
使用插件
1 2 3 4 5 6 7 8 9 10 11
|
Vue.use(MyPlugin)
new Vue({ })
Vue.use(MyPlugin, { someOption: true })
|
Vue.use 会自动阻止多次注册相同插件,即使多次调用,也会只注册一次
开发插件
vue 的插件应该暴露一个 install 方法,这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| MyPlugin.install = function (Vue, options) { Vue.myGlobalMethod = function () { }
Vue.directive('my-directive', { bind (el, binding, vnode, oldVnode) { } ... })
Vue.mixin({ created: function () { } ... })
Vue.prototype.$myMethod = function (methodOptions) { } }
|
过滤器
vue 允许自定义过滤器,可被用于一些常见的文本格式化,过滤器可以用在两个地方:双花括号插值和 v-bind 表达式,过滤器应该被添加在 js 表达式的尾部,有管道(|)符号指示
1 2 3 4 5
| {{ message | capitalize }}
<div v-bind:id="rawId | formatId"></div>
|
在组件中定义本地过滤器
1 2 3 4 5 6 7
| filters: { capitalize(value){ if(!value) return ''; value = value.toString(); return value.charAt(0).toUpperCase() + value.slice(1); } }
|
或者在创建 vue 实例之前全局定义过滤器
1 2 3 4 5
| Vue.filter('capitalize', function(value){ if(!value) return ''; value = value.toString(); return value.charAt(0).toUpperCase() + value.slice(1); })
|
当全局过滤器和局部过滤器重名时,会采用局部过滤器,过滤器函数总接收表达式的值作为第一个参数,在上例中,capitalize 会接收 message 的值作为第一个参数,并且过滤器可以串联
1
| {{message | filterA | filterB}}
|
filterA 被定义为接收单个参数的过滤器函数,表达式 message 的值将作为参数传入到函数,然后继续调用同样被定义为接收单个参数的过滤器函数 filterB,将 filterA 的结果传递到 filterB 中。过滤器是 JS 函数,因此可以接收参数
1 2 3
| {{ message | filterA('arg1', arg2) }}
|