您的位置:首页 > 产品设计 > UI/UE

Vue.JS学习基础

2017-03-02 13:43 483 查看
=
导航

顶部

vue.js介绍

vue.js实例

模板语法

计算属性

样式绑定

条件渲染

列表渲染

事件处理器

表单控件绑定

组件

顶部

vue.js介绍

vue.js实例

模板语法

计算属性

样式绑定

条件渲染

列表渲染

事件处理器

表单控件绑定

组件



全面教程

vue.js介绍

Vue.js 是用于构建交互式的 Web 界面的库。Vue.js 提供了 MVVM 数据绑定和一个可组合的组件系统,具有简单、灵活的 API。从技术角度讲,Vue.js 专注于 MVVM 模型的 ViewModel 层。它通过双向数据绑定把 View 层和 Model 层连接了起来。Vue.js 的 API 设计深受 AngularJS、KnockoutJS、Ractive.js 和 Rivets.js 的影响。

vue.js特点

简洁: HTML 模板 + JSON 数据,再创建一个 Vue 实例,就这么简单。

快速: 精确有效的异步批量 DOM 更新。

轻量: ~24kb min+gzip,无依赖。

组件化: 用解耦、可复用的组件来构造界面

<div id="app">
{{ message }}
</<div>

new Vue({
el:'#app',
data: {
message:'Hello World!'
}
});

或者也可以这样:

<div id="app-2">
<span v-bind:title="message">
啦啦啦
</span>
</div>

v-bind 属性被称为指令。指令带有前缀 v-,以表示它们是 Vue.js 提供的特殊属性。这个指令的简单含义是说:将这个元素节点的 title 属性和 Vue 实例的 message 属性绑定到一起

又如:

<div id="app-4">
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
</div>

var app4 = new Vue({
el: '#app-4',
data: {
todos: [
{ text: 'Learn JavaScript' },
{ text: 'Learn Vue' },
{ text: 'Build something awesome' }
]
}
})




ViewModel

一个同步 Model 和 View 的对象。在 Vue.js 中,每个 Vue 实例都是一个 ViewModel。它们是通过构造函数 Vue 或其子类被创建出来的。

视图

Vue.js 使用基于 DOM 的模板。每个 Vue 实例都关联着一个相应的 DOM 元素,当一个 Vue 实例被创建时,它会递归遍历根元素的所有子结点,同时完成必要的数据绑定。当这个视图被编译之后,它就会自动响应数据的变化。在使用 Vue.js 时,除了自定义指令 (稍后会有解释),您几乎不必直接接触 DOM。当数据发生变化时,视图将会自动触发更新。这些更新的粒度精确到一个文字节点。同时为了更好的性能,这些更新是批量异步执行的。

模型

一个轻微改动过的原生 JavaScript 对象。Vue.js 中的模型就是普通的 JavaScript 对象——也可以称为数据对象.一旦某对象被作为 Vue 实例中的数据,它就成为一个 “反应式” 的对象了。你可以操作它们的属性,同时正在观察它的 Vue 实例也会收到提示.Vue.js 把数据对象的属性都转换成了 ES5 中的 getter/setters,以此达到无缝的数据观察效果:无需脏值检查,也不需要刻意给 Vue 任何更新视图的信号。每当数据变化时,视图都会在下一帧自动更新。多个 Vue 实例可以观察同一份数据。在较大型的应用程序中,我们也推荐将 Vue 实例作为纯粹的视图看待,同时把数据处理逻辑放在更独立的外部数据层。

指令

带特殊前缀的 HTML 特性,可以让 Vue.js 对一个 DOM 元素做各种处理。v-text 指令,其值为 message。Vue.js 会让该 div 的文本内容与 Vue 实例中的 message 属性值保持一致。指令可以封装任何 DOM 操作。比如 v-attr 会操作一个元素的特性;v-repeat(v-for) 会基于数组来复制一个元素;v-on会绑定事件, v-model 指令,它使得在表单输入和应用状态中做双向数据绑定变得非常轻巧。等

过滤器

过滤器是用于在更新视图之前处理原始值的函数。它们通过一个 “管道” 在指令或绑定中进行处理.这样在 div 的文本内容被更新之前,message 的值会先传给 capitalizie 函数处理.

组件

