@dungan
2021-03-13T05:30:20.000000Z
字数 20953
阅读 150
前端
vue 是一个 MVVM 库,学习 Vue 时请先抛开手动操作 DOM 的思维,因为Vue是数据驱动的, 你无需手动操作 DOM。它通过一些特殊的 HTML 语法,将 DOM 和数据绑定起来。一旦你创建了绑定,DOM 将和数据保持同步, 每当变更了数据,DOM 也会相应地更新。
MVVM模式(Model-View-ViewMode)
MVVM
是前端视图层的分层开发思想,分为 M
、V
和 VM
:
- Model :数据模型,当我们更新 Model 中的数据时,页面中的 DOM 元素也会更新。
- View:视图层,它负责将数据模型转化成 UI 展现出来。
- ViewModel:是一个同步View 和 Model的对象,ViewModel 是 Vue 的核心,它是一个Vue实例。ViewModel 会监听自己身上 data 中所有的数据,只要数据发生变化,就自动把新数据同步到页面中。
使用 Vue 的过程就是定义 MVVM 各个组成部分的过程:
<!--定义 View-->
<div id="app">
{{ message }}
</div>
<script>
// 定义 Model
var exampleData = {
message: 'Hello World!'
}
// 定义 ViewModel(Vue实例)
new Vue({
el: '#app',
data: exampleData
})
</script>
每个 Vue 应用都需要通过实例化 Vue 来实现,Vue实例会向 Vue 的响应式系统中加入了其 data 对象中能找到的所有的属性。当这些属性的值发生改变时,html 视图将也会产生相应的变化。
格式如下:
var vm = new Vue({
el: '#app',
data: {},
methods:{},
computed:{},
capitalize:{}
});
实例属性说明:
- el :元素选择器,这意味着我们接下来的改动全部在 el 这个元素内,el 外部不受影响。
- data :用于定义 el 元素中的数据来源。
- methods :用于定义函数,事件处理函数置于该处。
- computed:用来定义计算属性函数,用在处理一些复杂逻辑时是很有用的。
- capitalize:用来自定义过滤器函数,被用作一些常见的文本格式化
<div id="app">
<h1>site : {{site}}</h1>
<h1>url : {{url}}</h1>
<h1>Alexa : {{alexa}}</h1>
<h1>hello : {{say()}}</h1>
</div>
<script type="text/javascript">
// 我们的数据对象
var data = {
site: "菜鸟教程",
url: "www.runoob.com",
alexa: 10000
}
var vm = new Vue({
el: '#app',
data: data,
methods:{
say : function(){
return 'vue!';
}
}
})
document.write(vm.$data === data) // true
document.write("<br>")
document.write(vm.$el === document.getElementById('app')) // true
</script>
vue 的实例属性可以通过前缀 $ 来访问,以便与用户定义的属性区分开来。例如:
vm.$data
vm.$el
指令是带有 v- 前缀的特殊html属性,指令后面的参数是正常的html属性。
用来绑定html属性值,响应地更新html属性。
<div id="app">
<label for="r1">修改颜色</label>
<input type="checkbox" v-model="use" id="r1">
<br><br>
<div v-bind:class="{'class1': use}">
v-bind:class 指令
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
use: false
}
});
</script>
上例中的 v-bind:class
,v-bind
就是指令,而 class
就是指令的参数。
主要用在有用户输入的表单场景,v-model有点像表单值和元素绑定值之间的中介,根据表单上的值,自动更新绑定的元素的值。常用在 input、select,textarea、checkbox、radio 等表单控件元素上创建双向数据绑定。
<div id="app">
<p>{{ message }}</p>
<input v-model="message">
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'Runoob!'
}
})
</script>
用来绑定事件, v-on 指令还支持事件修饰符
来控制事件行为,例如阻止事件冒泡等等。例如 .prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()。
<div id="app">
<p>{{ message }}</p>
<button v-on:click.prevent="reverseMessage">反转字符串</button>
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'Runoob!'
},
methods: {
reverseMessage: function() {
this.message = this.message.split('').reverse().join('')
}
}
})
</script>
Vue为 v-bind 和 v-on 这两个最为常用的指令提供了特别的缩写:
<!-- 完整语法 -->
<a v-bind:href="url"></a >
<!-- 缩写 -->
<a :href="url"></a >
<!-- 完整语法 -->
<a v-on:click="doSomething"></a >
<!-- 缩写 -->
<a @click="doSomething"></a >
用来输出html代码,正常情况下 vue 为了防止 xss,会对html标签进行转义,如果你想输出html,就使用 v-html 指令。
<div id="app">
<div v-html="message"></div>
</div>
<script>
new Vue({
el: '#app',
data: {
message: '<h1>菜鸟教程</h1>'
}
})
</script>
vue 的文本插值中提供了完全的 JavaScript 表达式支持。
<div id="app">
{{5+5}}<br>
{{ ok ? 'YES' : 'NO' }}<br>
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id">菜鸟教程</div>
</div>
<script>
new Vue({
el: '#app',
data: {
ok: true,
message: 'RUNOOB',
id: 1
}
})
</script>
过滤器用在一些常见的文本格式化。由管道符
指示, 格式如下:
<!-- 在两个大括号中 -->
{{ message | capitalize }}
<!-- 在 v-bind 指令中 -->
<div v-bind:id="rawId | formatId"></div>
<!-- 在两个大括号中 -->
{{ message | capitalize }}
<!-- 在 v-bind 指令中 -->
<div v-bind:id="rawId | formatId"></div>
<!-- 多个过滤器 -->
{{ message | filterA | filterB }}
示例:
<div id="app">
{{ message | capitalize }}
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'runoob'
},
filters: {
capitalize: function(value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
})
</script>
计算属性在处理一些复杂逻辑时是很有用的。例如当你想要在模板中需要多处翻转字符串时,在模板中放入太多的逻辑会让模板过重且难以维护。
例如:
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
而有了计算属性后,我们就可以这样。
<div id="app">
<p>原始字符串: {{ message }}</p>
<p>模板反转字符串: {{ message.split('').reverse().join('') }}</p>
<p>使用方法反转字符串: {{ reversedMessage2() }}</p>
<p>计算属性反转字符串: {{ reversedMessage }}</p>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
message: 'Runoob!'
},
computed: {
reversedMessage: function() {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
},
methods: {
reversedMessage2: function() {
return this.message.split('').reverse().join('')
}
}
})
</script>
可以看到还可以使用 methods 来替代 computed,只是 computed基于它的依赖缓存, 只有相关依赖发生改变时才会重新取值。而使用 methods 在重新渲染的时候,函数总会重新调用执行。
computed 属性默认只有 getter ,不过在需要时你也可以提供一个 setter :
<div id="app">
<p>{{ site }}</p>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
name: 'Google',
url: 'http://www.google.com'
},
computed: {
site: {
// getter
get: function() {
return this.name + ' ' + this.url
},
// setter
set: function(newValue) {
var names = newValue.split(' ')
this.name = names[0]
this.url = names[names.length - 1]
}
}
}
})
// 设置 vim.site 会触发 setter 函数
vm.site = '菜鸟教程 http://www.runoob.com';
// 读取 vim.site 会触发 getter 函数
document.write('site:' + vm.site);
document.write('<br>');
document.write('name: ' + vm.name);
document.write('<br>');
document.write('url: ' + vm.url);
</script>
监听属性用在当需要在数据变化时执行异步或开销较大的操作这种场景。
<div id="app">
<p style="font-size:25px;">计数器: {{ counter }}</p>
<button @click="counter++" style="font-size:25px;">点我</button>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
counter: 1
},
watch: {
counter: function(nval, oval) {
alert('计数器值的变化 :' + oval + ' 变为 ' + nval + '!');
}
}
});
</script>
可以用 v-bind 来设置样式属性,表达式的结果类型除了字符串之外,还可以是对象或数组。
<div id="app">
<div v-bind:class="{ active: isActive }"></div>
</div>
<script>
new Vue({
el: '#app',
data: {
isActive: true
}
})
</script>
以上示例中,如果isActive
的值为true,则会为该元div素设置一个叫做 activite
的class:
<div class="active"></div>
还可以在对象中传入更多属性用来动态设置多个 class 。
<style>
.active {
width: 500px;
height: 500px;
background: green;
}
.bigSize {
font-size: 100px;
}
</style>
<div id="app">
<div class="static" v-bind:class="{ active: isActive, 'bigSize': isBig }">
hello vue
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
isActive: true,
isBig: true
}
})
</script>
那么解析后的div标签会是这样:
<div class="static active bigSize"> hello vue </div>
还可以通过计算属性来返回绑定的样式对象。
<style>
.active {
width: 500px;
height: 500px;
background: #FF0000;
}
.bigSize {
font-size: 100px;
}
.smallSize {
font-size: 20px;
}
</style>
<div id="app">
设置字体大小 <input type="text" v-model="fontSize" />
<div v-bind:class="classObject">
{{ fontSize }}
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
isActive: true,
fontSize: 0
},
computed: {
classObject: function() {
let obj = {
active: true
}
if (this.fontSize > 50)
obj['bigSize'] = true;
else
obj['smallSize'] = true;
return obj;
}
}
})
</script>
还可以通过数组来设置多个class。
<style>
.active {
width: 500px;
height: 500px;
background: #FF0000;
}
.size {
font-size: 200px;
}
</style>
<div id="app">
<div v-bind:class="[activeClass, sizeClass]">
{{ str }}
</div>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
str: 'hello vue',
activeClass: 'active',
sizeClass: 'size',
}
});
</script>
还可以使用三元表达式来切换列表中的 class。
<div v-bind:class="[errorClass ,isActive ? activeClass : '']"></div>
条件语句v-if/v-else-if/v-else
用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true 值的时候被渲染。
<div id="app">
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else-if="loginType === 'phone'">
<label>phone</label>
<input placeholder="Enter your phone">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
<select v-model="loginType">
<option value="username">用户名登录</option>
<option value="email">邮箱登录</option>
<option value="phone">电话登录</option>
</select>
</div>
<script>
new Vue({
el: '#app',
data: {
loginType: "email"
}
})
</script>
注意:因为 v-if 是一个指令,所以必须将它挂载到一个元素上。但是如果想切换多个元素呢?此时可以把一个 <template>
元素当做不可见的包裹元素,并在上面使用 v-if。但最终的渲染结果将不包含<template>
元素。
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
还可以使用 v-show
来控制的元素的显示,不同于 v-if/v-else-if/v-else
的是,带有 v-show 只是简单地切换元素的 CSS display 属性。
<div id="app">
<h1 v-show="ok">Hello!</h1>
</div>
<script>
new Vue({
el: '#app',
data: {
ok: false
}
})
</script>
基于 v-for
指令可以对列表循环输出。
<ul id="app">
<li v-for="(item, index) in items" :key="index">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
<script>
new Vue({
el: '#app',
data: {
parentMessage: 'Parent',
items: [{
message: 'Foo'
},
{
message: 'Bar'
}
]
}
})
</script>
也可以用 of 替代 in 作为分隔符,因为它更接近 JavaScript 迭代器的语法:
<div v-for="item of items"></div>
<ul id="app" class="demo">
<li v-for="(value, key) in object">
{{ key }} : {{ value }}
</li>
</ul>
<script>
new Vue({
el: '#app',
data: {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
})
</script>
由于 v-for 渲染的元素列表时,如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序。
为了给 Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性(建议尽可能在使用 v-for 时提供 key 属性):
<div v-for="item in items" v-bind:key="item.id">
<!-- 内容 -->
</div>
事件监听可以使用 v-on 指令。
通常情况下,我们需要使用一个方法来调用 JavaScript 方法。v-on 可以接收一个定义的方法来调用。
<div id="app">
<button v-on:click="greet">Greet</button>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
name: 'Vue.js'
},
methods: {
greet: function(event) {
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
}
})
// 也可以用 JavaScript 直接调用方法
app.greet() // -> 'Hello Vue.js!'
</script>
除了直接绑定到一个方法,也可以用内联 JavaScript 语句,如果需要在内联语句处理器中访问原始的 DOM 事件,可以用特殊变量 $event 把它传入方法:
<div id="app">
<button v-on:click="say('hi', $event)">Say hi</button>
</div>
<script>
new Vue({
el: '#app',
methods: {
say: function(message, event) {
console.log(message);
if (event) {
console.log(event.target.tagName);
}
}
}
})
</script>
事件修饰符用来处理 DOM 事件细节,如:event.preventDefault() 或 event.stopPropagation()。
可以通过由点(.)表示的指令后缀来调用修饰符:
- .stop
- .prevent
- .capture
- .self
- .once
<!-- 阻止单击事件冒泡 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 多个修饰符 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件侦听器时使用事件捕获模式 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当事件在该元素本身(而不是子元素)触发时触发回调 -->
<div v-on:click.self="doThat">...</div>
<!-- 事件只触发一次 -->
<a v-on:click.once="doThis"></a>
注意多个修饰符时顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self
会阻止所有的点击,而 v-on:click.self.prevent
只会阻止对元素自身的点击。
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:
<!-- 只有按键的 keyCode 是 13 时才调用 vm.submit() -->
<input v-on:keyup.13="submit">
记住所有的 keyCode 比较困难,所以 Vue 为最常用的按键提供了别名:
<!-- 只有按键是 `Enter` 时才调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
<!-- 缩写语法 -->
<input @keyup.enter="submit">
<!-- 按住ctrl建点击鼠标时才触发 -->
<div @click.ctrl="doSomething">Do something</div>
全部的按键别名:
- .enter
- .tab
- .delete (捕获 "删除" 和 "退格" 键)
- .esc
- .space
- .up
- .down
- .left
- .right
- .ctrl
- .alt
- .shift
- .meta,田(windows)/command(Mac)
Vue 使用 v-model 指令在表单控件元素上创建双向数据绑定。
v-model 会忽略所有表单元素上的 value、checked、selected 等属性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
<div id="app">
<p>input 元素:</p>
<input v-model="message">
<p>消息是: {{ message }}</p>
<p>textarea 元素:</p>
<p>{{ message2 }}</p>
<textarea v-model="message2"></textarea>
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'hello',
message2: 'world'
}
})
</script>
复选框如果是一个则为bool值,如果是多个则绑定到同一个数组。
<div id="app">
<p>单个复选框:</p>
<input type="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
<p>多个复选框:</p>
<input type="checkbox" value="Runoob" v-model="checkedNames">
<label for="runoob">Runoob</label>
<input type="checkbox" value="Google" v-model="checkedNames">
<label for="google">Google</label>
<input type="checkbox" value="Taobao" v-model="checkedNames">
<label for="taobao">taobao</label>
<br>
<span>选择的值为: {{ checkedNames }}</span>
</div>
<script>
new Vue({
el: '#app',
data: {
checked: false,
checkedNames: []
}
})
</script>
<div id="app">
<input type="radio" value="Runoob" v-model="picked">
<label for="runoob">Runoob</label>
<br>
<input type="radio" value="Google" v-model="picked">
<label for="google">Google</label>
<br>
<span>选中值为: {{ picked }}</span>
</div>
<script>
new Vue({
el: '#app',
data: {
picked: 'Runoob'
}
})
</script>
<div id="app">
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
<script>
new Vue({
el: '#app',
data: {
selected: ''
}
})
</script>
v-for 动态渲染下拉框选项:
<div id="app">
<select v-model="selected">
<option v-for="option of options" v-bind:value="option">
{{ option }}
</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
<script type="text/javascript">
new Vue({
el: "#app",
data: {
selected: 'A',
options: ['A', 'B', 'C']
}
})
</script>
修饰符用在表单值的格式化处理上,例如去除空格,字符串转为数字等等。
在默认情况下, v-model 在 input 事件中同步输入框的值与数据;但你可以添加一个修饰符 lazy ,从而转变为在 change 事件中同步。
<!-- 在 "change" 而不是 "input" 事件中更新 -->
<input v-model.lazy="msg" >
将输入值转为 Number 类型(如果原值的转换结果为 NaN 则返回原值)。
input v-model.number="age" type="number">
自动过滤用户输入的首尾空格。
<input v-model.trim="msg">
组件是可复用的 Vue 实例,几乎任意类型的应用界面都可以抽象为一个组件树。在一个大型应用中,有必要将整个应用程序划分为组件,以使开发更易管理。
Vue 组件非常类似于自定义元素,组件名推荐字母全小写且必须包含一个连字符:
<div id="app">
<app-nav></app-nav>
<app-view>
<app-sidebar></app-sidebar>
<app-content></app-content>
</app-view>
</div>
全局组件能够被所有Vue 实例共享,全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生。
<div id="app">
<common></common>
</div>
<script>
// 注册全局组件
Vue.component('common', {
template: '<h1>全局组件!</h1>'
})
new Vue({
el: '#app'
})
</script>
注册在实例选项中的组件称为局部组件,局部组件只能在当前实例中使用。
<div id="app">
<child></child>
</div>
<script>
new Vue({
el: '#app',
// 注册局部组件
components: {
// <child> 组件将只在父模板可用
'child': {
template: '<h1>局部组件!</h1>'
}
}
})
</script>
props
用来解决父传子这种问题,将父组件数据传给子组件,子组件需要显式地在 props 选项里声明要收的 属性
。props 是子组件用来接受父组件传递过来的数据的一个自定义属性。
注意 :props 是单向绑定的,当父组件的属性变化时,将传导给子组件,但是不会反过来。
<div id="app">
<Common message="tcl"></Common>
</div>
<script>
// 注册
Vue.component('Common', {
// 申明 props,这里的 message 就是 prop
props: ['message'],
template: '<span>hello {{ message }}</span>'
})
new Vue({
el: '#app'
})
</script>
组件中的用到的数据都是从 props 这里获取的,可以将组件中的 props 属性可以看作是类似 Vue 实例中的data(数据模型)。
通常情况下我们希望每当父组件的数据变化时,该变化也会传导给子组件,这时就可以用 v-bind 动态绑定 props 的值到父组件的数据中。
<div id="app">
<common v-bind:message="msg"></common>
</div>
<script>
// 注册
Vue.component('common', {
// 声明 props
props: ['message'],
template: '<span>hello {{ message }}</span>'
})
new Vue({
el: '#app',
data: {
msg: "tcl"
}
})
</script>
使用 v-bind 指令将 todo 传到每一个重复的组件中:
<div id="app">
<ol>
<todo-item v-for="item in sites" v-bind:todo="item"></todo-item>
</ol>
</div>
<script>
Vue.component('todo-item', {
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
});
new Vue({
el: '#app',
data: {
sites: [
{
text: 'AAA'
},
{
text: 'BBB'
},
{
text: 'CCC'
}
]
}
});
</script>
当组件变得越来越复杂的时候,为每个相关的信息定义一个 prop
会变得很麻烦:
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:content="post.content"
v-bind:publishedAt="post.publishedAt"
v-bind:comments="post.comments"
></blog-post>
可以将这个组件重构成让它变成接受一个单独的 post
prop:
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>`
})
父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
但如果你确实有在子组件内部修改 prop 的情形,在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
父组件是使用 props 传递数据给子组件,但如果子组件要把数据传递回去,就需要使用自定义事件
。
每个 Vue 实例都实现了事件接口(Events interface):
- 使用 $on(eventName) 监听事件
- 使用 $emit(eventName) 触发事件
父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件; 这样一来子组件就已经和它外部完全解耦了,它所做的只是触发一个父组件关心的内部事件。
示例一 :
有的时候用一个事件来抛出一个特定的值是非常有用的。例如我们可能想让 组件决定它的文本要放大多少。这时可以使用 $emit 的第二个参数来提供这个值:
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
然后当在父级组件监听这个事件的时候,我们可以通过 $event 访问到被抛出的这个值:
<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>
或者,如果这个事件处理函数是一个方法:
<blog-post
...
v-on:enlarge-text="onEnlargeText"
></blog-post>
那么这个值将会作为第一个参数传入这个方法:
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
示例二 :
<div id="app">
<div>
<p>{{ total }}</p>
<!-- 监听子组件触发的 increment 事件,然后调用事件处理方法 incrementTotal -->
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
</div>
<script>
Vue.component('button-counter', {
template: '<button v-on:click="incrementHandler">{{ counter }}</button>',
data: function() {
return {
counter: 0
}
},
methods: {
incrementHandler: function() {
this.counter += 1
// 触发 increment 事件
this.$emit('increment', 10)
}
},
})
new Vue({
el: '#app',
data: {
total: 0
},
methods: {
// 如果 $emit() 有第二个参数,那么这个值会被传入事件处理函数
incrementTotal: function(n) {
this.total += 1 + n;
}
}
})
</script>
在界面你点击按钮会发现,每个按钮上的数字并不会受对方影响,每个组件间的数据变化是隔离的,这是因为 button-counter 组件中的 data 不是一个对象,而是一个函数。
每个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。
data: function () {
return {
count: 0
}
}
某些情况下你可能想在组件的根元素上监听一个原生事件,可以使用 .native 修饰 v-on :
<my-component v-on:click.native="doTheThing"></my-component>
在组件上使用 v-model 实现双向绑定时,组件内部需做些特殊设置。
假设有一个自定义组件:
<custom-input v-model="searchText"></custom-input>
为了使 v-model 在这个组件上正常工作的工作,这个组件内的 <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)"
>
`
})
但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的,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)"
>
`
})
现在在这个组件上使用 v-model 的时候:
<base-checkbox v-model="lovingVue"></base-checkbox>
这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox>
触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新。
注意:你仍然需要在组件的 props 选项里声明 checked 这个 prop。
子组件向父组件传递数据除了使用 $emit
抛出值外,Vue 还提供了 .sync
修饰符来实现组件上 prop 的数据同步,即对组件上的 prop
进行双向绑定
。
原理如下 :
在父组件中对传递给子组件的 prop 使用.sync修饰符,子组件改变prop后,以 this.$emit(‘update: propName’, newvalue)的模式触发事件,告诉父组件我已经更改这个prop值了。
父组件中对传递给子组件的prop使用.sync修饰符。
<select-tree :parent-id.sync="parentId"></select-tree>
子组件中通过this.$emit('update: propName', newvalue)
来同步prop的值。
<template>
<el-select v-model="value" @change="selectChange" placeholder="请选择">
...
</el-select>
</template>
<script>
export default {
props : ['parentId'],
data() {
return {
optionList : [],
value : this.parentId
};
},
methods : {
selectChange(val) {
this.$emit('update:parentId', val)
}
}
}
</script>
组件名强烈推荐字母全小写且必须包含一个连字符,而不是首字母大写的方式,这会帮助你避免和当前以及未来的 HTML 元素相冲突 :
//bad case
Vue.component('MyComponentName', { /* ... */ })
//good case(推荐这种)
Vue.component('my-component-name', { /* ... */ })
<my-component-name></<my-component-name>
由于浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,驼峰法命名的 prop 在模板中应该以短横线分隔的命名来引用。
Vue.component('blog-post', {
// 在 JavaScript 中是驼峰法
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是以短横线分割的命名来引用的 -->
<blog-post post-title="hello!"></blog-post>
v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent,因此,推荐始终使用短横线连接的事件名。
this.$emit('my-event')
<!-- 监听事件 -->
<my-component v-on:my-event="doSomething"></my-component>
混入 (mixins)定义了一部分可复用的方法或者计算属性。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。
<div id="app"></div>
<script type="text/javascript">
// 定义一个混入对象
var myMixin = {
created: function() {
this.startmixin()
},
methods: {
startmixin: function() {
document.write("欢迎来到混入实例");
}
}
};
//混入对象
var vm = new Vue({
el: '#app',
data: {},
methods: {},
mixins: [myMixin]
});
</script>
当组件和混入对象含有同名选项时,这些选项将以恰当的方式混合。
注意:同名钩子函数混入的情况下,混入对象的钩子函数将在组件自身钩子之前调用。
数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先:
<script type="text/javascript">
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" }
}
})
</script>
同名钩子函数将合并为一个数组,混入对象的钩子函数将在组件自身钩子之前调用:
<div id="app"></div>
<script type="text/javascript">
var mixin = {
//混入对象的钩子函数将在组件自身钩子之前调用
created: function() {
document.write('混入调用' + '<br>')
}
}
new Vue({
el: "#app",
mixins: [mixin],
created: function() {
document.write('组件调用' + '<br>')
}
});
</script>
如果 methods 选项中有相同的函数名,则 Vue 实例优先级会较高:
<div id="app"></div>
<script type="text/javascript">
var mixin = {
methods: {
hellworld: function() {
document.write('HelloWorld 方法' + '<br>');
},
samemethod: function() {
document.write('混入方法' + '<br>');
}
}
};
var vm = new Vue({
mixins: [mixin],
el:"#app",
methods: {
start: function() {
document.write('start 方法' + '<br>');
},
samemethod: function() {
document.write('实例方法' + '<br>');
}
}
});
vm.hellworld(); //HelloWorld 方法
vm.start(); //start 方法
vm.samemethod(); //实例方法
</script>
可以全局注册混入对象,一旦使用全局混入对象,将会影响到所有之后创建的 Vue 实例。
<script type="text/javascript">
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function() {
var myOption = this.$options.myOption
if (myOption) {
document.write(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// => "hello!"
</script>
还记得我们是如何注册组件的么?
<div id="app">
<common v-bind:message="msg"></common>
</div>
<script>
// 注册
Vue.component('common', {
// 声明 props
props: ['message'],
template: '<span>hello {{ message }}</span>'
})
new Vue({
el: '#app',
data: {
msg: "tcl"
}
})
</script>
但是当一个页面包含众多子组件时,我们这样注册组件显然不是一种优雅的方式,我们希望通过单文件的方式将一个个的组件剥离出去:
// Common.vue 子组件
<template>
<span>hello {{ message }}</span>
</template>
<script>
export default {
name:"Common",
props:{
message : String
}
}
</script>
<style>
</style>
需要注意:单文件组件中的 <template>
下面只能有一个根元素,否则会导致 Vue 编译出错。
// vue 实例
<div id="app">
<Common v-bind:message="msg"></Common>
</div>
<script>
import Common from 'common.vue
new Vue({
el: '#app',
data: {
msg: "tcl"
},
components: {
Common
}
})
</script>
src/router/index.js 中可以定义路由,其中 path 是路由地址,name 是路由别名,component 是路由地址映射的视图文件。
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Info from '../views/Info.vue'
Vue.use(VueRouter);
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/Info',
name: 'Info',
component: Info
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue')
}
]
声明式导航:
<router-link to="/index">
编程式导航:
//字符串
this.$router.push('/index')
//对象
this.$router.push({path:'helloworld'})
//params(刷新页面后传的值读不了)
this.$router.push({name:'helloworld', params:{goodsId:1}})
//query(拼接在地址后面)
this.$router.push({path:'helloworld', query:{userId:1}})
读取路由参数:
<template>
<div>
<span>{{ $route.params.goodsId }}</span>
<span>{{ $route.query.userId }}</span>
</div>
</template>
<script>
export default {}
</script>
$route.query.key 获取的是当前 url 中 query 中的字段值,$route.params.key 获取的是当前 url 中 params 中的字段值。