自定义指令
自定义全局指令
- 第一个参数:指令名称。
- 第二个参数:一个对象,该对象有一下指令相关的钩子函数。
- 注意:定义的时候,指令的名称前面不需加
v-
前缀,但调用的时候需要。
Vue.directive('指令名称', { /*钩子函数*/ });
自定义私有指令(directives属性)
- 在声明vm对象中通过
directives
选项定义。
directives: {
// focus为指令名
focus: {
// 指令的定义,inserted为钩子函数
inserted: function (el) {
el.focus();
}
}
}
使用自定义指令
- 与官方定义指令使用一样,也可传参数。
<input v-focus>
<!-- 带参数 -->
<div id="app" v-demo="message"></div>
钩子函数
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。与JS行为相关的操作,最好在inserted去执行。update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新。componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind
:只调用一次,指令与元素解绑时调用。
钩子函数参数
指令钩子函数会被传入以下参数: (加粗为重要(或常用)属性)
- el:指令所绑定的元素,可以用来直接操作 DOM 。
- binding:一个对象,包含以下属性:
- name:指令名,不包括 v- 前缀。
- value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2。
- oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
- expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
- arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
- modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
- vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
除了
el
之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的dataset
来进行。
<div id="app" v-demo="message"></div>
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!'
}
});
函数简写
在很多时候,可能想在 bind
和 update
时触发相同行为,而不关心其它的钩子。可以采用以下方式:
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value;
});
对象字面量
如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法的 JavaScript 表达式。
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color); // => "white"
console.log(binding.value.text); // => "hello!"
})
Vue中的动画 <transition>
使用<transition>
包裹需要动画的元素。
过渡类名
v-enter
:定义进入过渡的开始状态。在元素被插入之前生效 。v-enter-active
:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。v-enter-to
:2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时v-enter
被移除),在过渡/动画完成之后移除。v-leave
:定义离开过渡的开始状态。v-leave-active
:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。v-leave-to
: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时v-leave
被删除),在过渡/动画完成之后移除。
使用过渡类名
对于这些在过渡中切换的类名来说,**如果使用一个没有定义name
属性的 <transition>
,则 v-
是这些类名的默认前缀。**如果你使用了 <transition name="fade">
,那么 v-enter
会替换为 fade-enter
。
<style>
/* 定义进入和离开时候的过渡状态 */
.fade-enter-active,
.fade-leave-active {
transition: all 0.2s ease;
position: absolute;
}
/* 定义进入过渡的开始状态 和 离开过渡的结束状态 */
.fade-enter,
.fade-leave-to {
opacity: 0;
transform: translateX(100px);
}
</style>
<div id="app">
<input type="button" value="动起来" @click="myAnimate">
<!-- 使用 transition 将需要过渡的元素包裹起来 -->
<transition name="fade">
<div v-show="isshow">动画哦</div>
</transition>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
isshow: false
},
methods: {
myAnimate() {
this.isshow = !this.isshow;
}
}
});
</script>
过渡模式mode
in-out
:新元素先进行过渡,完成之后当前元素过渡离开。out-in
:当前元素先进行过渡,完成之后新元素过渡进入。
例:
<transition name="fade" mode="out-in">
<!-- ... the buttons ... -->
</transition>
使用第三方css动画库(自定义过渡的类名)
enter-class
enter-active-class
enter-to-class
(2.1.8+)leave-class
leave-active-class
leave-to-class
(2.1.8+)- 他们的优先级高于普通的类名,含义对应上面的过渡类名 。
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
<div id="example-3">
<button @click="show = !show" value="Toggle render"></button>
<transition
name="custom-classes-transition"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight"
>
<p v-if="show">hello</p>
</transition>
</div>
<script>
new Vue({
el: '#example-3',
data: {
show: true
}
});
</script>
显性的过渡持续时间
-
用
<transition>
组件上的duration
属性定制一个显性的过渡持续时间 。 -
<transition :duration="数值">...</transition>
-
<transition :duration="{ enter: 数值, leave: 数值}">...</transition>
定制进入和移出的持续时间 。
JavaScript钩子 实现动画
可以在属性中声明 JavaScript 钩子 。
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<!-- ... -->
</transition>
// ...
methods: {
// 进入中
// --------
beforeEnter: function (el) { ... },
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
enter: function (el, done) { /* ... */ done(); },
afterEnter: function (el) { ... },
enterCancelled: function (el) { ... },
// 离开时
// --------
beforeLeave: function (el) { ... },
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
leave: function (el, done) { /* ... */ done(); },
afterLeave: function (el) { ... },
// leaveCancelled 只用于 v-show 中
leaveCancelled: function (el) { ... }
}
ps:
- 当只用 JavaScript 过渡的时候,在 enter 和 leave 中必须使用 done 进行回调。否则,它们将被同步调用,过渡会立即完成。
- 推荐对于仅使用 JavaScript 过渡的元素添加
v-bind:css="false"
,Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。
初始渲染的过渡
可以通过 appear
特性设置节点在初始渲染的过渡 。
<transition appear>
<!-- ... 初始化过渡默认和进入/离开过渡一样 -->
</transition>
列表过渡
使用 <transition-group>
组件。
- 不同于
<transition>
,它会以一个真实元素呈现:**默认为一个<span>
。可以通过tag
特性更换为其他元素。 - 内部元素 总是需要 提供唯一的
key
属性值。 - 过渡模式不可用
<transition-group tag="ul">
<li v-for="(item, i) in list" :key="item.id" @click="del(i)">
{{item.id}}---{{item.name}}
</li>
</transition-group>
列表的排序过渡
新增的 v-move 特性,它会在元素的改变定位的过程中应用。像之前的类名一样,可以通过 name
属性来自定义前缀。
Vue定义组件
组件化和模块化的不同:
- 模块化: 是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一;
- 组件化: 是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用;
全局组件定义的三种方式
- 使用 Vue.extend 配合 Vue.component 方法:
Vue.extend{ template: 'HTMLCode' }
返回一个组件模板对象。Vue.component('组件名称', 创建出来的组件模板对象)
var login = Vue.extend({
template: '<h1>登录</h1>'
});
Vue.component('login', login);
- 直接使用 Vue.component 方法:
Vue.component('组件名称', { template: 'HTMLCode' })
Vue.component('register', {
template: '<h1>注册</h1>'
});
- 将模板字符串,定义到script标签(也可以是
<template>
标签,但要放到被控制的组件外使用)中:(常用)
<script id="tmpl" type="x-template">
<div><a href="#">登录</a> | <a href="#">注册</a></div>
</script>
同时,需要使用 Vue.component 来定义组件:
Vue.component('组件名称', 模板id选择器)
Vue.component('account', {
template: '#tmpl'
});
注意: 组件中的DOM结构,有且只能有唯一的根元素(Root Element)来进行包裹!
组件使用
**如果使用 Vue.component
定义全局组件的时候,组件名称使用了驼峰命名,则在引用组件的时候,需要把大写的驼峰改写为小写的字母,两个单词见使用-
链接。**如果不使用驼峰,则直接那名称来使用即可。
<account></account>
定义私有组件(components属性)
- 在声明vm对象中通过
components
选项定义。
...
components: {
account: {
template: '<div><a href="#">登录</a> | <a href="#">注册</a></div>'
}
}
组件中的data(注意)
- 在组件中,
data
需要被定义为一个方法,且必须return对象。 - 在子组件中,如果将模板字符串,定义到了script标签中,那么,要访问子组件身上的
data
属性中的值,需要使用this
来访问.
Vue.component('account', {
template: '#tmpl',
data: function() {
return {
msg: '大家好!'
}
},
methods:{
login: function() {
alert('点击了登录按钮');
}
}
});
使用components
属性定义局部子组件
- 组件实例定义方式:
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
components: { // 定义子组件
account: { // account 组件
template: '<div><h1>这是Account组件{{name}}</h1><login></login></div>'
}, // 在这里使用定义的子组件
components: { // 定义子组件的子组件
login: { // login 组件
template: "<h3>这是登录组件</h3>"
}
}
}
});
</script>
- 引用组件:
<div id="app">
<account></account>
</div>
使用flag
标识符结合v-if
和v-else
切换组件
- 页面结构:
<div id="app">
<input type="button" value="toggle" @click="flag=!flag">
<my-com1 v-if="flag"></my-com1>
<my-com2 v-else="flag"></my-com2>
</div>
- Vue实例定义:
<script>
Vue.component('myCom1', {
template: '<h3>奔波霸</h3>'
})
Vue.component('myCom2', {
template: '<h3>霸波奔</h3>'
})
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {
flag: true
},
methods: {}
});
</script>
动态组件&异步组件
- Vue提供了 compontent,来展示对应名称的组件。
- component是一个占位符, :is属性可以用来指定要展示的组件的名称。
<style>
.v-enter, .v-leave-to {
opacity: 0;
transform: translateX(150px);
}
.v-enter-active, .v-leave-active {
transition: all 0.5s ease;
}
</style>
<div id="app">
<a href="#" @click.prevent="comName='login'">登录</a>
<a href="#" @click.prevent="comName='register'">注册</a>
<!--通过mode属性,设置组件切换时候的模式-->
<transition mode="out-in">
<component :is="comName"></component>
</transition>
</div>
<script>
Vue.component('login', {
template: '<h3>登录组件</h3>'
});
Vue.component('register', {
template: '<h3>注册组件</h3>'
});
var vm = new Vue({
el: '#app',
data: {
// 存放is指定的名称
comName: 'login'
},
methods: {}
})
</script>
在动态组件使用<keep-alive>
当在这些组件之间切换的时候,都会重新渲染一遍;但有种时候切换回来时想保持第一次切换前的画面,则需要在第一次他们被创建的时候缓存下来,可以用一个 <keep-alive>
元素将其动态组件包裹起来。
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
注意这个
<keep-alive>
要求被切换到的组件都有自己的名字,不论是通过组件的name
选项还是局部/全局注册。
异步组件
一般用于大型项目,使用的组件足够多的时候,就需要考虑性能问题,则这时候就好使用异步组件。
Vue.js允许将组建定义为一个工厂函数,动态地解析组件。Vue.js 只是组件需要渲染时触发工厂函数,并且把结果缓存起来,用于后面的再次渲染。
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
注意:当使用webpack和.vue单文件的时候使用异步组件,要注意webpack的配置问题。
处理加载状态
2.3.0+新增
这里的异步组件工厂函数也可以返回一个如下格式的对象:
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
插槽v-slot
在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即
v-slot
指令)。它取代了slot
和slot-scope
。
Vue 2.x 提供了用来访问被slot分发的内容的方法$slot
,用法:this.$slot.具名slot
,不常用。
编译作用域
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}
<!--
这里的 `url` 会是 undefined,因为 "/profile" 是
_传递给_ <navigation-link> 的而不是
在 <navigation-link> 组件*内部*定义的。
-->
</navigation-link>
后备内容(slot的默认内容)
后备内容,只会在没有提供内容的时候被渲染:
例如:
<submit-button></submit-button>
<script>
var btn = Vue.component('submit-button', {
template:
`<button type="submit">
<slot>submit</slot>
</button>`
});
var app = new Vue({
el: '#app',
});
</script>
将被渲染为:
<button type="submit">
Submit
</button>
具名插槽
当想把具体模板插入具体位置,就需用到具名插槽v-slot
。
任何没有被包裹在带有 v-slot
的 <template>
中的内容都会被视为默认插槽的内容,即没有name的slot。
- 在组件模板中的slot提供name
- 使用组件的时候,内部内容添加v-slot
v-slot:插槽名字name
补充:不添加v-slot,其实内部默认添加v-slot:default
例如:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
<script>
var btn = Vue.component('base-layout', {
template:
`<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>`
});
var app = new Vue({
el: '#app',
});
</script>
将被渲染为:
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
作用域插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的。例如,设想一个带有如下模板的 <current-user>
组件:
<span>
<slot>{{ user.lastName }}</slot>
</span>
<current-user>
{{ user.firstName }}
</current-user>
只有
<current-user>
组件可以访问到user
,而user.firstName
是在父组件中渲染的,模板中slot访问的user却是子组件的数据(user)。
<div id="app">
<current-user>
<template v-slot="userSlot">
{{ userSlot.user.firstName }}
</template>
</current-user>
</div>
<!-- 省略引入vue.js -->
<script type="text/javascript">
Vue.component('current-user', {
template:
`<span>
<slot :user='user'>{{ user.lastName }}</slot>
</span>`,
data: function () {
return {
user: {
firstName: '好看',
lastName: '好好好'
}
}
},
});
var vm = new Vue({
el: '#app',
data: {}
});
</script>
解析:
在slot元素上有一个类似 props 传递数据给组件的写法 v-bind:属性名 = 'xxx'
将数据传到了插槽。在父组件中使用 v-slot = props
,这里的props是一个临时变量,保存传递过来的属性,用它来访问来自子组件传递过来的数据。
注意:
v-slot = props
等价于v-slot:default = props
(默认插槽)。2.6版本之前可以使用slot-scope = props
,现已废弃但还可以使用。
具名插槽的缩写
跟 v-on
和 v-bind
一样,v-slot
也有缩写,即把参数之前的所有内容 (v-slot:
) 替换为字符 #
。例如 v-slot:header
可以被重写为 #header
。
和其它指令一样,该缩写只在其有参数的时候才可用。即#="{user}",会报警告
Prop(父向子传值)
- 子组件中,默认无法访问到父组件中的data上的数据和methods中的方法。
- 组件中的所有props中的数据,都是通过父组件传递给子组件的。
- props中的数据都是只读的,data中的数据可读可写的
- props的值可以是两种,一种是字符串数组,一种是对象。
- 父组件,可以在引用子组件的时候,通过属性绑定(v-bind)的形式,把需要传递给子组件的数据,以属性绑定的形式,传递给子组件内部,供子组件使用。
由于HTML特性不区分大小写,当使用DOM模板时,驼峰命名的props名称要转为短横分隔命名。
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。
<div id="app">
<com1 v-bind:parentmsg="msg"></com1>
</div>
- 子组件通过
props
获取数据。
var vm = new Vue({
el: '#app',
components: {
com1: {
template: '<h1>这是子组件 --- {{parentmsg}}</h1>',
props: ['parentmsg']
}
}
});
字符串数字写法
props: ['warningText']
对象写法
Vue.component('my-component', {
props: {
// 必须是数字类型
propA: Number,
// 必须是字符串或数字类型
propB: [String, Number],
// 布尔值,如果没有定义,默认值就是true
propC: {
type: Boolean,
default: true
},
// 数字,而且是必传
propD: {
type: Number,
required: true
},
// 如果是数组或对象,默认值必须是一个函数来返回
propE: {
type: Array,
default: function () {
return [];
}
},
// 自定义一个验证函数
propF: {
validator: function (value) {
return value > 10;
}
}
}
});
单向数据流
Vue.2.x 与 Vue 1.x 比较大的一个改变就是,Vue 2.x 通过 props 传递数据时单向的,也就是父组件数据变化时会传递给子组件,但反过来是不行的。而Vue 1.x 里提供了 .sync 的修饰符来支持双向绑定。
非 Prop 的特性
一个非 prop 特性是指传向一个组件,但是该组件并没有相应 prop 定义的特性。
因为显式定义的 prop 适用于向一个子组件传入信息,然而组件库的作者并不总能预见组件会被用于怎样的场景。这也是为什么组件可以接受任意的特性,而这些特性会被添加到这个组件的根元素上。
例如,想象一下你通过一个 Bootstrap 插件使用了一个第三方的 <bootstrap-date-input>
组件,这个插件需要在其 <input>
上用到一个 data-date-picker
特性。我们可以将这个特性添加到你的组件实例上:
<bootstrap-date-input data-date-picker="activated"></bootstrap-date-input>
然后这个 data-date-picker="activated"
特性就会自动添加到 <bootstrap-date-input>
的根元素上。
替换/合并已有的特性
对于绝大多数特性来说,从外部提供给组件的值会替换掉组件内部设置好的值。比如传入 type="text"
就会替换掉 type="date"
并把它破坏!class
和 style
特性除外,即两边的值会被合并起来。
例:
<bootstrap-date-input>
的模板
<input type="date" class="form-control">
- 然后为这个组件添加一个类
<bootstrap-date-input
data-date-picker="activated"
class="date-picker-theme-dark"
></bootstrap-date-input>
禁用特性继承
如果不希望组件的根元素继承特性,可以在组件的选项中设置 inheritAttrs: false
。例如:
Vue.component('my-component', {
inheritAttrs: false,
// ...
})
这尤其适合配合实例的 $attrs
属性使用,该属性包含了传递给一个组件的特性名和特性值,例如:
{
required: true,
placeholder: 'Enter your username'
}
有了 inheritAttrs: false
和 $attrs
,就可以手动决定这些特性会被赋予哪个元素。在撰写基础组件的时候是常会用到的:
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
`
})
自定义事件(子向父传值)
- 原理:父组件将方法的引用,传递到子组件内部,子组件在内部调用父组件传递过来的方法,同时把要发送给父组件的数据,在调用方法的时候当作参数传递进去。
- 父组件将方法的引用传递给子组件,其中,
show
是父组件中methods
中定义的方法名称,func
是子组件调用传递过来方法时候的方法名称。
<div id="app">
<!--父组件向子组件传递方法,使用的是 事件绑定机制,v-on,当自定义一个 事件属性之后,子组件就能够通过$emit方法获取-->
<com2 @func="show"></com2>
</div>
- 子组件内部通过
this.$emit('方法名', 要传递的数据)
方式,来调用父组件中的方法,同时把数据传递给父组件使用。- $emit第一个参数是父组件传过来的方法,后面的参数可选,为父组件传过来的方法传参。
<div id="app">
<com2 @func="show"></com2>
</div>
<template id="tmp1">
<div>
<h1>这是子组件</h1>
<input type="button" value="子组件的按钮,点击触发父组件传递过来的func方法" @click="myclick">
</div>
</template>
<script>
var com2 = {
template: '#tmp1',
data: function () {
return {
sonmsg: {name: '小头儿子', age: 6}
}
},
methods: {
myclick: function () {
// $emit第一个参数是父组件传过来的方法,后面的参数可选,为父组件传过来的方法传参
this.$emit('func', this.sonmsg);
}
}
}
var vm = new Vue({
el: '#app',
data: {
datamsgFormSon: null
},
methods: {
show: function (data) {
this.datamsgFormSon = data;
}
},
components: {
'com2': com2
}
});
</script>
非父子组件通信
Vue 1.x 版本
- 在 Vue 1.x 中,除了
$emit
方法外,还提供了$dispatch()
和$boroadcast()
这两个方法。$dispatch()
用来向上级派发事件,只要是它的父级(一级或多级以上),都可以在Vue实例的events
选项内接收。 $broadcast()
是由上级向下级广播事件的,用法与$dispatch()
一致,只是方向相反。- 这两个方法一旦发出事件后,任何组件都是可以接收到的,就近原则,而且会在第一次接收到后停止冒泡,除非返回true。
例:(了解)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<!-- 注意:该实例需使用Vue.js 1.x版本 -->
<div id="app">
{{message}}
<my-component></my-component>
</div>
<script type="text/javascript" src="lib/vue.js"></script>
<script type="text/javascript">
Vue.component('my-component', {
template: '<button @click="handleDispatch">派生事件</button>',
methods: {
handleDispatch: function() {
this.$dispatch('on-message', '来自内部组件的数据');
}
}
});
var app = new Vue({
el: '#app',
data: {
message: ''
},
events: {
'on-message': function(msg) {
this.message = msg;
}
}
})
</script>
</body>
</html>
Vue 2.x 版本
- 在vue 2.x 中,使用一个空的Vue 实例座位中央事件总线(bus),也就是一个中介。
- bus 可以实现任何组件间的通信,包括父子、兄弟、跨级。当项目比较大的时候,推荐使用vuex。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
{{message}}
<component-a></component-a>
</div>
<script type="text/javascript" src="lib/vue.js"></script>
<script type="text/javascript">
var bus = new Vue();
Vue.component('component-a', {
template: '<button @click="handleEvent">传递事件</button>',
methods: {
handleEvent: function() {
bus.$emit('on-message', '来自组件component-a的内容');
}
}
});
var app = new Vue({
el: '#app',
data: {
message: ''
},
mounted: function() {
var _this = this;
// 在实例初始化时,监听来自bus实例的事件
bus.$on('on-message', function(msg) {
_this.message = msg;
});
}
});
</script>
</body>
</html>
处理边界情况
访问根实例
在所有子组件中,其根实例可以通过 $root
属性进行访问。
推荐大项目还是使用Vuex来管理。
访问父级组件实例
$parent
属性可以用来从一个子组件访问父组件的实例。
访问子组件实例或子元素
通过 ref
特性为子组件赋予一个 ID 引用(类似html的id)。
<h3 id="myh3" ref="myh3">我怎么这么好看,这么好看怎么办?</h3>
this.$refs.ref名字 // 获取到该组件的DOM对象
$refs
只会在组件渲染完成之后生效,并且它们不是响应式的。应该避免在模板或计算属性中访问$refs
。
依赖注入
使用 $parent
属性无法很好的扩展到更深层级的嵌套组件上(比如突然加多一层父组件则又需要加多一层$parent
)。
依赖注入使用到两个选项:provide
和 inject
。
-
provide
选项允许指定想要提供给后代组件的数据/方法。provide: function () { return { getMap: this.getMap } }
-
在任何后代组件里,可以使用
inject
选项来接收指定的想要添加在这个实例上的属性:inject: ['getMap']
缺点:依赖注入将应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。同时所提供的属性是非响应式的。
混入
基础
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
例:
// 定义一个混入对象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定义一个使用混入对象的组件
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component(); // => "hello from mixin!"
Vue.extend() 局部注册
选项合并
当组件和混入对象含有同名选项时,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。(methods
、components
和 directives
都是如此)
例:
var mixin = {
data: function () {
return {
message: 'hello',
foo: 'abc'
}
}
}
new Vue({
mixins: [mixin],
data: function () {
return {
message: 'goodbye',
bar: 'def'
}
},
created: function () {
console.log(this.$data)
// => { message: "goodbye", foo: "abc", bar: "def" }
}
})
同名钩子函数将合并为一个数组,因此都将被调用。而且混入对象的钩子将在组件自身钩子之前调用。
var mixin = {
created: function () {
console.log('混入对象的钩子被调用')
}
}
new Vue({
mixins: [mixin],
created: function () {
console.log('组件钩子被调用')
}
})
// => "混入对象的钩子被调用"
// => "组件钩子被调用"
全局混入(谨慎使用)
混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// => "hello!"