在 Vue.js,每个组件都是一个简单的 Vue 实例。一个树形嵌套的各种组件就代表了你的应用程序的各种接口。。通过 Vue.extend 返回的自定义构造函数可以把这些组件实例化,不过更推荐的声明式的用法是通过 Vue.component(id, constructor) 注册这些组件。一旦组件被注册,它们就可以在 Vue 实例的模板中以自定义元素形式使用了。在一个大型应用中,为了使得开发过程可控,有必要将应用整体分割成一个个的组件。看看使用了组件的应用模板是什么样的:

<div id="app">
<app-nav></app-nav>
<app-view>
<app-sidebar></app-sidebar>
<app-content></app-content>
</app-view>
</div>

vue实例

构造器

每个 Vue.js 应用都是通过构造函数 Vue 创建一个 Vue 的根实例,在文档中经常会使用 vm 这个变量名表示 Vue 实例,在实例化 Vue 时,需要传入一个选项对象,它可以包含数据、模板、挂载元素、方法、生命周期钩子等选项.可以扩展构造器,从而用预定义选项创建可复用的组件构造器.尽管可以命令式地创建扩展实例,不过在多数情况下建议将组件构造器注册为一个自定义元素,然后声明式地用在模板中。Vue.js 组件其实都是被扩展的 Vue 实例

var vm = new Vue({
// 选项
})

var MyComponent = Vue.extend({
// 扩展选项
})

// 所有的 `MyComponent` 实例都将以预定义的扩展选项被创建
var myComponentInstance = new MyComponent()

属性与方法

每个 Vue 实例都会代理其 data 对象里所有的属性,如vm.属性 和 message里面的属性

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

除了 data 属性, Vue 实例暴露了一些有用的实例属性与方法。如vm.$data ; vm.$el

var data = { a: 1 }
var vm = new Vue({
el: '#example',
data: data
})
vm.$data === data // -> true
vm.$el === document.getElementById('example') // -> true
// $watch 是一个实例方法
vm.$watch('a', function (newVal, oldVal) {
// 这个回调将在 `vm.a`  改变后调用
})

响应式原理

Vue.js的数据观测实现原理和Angular有着本质的不同。了解Angular的读者可能知道,Angular的数据观测采用的是脏检查(dirty checking)机制。每一个指令都会有一个对应的用来观测数据的对象,叫做watcher;一个作用域中会有很多个watcher。每当界面需要更新时,Angular会遍历当前作用域里的所有watcher,对它们一一求值,然后和之前保存的旧值进行比较。如果求值的结果变化了,就触发对应的更新,这个过程叫做digest cycle。

脏检查有两个问题:

1.任何数据变动都意味着当前作用域的每一个watcher需要被重新求值,因此当watcher的数量庞大时,应用的性能就不可避免地受到影响,并且很难优化。

2.当数据变动时,框架并不能主动侦测到变化的发生,需要手动触发digest cycle才能触发相应的DOM 更新。Angular通过在DOM事件处理函数中自动触发digest cycle部分规避了这个问题,但还是有很多情况需要用户手动进行触发。

Vue.js采用的则是基于依赖收集的观测机制。把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。这是 ES5 的特性,不能打补丁实现,这便是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。用户看不到 getter/setters,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。从原理上来说,和老牌MVVM框架Knockout是一样的。依赖收集的基本原理是:将原生的数据改造成 “可观察对象”。一个可观察对象可以被取值,也可以被赋值。当一个被依赖的可观察对象被赋值时,它会通知所有订阅自己的watcher重新求值,并触发相应的更新。依赖收集的优点在于可以精确、主动地追踪数据的变化,不存在上述提到的脏检查的两个问题。但传统的依赖收集实现,比如Knockout,通常需要包裹原生数据来制造可观察对象,在取值和赋值时需要采用函数调用的形式,在进行数据操作时写法繁琐,不够直观;同时,对复杂嵌套结构的对象支持也不理想。

Vue.js利用了ES5的Object.defineProperty方法,直接将原生数据对象的属性改造为getter和setter(这是ES5的特性,需要js解释引擎的支持,无法通过各种打shim补丁来实现。这也是为什么Vue不支持IE8及以下版本的原因),在这两个函数内部实现依赖的收集和触发,而且完美支持嵌套的对象结构。对于数组,则通过包裹数组的可变方法(比如push)来监听数组的变化。这使得操作Vue.js的数据和操作原生对象几乎没有差别。





受现代 Javascript 的限制(以及 Object.observe 的废弃),Vue 不能检测到对象属性的添加或删除。因为 Vue 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue 转换它,这样才能让它是响应的。例如:

