一、安装
<script>
标签引入
直接下载并用 <script>
标签引入,Vue
会被注册为全局变量
和 React 需要引入两个 <script>
标签相比,它少一个引入,只需引入一个
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.min.js"></script>
2. npm 安装、脚手架安装
yarn add vue
Vue 官方 CLI:https://github.com/vuejs/vue-cli
yarn global add @vue-cli
vue create my-app
yarn serve
3. 不同构建版本的解读
Vue.js 整体上可以分成两种版本:完整版 和 运行时版
- 完整版:同时包含编译器和运行时
- 运行时版: 用来创建 Vue 实例、渲染并处理虚拟 DOM 等代码。(除去了编译器的一切代码)
完整版 VS 运行时版
// 需要编译器
new Vue({
template:'<div>{{hi}}</div>'
})
// 不需要编译器
new Vue({
render(h){
return h('div',this.hi)
}
})
运行时版本比完整版体积小大约 30%
二、介绍
1. Vue 是什么?
Vue 是一套用于构建用户界面的渐进式框架
一个 Vue 类库学习 GitHub:https://github.com/vuejs/awesome-vue#libraries—plugins
一个 Vue 教学视频:https://scrimba.com/learn/vuedocs
一个小 demo 示例了解核心概念
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h1>Hello Word</h1>
<ul>
<li v-for="product in products">
<input type="number" v-model.number="product.quantity">
{{product.name}}
<span v-if="product.quantity === 0">- OUT OF STOCK</span>
<button @click="product.quantity += 1">Add</button>
</li>
</ul>
<h2>Total Inventory: {{totalProducts}}</h2>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
products:[]
},
computed:{
totalProducts(){
return this.products.reduce((sum,product)=>{
return sum + product.quantity
},0)
}
},
created(){
setTimeout(()=>{
const products = [
{id:1,quantity:1,name:'compass'},
{id:2,quantity:0,name:'Jacket'},
{id:3,quantity:5,name:'Hiking Socks'},
{id:4,quantity:2,name:'Suntan Lotion'},
]
this.products = products
})
}
})
</script>
</body>
</html>
2. 声明式渲染
Vue.js 的核心是采用简介的模板语法来声明式地将数据渲染进 DOM 的系统
- 不再与 HTML 直接交互
- 所有东西都是响应式的,修改
app.message
的值,下例的相应地更新 - 一个 Vue 应用会将其挂载到一个 DOM 元素上然后对其完全控制
div#app
是我们的入口
v-bind
attribute 叫做指令。指令带有前缀 v-
,以表示他们是 Vue 提供的特殊 attribute
- 下例中,该指令的意思是“将这个元素节点的 title attribute 和 Vue 实例的 message property 保持一致”
<div id="app">
<span>{{ message }}</span>
<span v-bind:title="message2">鼠标悬停几秒钟查看此处动态绑定的提示信息</span>
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!',
message2:'页面加载于' + new Data().toLocaleString()
}
})
3. 条件与循环
v-if
指令可以控制一个元素是否显示v-for
指令可以绑定数组的数据来渲染一个项目列表
<div id="app-3">
<p v-if="seen">现在你看到我了</p>
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
</div>
var app3 = new Vue({
el: '#app-3',
data: {
seen: true,
todos: [
{ text: '学习 JavaScript' },
{ text: '学习 Vue' },
{ text: '整个牛项目' }
]
}
})
4. 处理用户输入
用 v-on
指令添加一个事件监听器让用户和应用进行交互,通过它调用在 Vue 实例中定义的方法
Vue 还提供了 v-model
指令,它能轻松实现表单输入和应用状态之间的双向绑定
<div id="app-5">
<p>{{ message }}</p>
<button v-on:click="reverseMessage">反转消息</button>
<p>{{ message2 }}</p>
<input v-model="message2">
</div>
var app5 = new Vue({
el: '#app-5',
data: {
message: 'Hello Vue.js!',
message2: 'Hello Vue!'
},
methods: {
reverseMessage: function () {
this.message = this.message.split('').reverse().join('')
}
}
})
5. 组件化应用结构
组件系统是 Vue 的另一个重要概念,是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用
- 几乎任何类型的应用洁面都可以抽象为一个组件树
- 在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例
在 Vue 中注册组件
Vue.component('todo-item', {
template: '<li>这是个待办项</li>'
})
var app = new Vue(...)
构建组件模板并使用该组件
<ol>
<todo-item></todo-item>
</ol>
在一个大型应用中,有必要将整个应用程序划分为组件,以使开发更易管理,下面是一个假象例子
<div id="app">
<app-nav></app-nav>
<app-view>
<app-sidebar></app-sidebar>
<app-content></app-content>
</app-view>
</div>
6. Vue组件与自定义元素
Vue 组件非常类似于自定义元素,它是 Web 组件规范的一部分,Vue 的组件语法部分参考了该规范。例如 Vue 组件实现了 Slot API
与 is
attribute。但是,它们有关键性的差别
- Web Component 规范已经完全并通过,但未被所有浏览器原生实现,而 Vue 组件不需要任何 polyfill
- Vue 组件提供了一些重要功能,最突出的是跨组件数据流、自定义事件通信及构建工具集成
三、Vue 实例
1. 创建一个 Vue 实例
每个 Vue 应用都是通过用 Vue
函数创建一个新的 Vue 实例开始的
- 创建一个 Vue 实例时,可以传入一个选项对象
- 一个 Vue 应用有一个通过 new Vue 创建的跟元素实例,以及可选的嵌套的、可复用的组件树组成
var vm = new Vue({
// 选项
})
// 一个 todo 应用的组建树可以是这样的
根实例
└─ TodoList
├─ TodoItem
│ ├─ TodoButtonDelete
│ └─ TodoButtonEdit
└─ TodoListFooter
├─ TodosButtonClear
└─ TodoListStatistics
2. 数据和方法
当一个 Vue 实例被创建时,它将 data 对象中的所有 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
- 当这些数据改变时,视图会进行重渲染
- 值得注意的是只有当实例被创建时就已经存在于 data 中的 property 才是响应式的
- 唯一的例外是使用 Object.freeze(),这回阻止修改现有的 property,也意味这响应系统无法再追踪变化
- 除了数据 property,Vue 实例还暴露了一些有用的实例 property 与方法。它们都有前缀
$
,以便与用户定义的 property 区分开来
var data = { a: 1 }
var vm = new Vue({
data: data
})
vm.a == data.a // => true
vm.a = 2
data.a // => 2
data.a = 3
vm.a // => 3
var data = { a: 1 }
var vm = new Vue({
el: '#example',
data: data
})
vm.$data === data // => true
vm.$el === document.getElementById('example') // => true
vm.$watch('a', function (newValue, oldValue) {
// 这个回调将在 `vm.a` 改变后调用
})
3. 实例生命周期钩子
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
- 下面示例
created
钩子可以用来在一个实例被创建之后执行代码。 - 还有一些其他的钩子,如
mounted
、updated
和destroyed
。 - 生命周期钩子的 this 上下文指向调用它的 Vue 实例。
- 不要在选项 property 或回调函数上使用箭头函数,因为箭头函数没有 this,而选项中依赖 this,比如
created: () => console.log(this.a)
vm.$watch('a', newValue => this.myMethod())
new Vue({
data: {
a: 1
},
created: function () {
console.log('a is: ' + this.a)
}
})
// => "a is: 1"
4. 生命周期图示
四、模板语法
Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。
在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。
1. 插值
文本
- 数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值
- 通过使用 v-once 指令,也能执行一次性的插值
<span>Message: {{ msg }}</span>
<span v-once>这个将不会改变: {{ msg }}</span>
原始 HTML
- 双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html 指令
- 站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
Attribute
- Mustache 语法不能作用在 HTML attribute 上,遇到这种情况应该使用
v-bind
指令 - 对于布尔 attribute (它们只要存在就意味着值为
true
) - 如果
isButtonDisabled
的值是null
、undefined
或false
,则disabled
attribute 甚至不会被包含在渲染出来的<button>
元素中。<div v-bind:id="dynamicId"></div>
<button v-bind:disabled="isButtonDisabled">Button</button>
使用 JavaScript 表达式
- 对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持
- 模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math 和 Date 。你不应该在模板表达式中试图访问用户定义的全局变量
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
2. 指令
指令 (Directives) 是带有 v-
前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式
v-for
是例外情况- 指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
参数
- 一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 HTML attribute
- 另一个例子是
v-on
指令,它用于监听 DOM 事件:<a v-bind:href="url">...</a>
<a v-on:click="doSomething">...</a>
动态参数
- 从 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数
- 下面示例中,attributeName 和 eventName 会被作为一个 JavaScript 表达式进行动态求值
- 动态参数预期会求出一个字符串,异常情况下值为 null
- 动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的 ```html … …
修饰符
- 修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。
- 例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()
```html
<form v-on:submit.prevent="onSubmit">...</form>
3. 缩写
Vue 为 v-bind 和 v-on 这两个最常用的指令,提供了特定简写
- 它们看起来可能与普通的 HTML 略有不同
- 但 : 与 @ 对于 attribute 名来说都是合法字符,在所有支持 Vue 的浏览器都能被正确地解析 ```html …
<a name="ra3GU"></a>
## 五、计算属性和侦听器
<a name="IdNBo"></a>
### 1. 计算属性
我们声明了一个计算属性 reversedMessage,并将函数将用作 property vm.reversedMessage 的 getter 函数
- 我们可以像绑定普通 property 一样在模板中绑定计算属性
- 计算属性的 getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解
```html
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
})
2. 计算属性缓存 vs 方法
通过在表达式中调用方法可以达到同样的效果,然后 Vue 做了对计算属性做了一个功能,就是缓存
- 计算属性是基于它们的响应式依赖进行缓存的
- 只在相关响应式依赖发生改变时它们才会重新求值
- 相比之下,每当触发重新渲染时,调用方法将总会再次执行函数,性能开销会更大
<p>Reversed message: "{{ reversedMessage() }}"</p>
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
3. 计算属性 vs 侦听属性
Vue 提供了侦听属性来观察和响应 Vue 实例上的数据变动,但有一些数据需要随着其他数据变动而变动时,计算属性会是更好的做法
- 下面是他们之间的对比
<div id="demo">{{ fullName }}</div>
```javascriptvar vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
var vm = new Vue({ el: ‘#demo’, data: { firstName: ‘Foo’, lastName: ‘Bar’ }, computed: { fullName: function () { return this.firstName + ‘ ‘ + this.lastName } } })
<a name="6b6v7"></a>
### 4. 计算属性的 setter
计算属性默认只有 setter,不过在需要时也可以提供一个 setter
- 运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstName 和 vm.lastName 也会相应地被更新
```javascript
// ...
computed: {
fullName: {
get: function () {
return this.firstName + ' ' + this.lastName
},
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// ...
5. 侦听器
当需要在数据变化时执行异步或开销较大的操作时,使用 watch 这个方式是最有用的
- 下面是一个示例,使用
watch
选项允许我们执行异步操作(访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。 - 除了
watch
选项外,还可以使用命令式的vm.$watch
```htmlAsk a yes/no question:
{{ answer }}
<a name="zzp51"></a>
## 六、Class 与 Style 绑定
操作元素的 class 列表和内联样式是数据绑定的一个常见需求,可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。<br />在将 v-bind 用于 class 和 style 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组
<a name="iPdTU"></a>
### 1. 绑定 HTML Class
对象语法
```html
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
<div v-bind:class="classObject"></div>
数组语法
- 可以使用三元表达式
- 在数组项中,还可以使用对象语法 ```html
用在组件上
- 当在一个自定义组件上使用 class property 时,这些 class 将被添加到该组件的根元素上面
- 这个元素上已经存在的 class 不会被覆盖
javascript
Vue.component(‘my-component’, {
template: ‘ ‘
})
```html
<my-component class="baz boo"></my-component>
<!-- 将被渲染为 -->
<p class="foo bar baz boo">Hi</p>
### 2. 绑定内联样式
对象语法
- v-bind:style
的对象语法十分直观,看着非常像 CSS,但其实是一个 JavaScript 对象
- CSS property 名可以用驼峰式(camelCase)或短横线(kebal-case)来命名
```html
数组语法
- 将多个样式对象应用到同一个元素上
html
自动添加前缀
- 当 v-bind:style 使用需要添加浏览器引擎前缀的 CSS property 时,如 transform,Vue.js 会自动侦测并添加相应的前缀
多重值
- 从 2.3.0 起你可以为 style 绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值
- 这样写只会渲染数组中最后一个被浏览器支持的值
- 在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 `display: flex`
html
<a name="BqZvn"></a>
## 七、条件渲染
`v-if` 指令用于条件渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。
html
Vue is awesome!
Oh no 😢
<a name="r4MHW"></a>
### 1. 在 <template> 元素上使用 v-if 条件渲染分组
最终的渲染结果将不包含 `<template>` 元素
html
Title
Paragraph 1
Paragraph 2
<a name="UqmDg"></a>
### 2. v-else
html
<a name="gCv5U"></a>
### 3. v-else-if
- 2.1.0 新增
html
<a name="Tf6Fr"></a>
### 4. 用 key 管理可复用元素
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染
- 这么做除了使 Vue 变得非常快之外,还有其它一些好处,例如,如果你允许用户在不同的登录方式之间切换
- 下面示例中,每次切换时,输入框都将被重新渲染
- 并且 <label> 元素仍然会被高效地复用,因为它们没有添加 key attribute
html
<a name="rGBeK"></a>
### 5. v-show
v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS property `display`
html
Hello!
<a name="tpazI"></a>
### 6. v-if vs v-show
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。<br />v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。<br />一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
<a name="IGRFl"></a>
### 7. v-if 与 v-for 一起使用
当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级<br />但是,不推荐同时使用 v-if 和 v-for
<a name="PJMRD"></a>
## 八、列表渲染
<a name="AkTK3"></a>
### 1. 用 v-for 把一个数组对应为一组元素
`v-for` 指令需要使用 `item in items` 形式的特殊语法,其中 `items` 是源数据数组,而 `item` 则是被迭代的数组元素的别名
- 在 v-for 块中,我们可以访问所有父作用于的 property
- `v-for` 还支持一个可选的第二个参数,即当前项的索引
- 也可以用 of 替代 in 作为分隔符,因为它更接近 JavaScript 迭代器的语法
html
- {{ item.message }}
- {{ parentMessage }} - {{ index }} - {{ item.message }}
<a name="azmLb"></a>
### 2. 在 v-for 里使用对象
v-for 除了可以遍历数组,还可以遍历一个对象的 property
- 也可以提供第二个的参数为 property 名称 (也就是键名)
- 还可以用第三个参数作为索引
- 在遍历对象时,会按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致
html
- {{ value }}
<a name="JSHzf"></a>
### 3. 维护状态
当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。
- 如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 `key` attribute
- 建议尽可能在使用 `v-for `时提供 `key` attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升
- 因为它是 Vue 识别节点的一个通用机制,`key` 并不仅与 `v-for` 特别关联,它还有其他用途
- 不要使用对象或数组之类的非基本类型值作为 `v-for` 的 `key`。请用字符串或数值类型的值
html
<a name="KvkU7"></a>
### 4. 数组更新检测
变更方法
- Vue 将被侦听的数组的变更方法进行了变更,所以它们也将会触发视图更新。这些被变更过的方法包括
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
替换数组
- 数组的非变更方法 filter()、concat()、slice(),它们不会变更原始数组,而总是返回一个新数组
- 当使用非变更方法时,可以用新数组替换旧数组
html
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作<br />注意事项
- 由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。
<a name="NON4H"></a>
### 5. 显示过滤/排序后的结果
有时,我们想要显示一个数组经过过滤或排序后的版本,而不实际变更或重置原始数据。在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组
html
```javascript
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
在计算属性不适用的情况下 (例如,在嵌套 v-for 循环中) 你可以使用一个方法
html
<ul v-for="set in sets">
<li v-for="n in even(set)">{{ n }}</li>
</ul>
javascript
data: {
sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
### 6. 在 v-for 里使用值范围
v-for 也可以接受整数。在这种情况下,它会把模板重复对应次数
html
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
### 7. 在 上使用 v-for
类似于 v-if,你也可以利用带有 v-for 的 来循环渲染一段包含多个元素的内容
html
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
### 8. v-for 与 v-fi 一起使用
当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。当你只想为部分项渲染节点时,这种优先级的机制会十分有用
- 不推荐在同一元素上使用 v-if 和 v-for
- 如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素 (或 ) 上
html
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>
<ul v-if="todos.length">
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>
### 9. 在组件上使用 v-for
2.2.0+ 的版本里,当在组件上使用 v-for 时,key 现在是必须的
- 在自定义组件上,你可以像在任何普通元素上一样使用 v-for
- 任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域
- 为了把迭代数据传递到组件里,我们要使用 prop
html
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
></my-component>
下面是一个简单的 todo 列表的完整例子:
html
<div id="todo-list-example">
<form v-on:submit.prevent="addNewTodo">
<label for="new-todo">Add a todo</label>
<input
v-model="newTodoText"
id="new-todo"
placeholder="E.g. Feed the cat"
>
<button>Add</button>
</form>
<ul>
<li
is="todo-item"
v-for="(todo, index) in todos"
v-bind:key="todo.id"
v-bind:title="todo.title"
v-on:remove="todos.splice(index, 1)"
></li>
</ul>
</div>
javascript
Vue.component('todo-item', {
template: '\
<li>\
{{ title }}\
<button v-on:click="$emit(\'remove\')">Remove</button>\
</li>\
',
props: ['title']
})
new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
{
id: 1,
title: 'Do the dishes',
},
{
id: 2,
title: 'Take out the trash',
},
{
id: 3,
title: 'Mow the lawn'
}
],
nextTodoId: 4
},
methods: {
addNewTodo: function () {
this.todos.push({
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = ''
}
}
})
## 九、事件处理
### 1. 监听事件
可以使用 v-on
指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码
html
<div id="example-1">
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
### 2. 事件处理方法
v-on
还可以接收一个需要调用的方法名称
html
<div id="example-2">
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
</div>
### 3. 内联处理器中的方法
除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法
- 有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法
```html
<a name="grZt9"></a>
### 4. 事件修饰符
在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。
- 尽管我们可以在方法中轻松实现这点
- 但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。
- .stop
- .prevent
- .capture
- .self
- .once
- .passive
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生
- 因此,用 v-on:click.prevent.self 会阻止所有的点击
- 而 v-on:click.self.prevent 只会阻止对元素自身的点击
不要把 .passive 和 .prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告
- 请记住,.passive 会告诉浏览器你不想阻止事件的默认行为
html
<a name="P3kva"></a>
### 5. 按键修饰符
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符
html
<a name="rwMQr"></a>
### 6. 系统修饰符
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器
- .ctrl
- .alt
- .shift
- .meta
`.exact` 修饰符允许你控制由精确的系统修饰符组合触发的事件
html
<a name="mtQt4"></a>
### 7. 鼠标按钮修饰符
这些修饰符会限制处理函数仅响应特定的鼠标按钮
- .left
- .right
- .middle
<a name="CGP84"></a>
### 8. 为什么在 HTML 中监听事件
使用 v-on 有几个好处:
- 扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。
- 因为你无须在 JavaScript 里手动绑定事件,你的 ViewModel 代码可以是非常纯粹的逻辑,和 DOM 完全解耦,更易于测试。
- 当一个 ViewModel 被销毁时,所有的事件处理器都会自动被删除。你无须担心如何清理它们。
<a name="RhACD"></a>
## 十、表单输入绑定
<a name="DAZIZ"></a>
### 1. 基础用法
你可以用 v-model 指令在表单 <input>、<textarea> 及 <select> 元素上创建双向数据绑定。
- 它会根据控件类型自动选取正确的方法来更新元素。
- 尽管有些神奇,但 v-model 本质上不过是语法糖。
- 它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
- v-model 会忽略所有表单元素的 value、checked、selected attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用 value property 和 input 事件;
- checkbox 和 radio 使用 checked property 和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件。
文本
```html
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>
多行文本
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
复选框
<!-- 单个复选框,绑定到布尔值 -->
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
<!-- 多个复选框,绑定到同一个数组 -->
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>
单选按钮
<div id="example-4">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>
</div>
选择框
<!-- 单选时 -->
<div id="example-5">
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
<!-- 多选时(绑定到一个数组) -->
<div id="example-6">
<select v-model="selected" multiple style="width: 50px;">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected: {{ selected }}</span>
</div>
2. 值绑定
对于单选按钮,复选框及选择框的选项,v-model 绑定的值通常是静态字符串 (对于复选框也可以是布尔值)
有时我们可能想把值绑定到 Vue 实例的一个动态 property 上,这时可以用 v-bind 实现,并且这个 property 的值可以不是字符串
<!-- 当选中时,`picked` 为字符串 "a" -->
<input type="radio" v-model="picked" value="a">
<!-- `toggle` 为 true 或 false -->
<input type="checkbox" v-model="toggle">
<!-- 当选中第一个选项时,`selected` 为字符串 "abc" -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>
复选框
<input
type="checkbox"
v-model="toggle"
true-value="yes"
false-value="no"
>
单选按钮
<input type="radio" v-model="pick" v-bind:value="a">
选择框的选项
<select v-model="selected">
<!-- 内联对象字面量 -->
<option v-bind:value="{ number: 123 }">123</option>
</select>
3. 修饰符
.lazy
- 在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步。你可以添加 lazy 修饰符,从而转为在 change 事件之后进行同步
<!-- 在“change”时而非“input”时更新 --> <input v-model.lazy="msg">
.number
- 如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符
- 这通常很有用,因为即使在 type=”number” 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值
<input v-model.number="age" type="number">
.trim
- 如果要自动过滤用户输入的首尾空白字符,可以给
v-model
添加trim
修饰符<input v-model.trim="msg">
4. 在组件上使用 v-model
HTML 原生的输入元素类型并不总能满足需求。幸好,Vue 的组件系统允许你创建具有完全自定义行为且可复用的输入组件。这些输入组件甚至可以和 v-model 一起使用!
十一、组件基础
1. 基本示例
组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是
因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项
- 例如 data、computed、watch、methods 以及生命周期钩子等
- 仅有的例外是像 el 这样根实例特有的选项
// 定义一个名为 button-counter 的新组件 Vue.component('button-counter', { data: function () { return { count: 0 } }, template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>' })
<div id="components-demo"> <button-counter></button-counter> </div>
new Vue({ el: '#components-demo' })
2. 组件复用
我们可以将组件进行任意次数的复用
- 当点击按钮时,每个组件都会各自独立维护它的 count。因为你每用一次组件,就会有一个它的新实例被创建
<div id="components-demo"> <button-counter></button-counter> <button-counter></button-counter> <button-counter></button-counter> </div>
data 必须是一个函数
- 一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝
data: function () { return { count: 0 } }
3. 组件的组织
通常一个应用会以一棵嵌套的组件树的形式来组织
- 为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。
- 这里有两种组件的注册类型:全局注册和局部注册
- 全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中
Vue.component('my-component-name', {
// ... options ...
})
4. 通过 Prop 向子组件传递数据
用一个 props 选项将其包含在该组件可接受的 prop 列表中
- 一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop
- 在下述模板中,你会发现我们能够在组件实例中访问这个值,就像访问 data 中的值一样
Vue.component('blog-post', { props: ['title'], template: '<h3>{{ title }}</h3>' })
<blog-post title="My journey with Vue"></blog-post> <blog-post title="Blogging with Vue"></blog-post> <blog-post title="Why Vue is so fun"></blog-post>
5. 单个根元素
每个组件必须只有一个根元素
当组件变得越来越复杂的时候,我们的博文不只需要标题和内容,还需要发布日期、评论等等。为每个相关的信息定义一个 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>
`
})
6. 监听自组件事件
Vue 实例提供了一个自定义事件的系统来解决这个问题
- 父级组件可以像处理 native DOM 事件一样通过 v-on 监听子组件实例的任意事件
- 同时子组件可以通过调用内建的 $emit 方法并传入事件名称来触发一个事件
<blog-post ... v-on:enlarge-text="postFontSize += 0.1" ></blog-post>
<button v-on:click="$emit('enlarge-text')"> Enlarge text </button>
使用事件抛出一个值
- 子组件使用 $emit 的第二个参数来提供这个值
- 父级组件监听这个事件的时候,我们可以通过 $event 访问到被抛出的这个值
<button v-on:click="$emit('enlarge-text', 0.1)"> Enlarge text </button>
<blog-post ... v-on:enlarge-text="postFontSize += $event" ></blog-post>
在组件上使用 v-model
- 自定义事件也可以用于创建支持 v-model 的自定义输入组件
为了让它正常工作,这个组件内的 必须:
- 将其 value attribute 绑定到一个名叫 value 的 prop 上
- 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出 ```html
<custom-input v-bind:value=”searchText” v-on:input=”searchText = $event”
```
7. 使用插槽分发内容
Vue 自定义的
<alert-box>
Something bad happened.
</alert-box>
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
8. 动态组件
有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里
- 可以通过 Vue 的
元素加一个特殊的 is attribute 来实现 - 在下示例中,currentTabComponent 可以包括
- 已注册组件的名字
- 一个组件的选项对象
- 这个 attribute 可以用于常规 HTML 元素,但这些元素将被视为组件,这意味着所有的 attribute 都会作为 DOM attribute 被绑定
- 对于像 value 这样的 property,若想让其如预期般工作,你需要使用 .prop 修饰器
<!-- 组件会在 `currentTabComponent` 改变时改变 --> <component v-bind:is="currentTabComponent"></component>