模版语法
不管是使用v-指令绑定attribute还是Mustache语法,后面可以跟值或者js表达式,如果是事件监听后面可以跟函数或js语句,例如:
<button v-bind:disabled="isButtonDisabled">Button</button>
<a v-bind:href="isButtonDisabled url : ''">...</a>
{{ message.split('').reverse().join('') }}
<el-checkbox @change="getPort"></el-checkbox>
<el-checkbox @change="(val, $event) => getPort(val, event, 'check')"></el-checkbox>
动态属性
上面说的是值是动态的,从2.6.0开始,新增属性动态
<a v-bind:[attributeName]="url"> ... </a>
<a :[attributeName]="url"> ... </a>
<a @[event]="doSomething"> ... </a>
Class和Style绑定
Class对象语法
<div v-bind:class="{ active: isActive }"></div>
<div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>
<div v-bind:class="classObject"></div>
data: {
classObject: {
active: true,
'text-danger': false
}
}
<div v-bind:class="classObject"></div>
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
// 用于自定义组件
<my-component v-bind:class="{ active: isActive }"></my-component>
Class数组语法
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
<div v-bind:class="[isActive activeClass : '', errorClass]"></div>
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
通过以上例子我们总结出如果是对象,那么变量必须是布尔值,如果是数组,那么变量必须是值或js表达式。
Style对象语法
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
Style数组语法
<div v-bind:style="[baseStyles, overridingStyles]"></div>
data () {
return {
baseStyles: {
color: 'red'
},
overridingStyles: {
fontSize: '70px'
}
}
}
从上面我们可以看到如果是对象,变量就是值或js表达式,如果是数组内部对象的变量同样是值或js表达式。
列表渲染
<ul id="example-2">
<li v-for="(item, index) in items" :key="item.message">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
<ul id="example-2">
<li v-for="(item, index) of items" :key="item.message">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
<div v-for="(value, name, index) in object" :key="name">
{{ index }}. {{ name }}: {{ value }}
</div>
data: {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
从上面例子中我们可以看出v-for可以遍历数组也可以遍历对象,遍历数组最多两个参数,遍历对象可以有三个参数,in和of遍历效果一样,只不过of更接近js迭代器语法。v-for的变量只能是值或js表达式,比如从计算属性、方法里面返回的值,也可以是整数例如v-for="n in 10"
在<template>上使用v-for
类似于v-if,也可以利用带有v-for的<template>来循环渲染一段包含多个元素的内容。
比如:
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
v-for和v-if不能在同一个层级使用,但是可以跟v-show一起使用
事件处理
监听事件
v-on指令监听DOM事件,并在触发时运行一些js语句或函数,直接把 JavaScript 代码写在 v-on 指令中是不可行的
下面的例子是一个特例:
<input onKeypress='javascript:if(event.keyCode==32)event.returnValue=false' />
可以写成下面这样
<button v-on:click="counter += 1">Add 1</button>
$event变量
有时候需要在内联语句处理器中访问原始的DOM事件,可以使用特殊变量$event把它传入方法:
Vue模版
<div @click="submit">Submit1</div>
<div @click="submitFile('file', $event)">Submit2</div>
methods: {
submit(event) {
event.preventDefault()
},
submitFile(type, event) {
// 现在我们可以访问原生事件对象
if (event) {
console.log(event)
event.preventDefault()
}
}
}
// 正确写法
<div @change="(val, $event) => getPort(val, event, 'check')"></div>
<el-checkbox @change="(val, $event) => getPort(val, event, 'check')"></el-checkbox>
// 错误写法
<div @change="function(val, $event) {getPort(val, event, 'check')}"></div>
methods: {
getPort (val, event, type) {
console.log(val, event, type)
}
}
我们可以看到触发方法如果没有传参,那么调用方法第一个参数默认是原生DOM对象,也可以将原生DOM对象以参数的形式传递,
事件修饰符
<div @click="clickEvent">
<a @click="test" href="http://www.baidu.com">百度</a> // 触发test、clickEvent、自身事件
<a @click.stop="test" href="http://www.baidu.com">百度</a> // 阻止事件冒泡,触发test、自身事件
<a @click.prevent="test" href="http://www.baidu.com">百度</a> // 阻止a标签默认行为,触发test、clickEvent
<a @click.capture="test" href="http://www.baidu.com">百度</a> // 事件在捕获阶段触发,触发test、clickEvent、自身事件
<a @click.self="test" href="http://www.baidu.com">百度</a> // 事件在目标阶段触发,触发test、clickEvent、自身事件
<a @click.once="test" href="http://www.baidu.com">百度</a> // 点击事件只会触发一次,触发test、clickEvent、自身事件,test事件只会触发一次,其他不影响,不像其它只能对原生的 DOM 事件起作用的修饰符,`.once` 修饰符还能被用到自定义组件事件上,例如<base-checkbox v-model.once="lovingVue"></base-checkbox>
</div>
<div v-on:scroll.passive="onScroll">页面滚动</div> .passive 会告诉浏览器你不想阻止事件的默认行为,.passive 修饰符尤其能够提升移动端的性能。不要把 .passive 和 .prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。
methods: {
clickEvent () {
alert('点击')
},
test () {
alert('测试')
}
}
组件基础
data必须是一个函数
data不是直接提供一个对象,而必须是一个函数,因此每个实例可以维护一份被返回对象的独立拷贝。
在组件上使用v-model
v-model是vue的语法糖
<input v-model="searchText">
// 等价于
<input v-bind:value="searchText" v-on:input="searchText = $event.target.value">
当用在组件上时,v-model 则会这样:
<custom-input v-bind:value="searchText" v-on:input="searchText = $event"></custom-input>
// 等价于
<custom-input v-model="searchText"></custom-input>
为了让它正常工作,这个组件内的 <input> 必须:
- 将其 value attribute 绑定到一个名叫 value 的 prop 上
- 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出
写成代码之后是这样的:
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)">
`
})
Prop
Prop类型
以字符串数组形式列出的 prop:
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
prop 指定的值类型
props: { title: String, likes: Number, isPublished: Boolean, commentIds: Array, author: Object, callback: Function, contactsPromise: Promise // or any other constructor }
传递静态或动态Prop
// 包含该 prop 没有值的情况在内,都意味着 `true`
<blog-post is-published></blog-post>
// 如果你想要将一个对象的所有 property 都作为 prop 传入,你可以使用不带参数的 v-bind
<blog-post v-bind="post"></blog-post>
// 等价于
<blog-post
v-bind:id="post.id"
v-bind:title="post.title">
</blog-post>
单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,如果传递的是基本数据类型,改变子组件的prop会报错,如果传递的是对象或数组 ,改变子组件的prop父组件的值也会被改变。
有两种常见的试图变更一个prop的情形:
1.这个prop用来传递一个初始值,这个子组件接下来希望将其作为一个本地的prop数据来使用。可以在data中将这个prop用作初始值:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
2.这个prop以一种原始的值传入且需要进行转换。这时候最好使用计算属性:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
Prop验证
我们可以为组件的prop指定验证要求,如果需求没有满足则Vue会在浏览器中报错。
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 data、computed 等) 在 default 或 validator 函数中是不可用的。
非Prop的Attribute
一个非prop的attribute是指传向一个组件,但是改组件并没有相应prop定义的attribute。
<bootstrap-date-input data-date-picker="activated"></bootstrap-date-input>
然后这个 data-date-picker="activated" attribute 就会自动添加到 <bootstrap-date-input> 的根元素上。
替换/合并已有的Attribute
想象一下 <bootstrap-date-input> 的模板是这样的:
<input type="date" class="form-control">
为了给我们的日期选择器插件定制一个主题,我们可能需要像这样添加一个特别的类名:
<bootstrap-date-input
type="text"
data-date-picker="activated"
class="date-picker-theme-dark">
</bootstrap-date-input>
最终结果
<input type="text" class="form-control date-picker-theme-dark" data-date-picker="activated">
我们看到内部模版的type被替换成外部传入的,class被合并。class和style会合并内外的值,其他的属性都会被替换成外部传入的值。
禁用Attribute继承
inheritAttrs:默认值为true,默认情况下父作用域不被子组件认作props特性的属性将会作为子组件根元素的属性,inheritAttrs就是去掉这一默认行为,class和style除外。
$attrs
:包含了父作用域中不作为prop被识别的属性(class和style除外)。
inheritAttrs和$attrs可以实现跨组件通信。
<template>
<div>
<p>我是父组件</p>
<test name="tom" :age="12" :id="12345" class="child" />
</div>
</template>
<script>
export default {
components: {
test: {
template: `
<div>
<p>我是子组件</p>
<test2 v-bind="$attrs" s1="sss" s2="sss" />
</div>`,
inheritAttrs: false,
props: ["name"],
created() {
console.log(this.$attrs); // {age: 12, id: 12345}
},
components: {
test2: {
template: `<p>我是孙子组件</p>`,
inheritAttrs: false,
props: ["age", "s1"],
created() {
console.log(this.$attrs); // {s2: "sss", id: 12345}
}
}
}
}
}
};
</script>
自定义组件的v-model
一个组件上的v-model默认会利用名为value的prop和名为input的事件,但是像单选框、复选框等类型的输入控件可能会将value的attribute用于不用的目的。model就是用来改变v-model默认属性和方法
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)">
`
})
父组件
<base-checkbox v-model="lovingVue"></base-checkbox>
.sync修饰符
在有些情况下,我们可能需要对某些数据双向绑定,父子组件数据同步,prop是单向数据流,不建议直接改变prop,以往的做法是将数据传递给子组件,子组件使用$emit触发父组件的监听方法重新赋值,现在这个方法有个语法糖.sync
<text-document v-bind:title="doc.title" v-on:update:title="doc.title = $event"></text-document>
// .sync简写
<text-document :title.sync="doc.title"></text-document>
子组件
<template>
<button @click="changeTitle"></button>
</template>
export default {
props: {
title: String
},
methods: {
changeTitle () {
this.$emit('update:title', newTitle)
}
}
}
v-model和.sync
共同点
- 只能跟变量,都不能跟表达式一起使用
- 都可以实现数据的双向绑定
不同点
- v-model一个组件只能使用一个,.sync一个组件可以使用多个
<text-document :title.sync="doc.title" :name.sync="doc.name"></text-document>
// 可以简写
<text-document :title.sync="doc"></text-document>
具体可参考:https://www.jianshu.com/p/f0673a9eba3f
$listeners
包含了父作用域(不含.native修饰符)v-on事件监听器。它可以通过v-on="$listeners"传入内部组件。它是一个对象,里面包含了作用在这个组件上的所有事件监听器,相当于子组件继承了父组件的事件,可以实现跨组件传递方法。
father.vue组件
<template>
<child :name="name" :age="age" :infoObj="infoObj" @updateInfo="updateInfo" @delInfo="delInfo" />
</template>
child.vue组件
<template>
<grand-son @addInfo="addInfo" v-bind="$attrs" v-on="$listeners" />
// 通过 $listeners 将父作用域中的事件,传入 grandSon 组件,使其可以获取到 father 中的事件
</template>
<script>
import GrandSon from '../components/grandSon.vue'
export default {
components: { GrandSon },
created() {
console.log(this.$listeners); // updateInfo: f, delInfo: f
}
}
</script>
grandSon.vue组件
<template>
<div>
{{ $attrs }} --- {{ $listeners }}
<div>
</template>
<script>
export default {
created() {
console.log(this.$listeners) // updateInfo: f, delInfo: f, addInfo: f
this.$emit('updateInfo') // 可以触发 father 组件中的updateInfo函数
}
}
</script>
处理边界情况
provide和inject
父子组件可以使用prop、$emit实现数据或方法传递,但是针对嵌套多层组件这种情况,除了使用vuex还可以使用依赖注入,我们可以将依赖注入理解成大范围有效的prop,不过依赖注入不是响应式的。
// 祖先元素
export default {
name: "grandfather",
provide(){
return{
foo:'halo'
}
},
}
//后代组件 注入foo
export default {
inject:['foo'],
}
改成响应式的,只需要传递的是对象即可
provide(){
return{
test:this.activeData
}
},
data() {
return {
activeData:{name:'halo'},
}
}
mounted(){
setTimeout(()=>{
this.activeData.name = 'world';
},3000)
}
插槽
父级模版里的所有内容都是在父级作用域中编译的;子模版里的所有内容都是在子作用域中编译的。如果让父组件中的插槽内容访问子组件中数据,就需要用到作用域插槽。绑定在slot元素上的attribute被称为插槽prop,在这个例子中,我们将所有插槽prop的对象命名为slotProps,也可以命名为其他名字。
父组件
<current-user>
<template v-slot:default="slotProps">
{{slotProps.user.firstName}}
</template>
</current-user>
// 解构插槽prop,跟js解构赋值写法一样
<current-user>
<template v-slot:default="{{user}}">
{{user.firstName}}
</template>
</current-user>
子组件
<template>
<section>
<slot v-bind:user="user">{{user.lastName}}</slot>
</section>
</template>
<slot></slot>这个是插槽,v-slot是对应插槽的内容,但是必须添加在template标签上,只有一种情况除外,就是被提供的内容只有默认插槽的时候,组件的标签才可以被当作插槽的模版来使用,这一点和已经废弃的slot attribute不同。
<current-user v-slot:default="slotProps">
{{slotProps.user.firstName}}
</current-user>
动态插槽名
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
具名插槽的缩写
跟v-on和v-bind一样,v-slot也有缩写,v-slot替换为字符#,例如v-slot:header可以被重写为#header
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
使用场景
- 通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理
- 如果父组件在使用到一个复用组件的时候,获取这个组件在不同的地方有少量的更改,如果去重写组件是一件不明智的事情
- 通过slot插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用,比如布局组件、表格列、下拉选择、弹框显示内容等
自定义指令
除了核心功能默认内置的指令(v-model和v-show),Vue也允许注册自定义指令。
一个指令定义对象可以提供如下几个钩子函数(均为可选):
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性初始化设置。
- inserted:被绑定元素插入父节点时调用
- update:所在组件的VNode更新时调用,也可能在其子VNode更新之前调用
- componentUpdated:指令所在组件VNode及其子VNode全部更新后调用
- unbind:只调用一次,指令与元素解绑时调用
钩子函数参数
- el:指令所绑定的元素,可以用来直接操作DOM
- binding:一个对象,包含以下property:
1.name:指令名,不包括v-前缀
2.value:指令的绑定值,例如v-my-directive=“1+1”,绑定值为2
3.oldValue:指令绑定的前一个值
4.expression:字符串形式的指令表达式。例如v-my-directive=“1+1”,表达式为‘1+1’
5.arg:传给指令的参数。例如v-my-directive=“foo”中,参数为‘foo’
6.modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar中,修饰符对象为{foo:true, bar: true} - vnode:Vue编译生成的 虚拟节点
- oldVnode:上一个虚拟节点
除了el之外,其他参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的dataset来进行。
这是一个自定义钩子样例:
<div id="hook-arguments-example" v-demo:foo.a.b="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: '#hook-arguments-example',
data: {
message: 'hello!'
}
})
动态指令参数
指令的参数可以是动态的。例如,在 v-mydirective:[argument]="value" 中,argument 参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。
例如你想要创建一个自定义指令,用来通过固定布局将元素固定在页面上。我们可以像这样创建一个通过指令值来更新竖直位置像素值的自定义指令:
<div id="baseexample">
<p>Scroll down the page</p>
<p v-pin="200">Stick me 200px from the top of the page</p>
</div>
Vue.directive('pin', {
bind: function (el, binding, vnode) {
el.style.position = 'fixed'
el.style.top = binding.value + 'px'
}
})
new Vue({
el: '#baseexample'
})
这会把该元素固定在距离页面顶部 200 像素的位置。但如果场景是我们需要把元素固定在左侧而不是顶部又该怎么办呢?这时使用动态参数就可以非常方便地根据每个组件实例来进行更新。
<div id="dynamicexample">
<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>
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: '#dynamicexample',
data: function () {
return {
direction: 'left'
}
}
})
函数简写
在很多时候,你可能想在 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!"
})