var data = { a: 1 };
var vm = new Vue({
data: data
});
// `vm.a` 和 `data.a` 现在是响应的
vm.b = 2
// `vm.b` 不是响应的
data.b = 2// `data.b` 不是响应的

不过,也有办法在实例创建后添加属性并且让它是相应的。可以使用set(key,value)实例方法

// `vm.b` 和 `data.b` 现在是响应的
vm. set('b', 2)

对于普通对象可以使用全局方法:Vue.set(object, key, value):

// `vm.c` 和 `data.c` 现在是响应的
Vue.set(vm.data, 'c', 3)

初始化数据:尽管Vue.js提供动态的添加相应属性,还是推荐在data对象上声明所有的相应属性。由于 Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明根级响应式属性,哪怕只是一个空值.如果你不在 data 对象中声明 message,Vue 将发出警告表明你的渲染方法正试图访问一个不存在的属性。

var vm = new Vue({
data: {
// 以一个空值声明 `msg`
msg: ''
},
template: '<div>{{msg}}</div>'
})
// 然后设置 `msg`
vm.msg = 'Hello!'

//而不这么做
var vm = new Vue({
template: '<div>{{msg}}</div>'
})
// 然后添加 `msg`
vm.$set('msg', 'Hello!')

Vue 执行 DOM 更新是异步的,只要观察到数据变化,Vue 就开始一个队列,将同一事件循环内所有的数据变化缓存起来。如果一个 watcher 被多次触发,只会推入一次到队列中。然后,在接下来的事件循环中,Vue 刷新队列并仅执行必要的 DOM 更新。Vue 在内部尝试利用原生的 Promise.then 和 MutationObserver 来调用异步队列,如果执行环境不兼容,会采用 setTimeout(fn, 0) 代替。当你设置 vm.someData = 'new value' ,该组件不会马上被重新渲染。当刷新队列时,这个组件会在下一次事件循环清空队列时更新。一般来讲, Vue 鼓励开发者沿着数据驱动的思路,尽量避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调在 DOM 更新完成后就会调用。vm.$nextTick() 这个实例方法在组件内使用特别方便,因为它不需要全局 Vue ,它的回调 this 将自动绑定到当前的 Vue 实例上

<div id="example">{{message}}</div>

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
})

模板语法

插值:数据绑定最常见的形式就是使用 “Mustache” 语法(双大括号)的文本插值,无论何时,绑定的数据对象上 msg 属性发生了改变,插值处的内容都会更新。通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。但请留心这会影响到该节点上所有的数据绑定

纯HTML:双大括号会将数据解释为纯文本,而非 HTML 。为了输出真正的 HTML ,你需要使用 v-html 指令

Mustache 不能在 HTML 属性中使用,应使用 v-bind 指令

js表达式:表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。有个限制就是,每个绑定都只能包含单个表达式

过滤器:Vue.js 允许你自定义过滤器,被用作一些常见的文本格式化。过滤器应该被添加在 mustache 插值的尾部,由“管道符”指示.Vue 2.x 中,过滤器只能在 mustache 绑定中使用。为了在指令绑定中实现同样的行为,你应该使用计算属性.

<span>Message: {{ msg }}</<span>

<span v-once>This will never change: {{ msg }}</span>

<div v-html="rawHtml"></div>

<div v-bind:id="'list-' + id"></div>
<button v-bind:disabled="someDynamicCondition">Button</button>

{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
new Vue({
filter:{ capitalize:function(value){
if(!vaule) return '';
value=value.tostring();
return value.chartAt(0).toUpperCase()+value.slice(1);
}
}
})

//下面不会生效

{{ var a = 1 }}

{{ if (ok) { return message } }}

{{ message | capitalize }}

指令

一些指令能接受一个“参数”,在指令后以冒号指明。例如, v-bind 指令被用来响应地更新 HTML 属性

<a v-bind:href="url"></<a>
<a v-on:click="doSomething">

缩小

v- 前缀在模板中是作为一个标示 Vue 特殊属性的明显标识。当你使用 Vue.js 为现有的标记添加动态行为时,它会很有用,但对于一些经常使用的指令来说有点繁琐。同时,当搭建 Vue.js 管理所有模板的 SPA 时,v- 前缀也变得没那么重要了。因此,Vue.js 为两个最为常用的指令提供了特别的缩写。

// 完整语法
<a v-bind:href="url"></a>
//缩写
<a :href="url"></a>
//完整语法
<a v-on:click="doSomething"></<a>
//缩写
<a @click="doSomething"></<a>

计算属性

<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: {
// a computed getter
reversedMessage: function () {
// `this` points to the vm instance
return this.message.split('').reverse().join('')
}
}
})

