混入(mixins)提供了一种非常灵活的方式来分发vue组件中的可复用功能,一个混入对象可以包含任意组件选项,当组件使用混入对象时,所有混入对象的选项将被混合进入该组件本身的选项。
例如:
let mixins = {
data: function () {
return {
message1: "HelloHH",
message3: "123"
}
},
methods: {
fun1: function () {
console.log("我是f1");
},
fun2: function () {
console.log("我是f2");
}
},
created: function () {
console.log("我是混入对象");
}
};
const vm = new Vue({
el: "#app",
mixins: [mixins],
data: function(){
return {
message1: "Hello",
message2: "World"
}
},
methods:{
fun2: function () {
console.log("我是vm中的f2");
},
fun3: function () {
console.log("我是vm中的f3");
}
},
created: function () {
//钩子函数
console.log("我是vm");
document.getElementById("p1").innerText = this.$data.message1;
}
});
vm.fun1();
vm.fun2();
vm.fun3();
全局混入:混入也可以全局注册,一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// => "hello!"
自定义选项合并策略:自定义选项将适用默认策略,简单覆盖已有的值,如果想自定义合并逻辑,可以向Vue.config.optionMergeStrategies中添加一个函数:
Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
// 合并逻辑
}
对于多数值为对象的选项,可以使用与methods相同的合并策略(直接覆盖):
var strategies = Vue.config.optionMergeStrategies
strategies.myOption = strategies.methods
vue实现了一套内容分发的API,将slot元素作为承载分发内容的出口。
当你想在一个插槽中使用数据时,例如;
<navigation-link url="/profile">
Logged in as {
{ user.name }}
navigation-link>
该插槽和模板的其他地方一样可以访问相同的实例属性(相同的作用域),但是不能访问navigation-link的作用域,例如url就是访问不到的。
tips:父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
单个插槽使用:
<t1>
<h1>{
{name}}h1>
t1>
components: {
"t1": {
template: "<div>" +
"<h1>测试数据h1>" +
"<slot>slot>" +
"div>"
}
}
具名插槽:
<t2>
<template v-slot:header>
<h1>Hello Header Messageh1>
template>
<template v-slot:default>
<h1>主要内容h1>
template>
<template v-slot:footer>
<h1>Hello Footer Messageh1>
template>
t2>
"t2": {
template: "<div class='container'>" +
"<header><slot name='header'>slot>header>" +
"<main><slot name='default'>slot>main>" +
"<footer><slot name='footer'>slot>footer>" +
"div>"
}
v-slot只能在template标签
上使用,slot则不一样。
作用域插槽:有时候让插槽内容能够访问子组件中才有的数据是很有用的,作用域插槽在解决需要动态生成字符串模板时非常有用。
<t3 v-bind:list="contentList">
<template v-slot:thead>
<tr>
<td>编号td>
<td>内容td>
<td>状态td>
tr>
template>
<template slot-scope="{item}" slot="tbody">
<td>{
{item.id}}td>
<td>{
{item.text}}td>
<td>{
{item.isALive}}td>
template>
t3>
"t3": {
props: ["list"],
template: "<table border='1px'>" +
"<thead>" +
"<slot name='thead'>slot>" +
"thead>" +
"<tbody>" +
"<tr v-for='item in list'>" +
"<slot name='tbody' v-bind:item='item'>slot>" +
"tr>" +
"tbody>" +
"table>"
}
在父组件中只需要在thead中指定具体内容,对应每个tbody中tr每个td的字段绑定,其他交给组件处理即可,其中数据源是list属性,与slot通信是通过slot-scope来实现数据传递,组件中 v-bind:item=“item” 与父组件 slot-scope="{item}" 完成数据访问的传递。(注意v-slot不能同时和slot-scope一起使用,因为v-slot是属于2.6.0开始的新语法,而slot-scope是旧语法)。
和v-on、v-bind一样,v-slot也有缩写,例如:v-slot:header就可以携程#header,插槽没有name的话可以写为:#default。
除了核心功能默认内置的指令(v-model和v-show),vue也允许注册自定义指令,但是在有的情况下需要对普通dom元素进行操作,这时我们就可以使用自定义指令,例如:
一打开页面就聚焦输入框:
请输入:<input type="text" v-focus>
directives: {
//自定义指令
focus: {
//inserted:在被绑定元素插入父节点时调用
inserted: function (el) {
el.focus();
}
}
}
钩子函数
:
钩子函数参数
:
简单使用:
<div id="app" v-demo:foo.a.b="message">
div>
demo: {
//只调用一次
bind: function (el,binding,vnode) {
let str = JSON.stringify;
el.innerHTML =
'name: ' + str(binding.name) + '<br>' +
'value: ' + str(binding.value) + '<br>' +
'expression: ' + str(binding.expression) + '<br>' +
'argument: ' + str(binding.arg) + '<br>' +
'modifiers: ' + str(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
}
动态指令参数:指令的参数可以是动态的。
例如:
<p v-internal="200">我是一段文本p>
internal: {
bind: function (el,binding,vnode) {
el.style.position = "fixed"; //固定
el.style.top = binding.value + "px";
}
}
<p v-internal1:[direction]="300">我也是一段文本p>
internal1: {
bind: function (el,binding,vnode) {
el.style.position = "fixed";
let str = binding.arg === "left"?"left":"top";
el.style[str] = binding.value + "px";
}
}
函数简写:
//在bind和update时触发相同行为
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
传入多个参数:(指令函数能够接受所有合法的JavaScript表达式)
<p v-test1="{a: 'red',b: 'blue'}">p>
Vue.directive('test1', function (el, binding) {
console.log(binding.value.a) // => "white"
console.log(binding.value.b) // => "hello!"
})
vue允许我们自定义过滤器,可被用于一些常见的文本格式化,过滤器可以用在两个地方:双花括号赋值和v-bind表达式:
{
{ message | capitalize }}
<div v-bind:id="rawId | formatId">div>
自定义过滤器例子:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue学习title>
head>
<body>
<div id="app">
<h1>
{
{time | MyFilter1("yyyy-mm-dd")}}
h1>
<h2>
{
{"hello" | toUpper()}}
h2>
div>
body>
<script src="../js/vue.js">script>
<script type="text/javascript">
Vue.filter("MyFilter1",function (input,pattern="") {
let date = new Date(input);
let year = date.getFullYear();
let month = (date.getMonth() + 1).toString().padStart(2,"0");
//padStart(arg0,arg1),第一个参数指定字符串的最小长度
//第二个参数是字符串长度不足时添加的字符串,padStart添加在首部,而padEnd添加在尾部
let day = (date.getDay() + 1).toString().padStart(2,"0");
if(pattern === "yyyy-mm-dd"){
return year+"-"+month+"-"+day;
}else{
let hours = date.getHours().toString().padStart(2,"0");
let minutes = date.getMinutes().toString().padStart(2,"0");
let seconds = date.getSeconds().toString().padStart(2,"0");
return year+"-"+month+"-"+day+" "+hours+":"+minutes+":"+seconds;
}
// console.log(year+','+month+','+day);
});
const vm = new Vue({
el: "#app",
data: {
time: 1575182777000 //毫秒
},
filters: {
toUpper: function (str) {
if(str === "" || str ==null) return "";
return str.charAt(0).toUpperCase() + str.slice(1); //首字母大写
}
}
});
script>
html>
过滤器函数总接收表达式的值(之前的操作链的结果)作为第一个参数,例如:下面的例子,filterA会将message作为第一个参数,然后filterB会将filterA的结果作为第一个参数,arg0和arg1作为第二个和第三个参数。
{
{message | filterA | filterB(arg0,arg1)}}
vue在插入、更新和移除dom时,提供多种不同方式的应用过渡效果。
包括:
vue提供了transition的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡。
例如:
<div id="app">
<button @click="isShow=!isShow">点我button>
<transition name="fade">
<p v-if="isShow">Hellop>
transition>
div>
body>
<script src="../js/vue.js">script>
<script type="text/javascript">
const vm = new Vue({
el: "#app",
data: {
isShow: true
}
});
script>
<style>
/* 定义开始和结束状态 */
.fade-enter,.fade-leave-to{
opacity: 0;
}
/* 定义进入和离开过渡过程中需要的时间 */
.fade-enter-active,.fade-leave-active{
transition: opacity 5s;
}
style>
当插入和删除包含在transition组件中的元素时,vue将会做以下处理:
过渡的类名:
在进入/离开的过渡中,会有6个class切换:
1.v-enter:定义进入过渡的开始状态,在元素被插入之前生效,在元素被插入之后的下一帧移除。
2.v-enter-active:定义进入过渡生效时的状态,在整合进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除,这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数
3.v-enter-to: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
4.v-leave: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
5.v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
6.v-leave-to: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。
对于这些在过渡中切换的类名来说,如果你使用一个没有name的transition,则v是默认的前缀,有name的话,name作为默认前缀。
css过渡:
<transition name="side-fade">
<p v-if="isShow">Worldp>
transition>
.side-fade-enter,.side-fade-leave-to{
transform: translateX(10px); /* 移动10px */
opacity: 0;
}
/* 进入动画 */
.side-fade-enter-active{
transition: all .3s ease;
}
/* 离开动画 */
.side-fade-leave-active{
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
css动画:css动画用法同css过渡区别是在动画中v-enter类名在节点插入DOM后不会立即删除,而是在animationend事件触发后删除。
<transition name="bounce">
<p v-if="isShow">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.p>
transition>
.bounce-enter-active {
animation: bounce-in .5s;
}
.bounce-leave-active {
animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
自定义过渡的类名:
我们可以通过以下特性来自定义过渡类名:
他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用十分有用,例如:
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/[email protected]">
<transition
name="custom-classes-transition"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight"
>
<p v-if="isShow">hellop>
transition>
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"
>
// html
transition>
<script>
// ...
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) {
// ...
}
}
script>
tips:当只用 JavaScript 过渡的时候,在 enter 和 leave 中必须使用 done 进行回调。否则,它们将被同步调用,过渡会立即完成;推荐对于仅使用 JavaScript 过渡的元素添加 v-bind:css=“false”,Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在属性被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
tips:受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),Vue 无法检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如:
var vm = new Vue({
data:{
a:1
}
})
// `vm.a` 是响应式的
vm.b = 2
// `vm.b` 是非响应式的
对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象
添加响应式属性。例如:
Vue.set(vm.someObject, 'b', 2)
同:
this.$set(this.someObject,'b',2)
有时你可能需要为已有对象赋值多个新属性,比如使用 Object.assign() 或 _.extend()。但是,这样添加到对象上的新属性不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的属性一起创建一个新的对象。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
声明响应式属性:由于 Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明所有根级响应式属性,哪怕只是一个空值:
var vm = new Vue({
data: {
// 声明 message 为一个空值字符串
message: ''
},
template: '<div>{
{ message }}div>'
})
// 之后设置 `message`
vm.message = 'Hello!'
//这样的限制在背后是有其技术原因的,它消除了在依赖项跟踪系统中的一类边界情况,也使 Vue 实例能更好地配合类型检查系统工作。但与此同时在代码可维护性方面也有一点重要的考虑:data 对象就像组件状态的结构 (schema)。提前声明所有的响应式属性,可以让组件代码在未来修改或给其他开发人员阅读时更易于理解。
异步更新队列:
可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
例如,当你设置 vm.someData = ‘new value’,该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick” 中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。例如:
<div id="example">{
{message}}div>
<script>
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})
script>
在组件内使用 vm.$nextTick() 实例方法特别方便,因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上:
Vue.component('example', {
template: '<span>{
{ message }}span>',
data: function () {
return {
message: '未更新'
}
},
methods: {
updateMessage: function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
this.$nextTick(function () {
console.log(this.$el.textContent) // => '已更新'
})
}
}
})
因为 $nextTick() 返回一个 Promise 对象,所以你可以使用新的 ES2017 async/await 语法完成相同的事情:
methods: {
updateMessage: async function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
await this.$nextTick()
console.log(this.$el.textContent) // => '已更新'
}
}