Vue 知道 vm.reversedMessage 依赖于 vm.message ,因此当 vm.message 发生改变时,依赖于 vm.reversedMessage 的绑定也会更新

计算缓存

Vue.js 的计算属性不是简单的 getter。计算属性持续追踪它的响应依赖。在计算一个计算属性时,Vue.js 更新它的依赖列表并缓存结果,只有当其中一个依赖发生了变化,缓存的结果才无效。因此,只要依赖不发生变化,访问计算属性会直接返回缓存的结果,而不是调用 getter。为什么要缓存呢?假设我们有一个高耗计算属性 A,它要遍历一个巨型数组并做大量的计算。然后,可能有其它的计算属性依赖 A。如果没有缓存,我们将调用 A 的 getter 许多次,超过必要次数。由于计算属性被缓存了,在访问它时 getter 不总是被调用。

不经过计算属性,我们可以在 method 中定义一个相同的函数来替代它。对于最终的结果,两种方式确实是相同的。然而,不同的是计算属性是基于它的依赖缓存。计算属性只有在它的相关依赖发生改变时才会重新取值。这就意味着只要 message 没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

<p>Reversed message: "{{ reverseMessage() }}"</<p>

methods: {
reverseMessage: function () {
return this.message.split('').reverse().join('')
}
}

//该计算属性将不会更新,因为 Date.now() 不是响应式依赖
computed: {
now: function () {
return Date.now()
}
}

var vm = new Vue({
data : {
msg : 'hi'
},
computed : {
example : function (){
return Date.now() + this.msg
}
}
})

计算属性 example 只有一个依赖: vm.msg 。 Date.now() 不是 响应依赖,因为它跟 Vue 的数据观察系统无关。因而,在访问 vm.example 时将发现时间戳不变,除非 vm.msg 变了。有时希望 getter 不改变原有的行为,每次访问 vm.example 时都调用 getter。这时可以为指定的计算属性关闭缓存

computed : {
example : {
cache : false,
get : function (){
return Date.now() + this.msg
}
}
}

现在每次访问 vm.example 时,时间戳都是新的。但是,只是在 JavaScript 中访问是这样的;数据绑定仍是依赖驱动的。如果在模块中这样绑定计算属性 {{example}} ,只有响应依赖发生变化时才更新DOM。

和watch属性比较

Vue.js 提供了一个方法 $watch ,它用于观察 Vue 实例上的数据变动。当一些数据需要根据其它数据变化时, $watch 很诱人 —— 特别是如果你来自 AngularJS 。不过,通常更好的办法是使用计算属性而不是一个命令式的 $watch 回调

var 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
}
}
})

计算setter

计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter

computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}

class与style绑定

我们可以传给 v-bind:class 一个对象,以动态地切换 class,也可以在对象中传入更多属性用来动态切换多个 class 。此外, v-bind:class 指令可以与普通的 class 属性共存

<div v-bind:class="{ active: isActive }"></<div>
<div class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }">
</<div>

data: {
isActive: true,
hasError: false
}

//或者
<div v-bind:class="classObject"></<div>
data: {
classObject: {
active: true,
'text-danger': false
}
}

我们可以把一个数组传给 v-bind:class ,以应用一个 class 列表

<div v-bind:class="[activeClass,errorClass]"></div>
<div v-bind:style="[baseStyles, overridingStyles]">

data: {
activeClass: 'active',
errorClass: 'text-danger'
}
<div class="active text-danger"></div>

//还可以
<div v-bind:class="[isActive ? activeClass : '', errorClass]">

绑定内联样式

v-bind:style 的对象语法十分直观——看着非常像 CSS ,其实它是一个 JavaScript 对象。 CSS 属性名可以用驼峰式(camelCase)或短横分隔命名(kebab-case)

<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'
}
}

自动添加前缀

当 v-bind:style 使用需要特定前缀的 CSS 属性时,如 transform ,Vue.js 会自动侦测并添加相应的前缀。

条件渲染

v-if 是一个指令,需要将它添加到一个元素上.把一个 <template>元素当做包装元素,并在上面使用 v-if,最终的渲染结果不会包含它

<h1 v-if="ok">Yes</<h1>

<h1 v-if="ok">Yes</<h1>
<h1 v-lese>No</<h1>

<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</<template>

以用 v-else 指令给 v-if 或 v-show 添加一个 “else” 块,v-else 元素必须紧跟在 v-if 或 v-show 元素的后面——否则它不能被识别

<div v-if="Math.random() > 0.5">
Sorry
</<div>
<div v-else>
Not sorry
</<div>

v-show 的元素会始终渲染并保持在 DOM 中。v-show 是简单的切换元素的 CSS 属性 display,-show 不支持 <template> 语法

<h1 v-show="ok">Hello!</<h1>

v-if 是真实的条件渲染,因为它会确保条件块在切换当中适当地销毁与重建条件块内的事件监听器和子组件。v-if 是真实的条件渲染,因为它会确保条件块在切换当中适当地销毁与重建条件块内的事件监听器和子组件。相比之下, v-show 简单得多——元素始终被编译并保留,只是简单地基于 CSS 切换。一般来说, v-if 有更高的切换消耗而 v-show 有更高的初始渲染消耗。因此,如果需要频繁切换使用 v-show 较好,如果在运行时条件不大可能改变则使用 v-if 较好。

列表渲染

v-for 指令根据一组数组的选项列表进行渲染。 v-for 指令需要以 item in items 形式的特殊语法, items 是源数据数组并且 item 是数组元素迭代的别名。在 v-for 块中,我们拥有对父作用域属性的完全访问权限。 v-for 还支持一个可选的第二个参数为当前项的索引。如同 v-if 模板,以用带有 v-for 的 <template> 标签来渲染多个元素块

<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>

<ul id="example-2">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</<li>
</<ul>

var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})

<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider"</li>
</template>
</ul>

对象迭代 v-for

可以用 v-for 通过一个对象的属性来迭代,在遍历对象时,是按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下是一致的

<div v-for="(value, key) in object">
{{ key }} : {{ value }}
</div>

<ul id="repeat-object" class="demo">
<li v-for="value in object">
{{ value }}
</li>
</ul>

new Vue({
el: '#repeat-object',
data: {
object: {
FirstName: 'John',
LastName: 'Doe',
Age: 30
}
}
})

v-for 也可以取整数。在这种情况下,它将重复多次模板。

<div>
<span v-for="n in 10">{{ n }}</span>
</div>

Vue 包含一组观察数组的变异方法,所以它们也将会触发视图更新

push()

pop()

shift()

unshift()

splice()

sort()

reverse()

过滤

<li v-for="n in evenNumbers">{{ n }}</li>

data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}

//或者换methods
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}

事件处理器

可以用 v-on 指令监听 DOM 事件来触发一些 JavaScript 代码,v-on 也可以接收一个定义的方法来调用,也可以用内联 JavaScript 语句。

<div id="example-1">
<button v-on:click="counter += 1">增加 1</button>
<p>这个按钮被点击了 {{ counter }} 次。</p>
</<div>

var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})

<div id="example-2">
<button v-on:click="greet">增加 1</button>
</<div>

var example1 = new Vue({
el: '#example-2',
data: {
counter: 0
},
methods: {
greet: function (event) {
// `this` 在方法里指当前 Vue 实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
alert(event.target.tagName)
}
}
})

<div id="example-3">
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
</div>

new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})

有时也需要在内联语句处理器中访问原生 DOM 事件。可以用特殊变量 $event 把它传入方法

<button v-on:click="warn('Form cannot be submitted yet.', $event)">Submit</button>

methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) event.preventDefault()
alert(message)
}
}

在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。尽管我们可以在 methods 中轻松实现这点,但更好的方式是:methods 只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。为了解决这个问题, Vue.js 为 v-on 提供了 事件修饰符。通过由点(.)表示的指令后缀来调用修饰符:stop,prevent,capture,self。

//阻止单击事件冒泡
<a v-on:click.stop="doThis"></a>

//提交事件不再重载页面
<form v-on:submit.prevent="onSubmit"></form>

//只当事件在该元素本身(而不是子元素)触发时触发回调
<div v-on:click.self="doThat">...</div>

在监听键盘事件时,我们经常需要监测常见的键值。 Vue 允许为 v-on 在监听键盘事件时添加按键修饰符,也可以通过全局 config.keyCodes 对象自定义按键修饰符别名

<input v-on:keyup.13="submit">
<input v-on:keyup.enter="submit">
//缩写语法
<input @keyup.enter="submit">
//enter tab delete  esc  space  up  down  left  right

//Vue.config.keyCodes.f1 = 112

表单控件绑定

你可以用 v-model 指令在表单控件元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖,它负责监听用户的输入事件以更新数据,并特别处理一些极端的例子。v-model 并不关心表单控件初始化所生成的值。因为它会选择 Vue 实例数据来作为具体的值。

<p style="white-space: pre">{{ 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>

new Vue({
el: '...',
data: {
checkedNames: []
}
})

下拉菜单 v-for

<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selected }} </span>

new Vue({
el: '...',
data: {
selected: 'A',
options: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
]
}
})

对于单选按钮,勾选框及选择列表选项, v-model 绑定的 value 通常是静态字符串(对于勾选框是逻辑值)

// 当选中时,`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>

如果想绑定 value 到 Vue 实例的一个动态属性上,这时可以用 v-bind 实现,并且这个属性的值可以不是字符串

<input type="checkbox"  v-model="toggle" v-bind:true-value="a"  v-bind:false-value="b" />
// 当选中时
vm.toggle === vm.a
// 当没有选中时
vm.toggle === vm.b

<input type="radio" v-model="pick" v-bind:value="a"/>
// 当选中时
vm.pick === vm.a

修饰符

.number: 如果想自动将用户的输入值转为 Number 类型(如果原值的转换结果为 NaN 则返回原值),可以添加一个修饰符 number 给 v-model 来处理输入值

.lazy:在默认情况下, v-model 在 input 事件中同步输入框的值与数据,但你可以添加一个修饰符 lazy ,从而转变为在 change 事件中同步

.trim:如果要自动过滤用户输入的首尾空格,可以添加 trim 修饰符到 v-model 上过滤输入

<input v-model.number="age" type="number">

// 在 "change" 而不是 "input" 事件中更新
<input v-model.lazy="msg" >

<input v-model.trim="msg">

组件

组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素, Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展

注册

我们可以通过以下方式创建一个 Vue 实例,要注册一个全局组件,你可以使用 Vue.component(tagName, options),对于自定义标签名,Vue.js 不强制要求遵循 W3C规则 (小写,并且包含一个短杠),尽管遵循这个规则比较好

new Vue({
el: '#some-element',
// 选项
})

Vue.component('my-component', {
// 选项
})

组件在注册之后,便可以在父实例的模块中以自定义元素 <my-component></my-component> 的形式使用。要确保在初始化根实例 之前 注册了组件

<div id="example">
<my-component></my-component>
</div>

// 注册
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})

// 创建根实例
new Vue({
el: '#example'
})

//或者
var MyComponent = Vue.extend({
template : '<div>A custom component!</div>'
})
Vue.component('my-component', MyComponent)

局部注册

不必在全局注册每个组件。通过使用组件实例选项注册,可以使组件仅在另一个实例/组件的作用域中可用

var Child = {
template: '<div>A custom component!</div>'
}

//或者
var Child = Vue.extend({ '<div>A custom component!</div>' })

new Vue({
// ...
components: {
// <my-component> 将只在父模板可用
'my-component': Child
}
})

DOM 模版解析说明

当使用 DOM 作为模版时,你会受到 HTML 的一些限制.像这些元素 <ul> , <ol>, <table> , <select> 限制了能被它包裹的元素, <option> 只能出现在其它元素内部,自定义组件 <my-row> 被认为是无效的内容,因此在渲染的时候会导致错误。变通的方案是使用特殊的 is 属性,有必要的话请使用字符串模版

<table>
<my-row>...</my-row>
</table>

<table>
<tr is="my-row"></tr>
</table>

data 必须是函数

使用组件时,大多数选项可以被传入到 Vue 构造器中,有一个例外: data 必须是函数,否则 Vue 会在控制台发出警告,告诉你在组件中 data 必须是一个函数

<div id="example-2">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>

var data = { counter: 0 }
Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
// data 是一个函数,因此 Vue 不会警告,
// 但是我们为每一个组件返回了同一个对象引用
data: function () {
return data
}
})
new Vue({
el: '#example-2'
})

由于这三个组件共享了同一个 data , 因此增加一个 counter 会影响所有组件!我们可以通过为每个组件返回新的 data 对象来解决这个问题

data: function () {
return {
counter: 0
}
}

构成组件

组件意味着协同工作,通常父子组件会是这样的关系:组件 A 在它的模版中使用了组件 B 。它们之间必然需要相互通信:父组件要给子组件传递数据,子组件需要将它内部发生的事情告知给父组件。然而,在一个良好定义的接口中尽可能将父子组件解耦是很重要的。这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性。

在 Vue.js 中,父子组件的关系可以总结为 props down, events up 。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。看看它们是怎么工作的。



使用Props传递数据
组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。可以使用 props 把数据传给子组件.prop 是父组件用来传递数据的一个自定义属性。子组件需要显式地用 props 选项 声明 “prop”

Vue.component('child', {
// 声明 props
props: ['message'],
// 就像 data 一样,prop 可以用在模板内
// 同样也可以在 vm 实例中像 “this.message” 这样使用
template: '<span>{{ message }}</span>'
})

<child message="hello!"></child>

动态Props
类似于用 v-bind 绑定 HTML 特性到一个表达式,也可以用 v-bind 动态绑定 props 的值到父组件的数据中。每当父组件的数据变化时,该变化也会传导给子组件

<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>

<child :my-message="parentMsg"></child>

类似于用 v-bind 绑定 HTML 特性到一个表达式,也可以用 v-bind 绑定动态 Props 到父组件的数据。每当父组件的数据变化时,也会传导给子组件

<div>
<input v-model="parentMsg">
<br/>
<child v-bind:my-message="parentMsg"></child>
</div>

<child :my-message="parentMsg"></child>

单向数据流
子组件可以用 this.$parent 访问它的父组件。根实例的后代可以用 this.$root 访问它。父组件有一个数组 this.$children ,包含它所有的子元素。尽管可以访问父链上任意的实例,不过子组件应当避免直接依赖父组件的数据,尽量显式地使用 props 传递数据。另外,在子组件中修改父组件的状态是非常糟糕的做法,因为这让父组件与子组件紧密地耦合,还有只看父组件,很难理解父组件的状态。因为它可能被任意子组件修改!理想情况下,只有组件自己能修改它的状态。

prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop 。如果你这么做了,Vue 会在控制台给出警告。

//定义一个局部 data 属性,并将 prop 的初始值作为局部数据的初始值。
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}

//定义一个 computed 属性,此属性从 prop 的值计算得出
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
//注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。

Prop验证
组件可以为 props 指定验证要求。如果未指定验证要求,Vue 会发出警告。当组件给其他人使用时这很有用。prop 是一个对象而不是字符串数组时,它包含验证要求

Vue.component('example', {
props: {
// 基础类型检测 (`null` 意思是任何类型都可以)
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 value > 10
}
}
}
})

type 可以是原生构造器:String,Number,Boolean,Function,Object,Array。当 prop 验证失败了, Vue 将拒绝在子组件上设置此值,如果使用的是开发版本会抛出一条警告

自定义事件

我们知道,父组件是使用 props 传递数据给子组件,但如果子组件要把数据传递回去,应该怎样做?那就是自定义事件!

每个 Vue 实例都实现了事件接口(Events interface);使用 $on(eventName) 监听事件;使用 $emit(eventName) 触发事件。使用 $dispatch() 派发事件,事件沿着父链冒泡,使用 $broadcast() 广播事件,事件向下传导给所有的后代。另外,父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。

<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>

Vue.component('button-counter', {
template: '<button v-on:click="increment">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
increment: function () {
this.counter += 1
this.$emit('increment')
}
},
})

new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
//子组件已经和它外部完全解耦了。它所做的只是触发一个父组件关心的内部事件。

给组件绑定原生事件
有时候,你可能想在某个组件的根元素上监听一个原生事件。可以使用 .native 修饰 v-on

<my-component v-on:click.native="doTheThing"></my-component>

使用自定义事件的表单输入组件

自定义事件也可以用来创建自定义的表单输入组件,使用 v-model 来进行数据双向绑定

<input v-model="something">
//仅仅是一个语法糖
<input v-bind:value="something" v-on:input="something = $event.target.value">
// 所以在组件中使用时,它相当于下面的简写
<input v-bind:value="something" v-on:input="something = arguments[0]">
//所以要让组件的 v-model 生效,它必须接受一个 value 属性,在有新的 value 时触发 input 事件
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: