转自(https://zhuanlan.zhihu.com/p/23078117)
模板语法
Vue 提供了一堆数据绑定语法。
- {{ text }} 文本插值
- <div v-html="html"></div> HTML 输出
- v-bind HTML 属性插值。如<button v-bind:disabled="someDynamicCondition">Button</button>
- JavaScript 表达式。直接在 mustache、属性插值里面使用各种表达式(加减乘除、三元运算、方法调用等)。
- 过滤器(有点类似 Shell 命令中的管道,可以定义过滤器来对原始值进行变化)。
- 指令。之前提到的 v-bind 也是一种指定,其他包括 v-on: 系列(dom 事件的监听)、v-for、v-model等。
Vue 实例
Vue 实例,实则也就是 ViewModel(数据 + 函数),都是通过构造函数 Vue 创建的:
var data = { a: 1 } var vm = new Vue({ el: ‘#example‘, data: data, created: function () { // `this` 指向 vm 实例 console.log(‘a is: ‘ + this.a) } })//注意这里对vue对象属性的调用 vm.$data === data // -> true vm.$el === document.getElementById(‘example‘) // -> true // $watch 是一个实例方法 vm.$watch(‘a‘, function (newVal, oldVal) { // 这个回调会在 `vm.a` 改变的时候触发 })
Vue 实例都有自己的生命周期,比如 created, mounted, updated 以及 destroyed。所有方法被 called 的时候,this 都指向所在的 Vue 实例。
Lifecycle 图如下:
计算属性和监听器
计算属性
其实就是一个需要计算的 getter:
<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: { // 一个 computed getter reversedMessage: function () { // `this` 指向 vm 实例 return this.message.split(‘‘).reverse().join(‘‘) } } }) </div>
和使用 method 的区别在于,计算属性根据它的依赖被缓存,即如果 message 没有被修改,下次 get 不会进行重复计算,而 method 则每次调用都会重新计算。这也意味着如 Date.now() 这样返回的计算属性会永远得不到更新。
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] } } }
如此,当我们调用 vm.fullName = ‘MarkZhai‘ 的时候,firstName 和 lastName 都会被更新。
监听器
Vue 的 watch 也可以用来做类似的事:
<div id="demo">{{ fullName }}</div> 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 } } })
看上去好像简单了很多,那还要 Watcher 干啥呢。。。主要应用场景是异步或耗时操作:
<script> var watchExampleVM = new Vue({ el: ‘#watch-example‘, data: { question: ‘‘, answer: ‘I cannot give you an answer until you ask a question!‘ }, watch: { // 只要 question 改变,这个函数就会执行 question: function (newQuestion) { this.answer = ‘Waiting for you to stop typing...‘ this.getAnswer() } }, methods: { // _.debounce is a function provided by lodash to limit how // often a particularly expensive operation can be run. // In this case, we want to limit how often we access // yesno.wtf/api, waiting until the user has completely // finished typing before making the ajax request. To learn // more about the _.debounce function (and its cousin // _.throttle), visit: Lodash Documentation getAnswer: _.debounce( function () { var vm = this if (this.question.indexOf(‘?‘) === -1) { vm.answer = ‘Questions usually contain a question mark. ;-)‘ return } vm.answer = ‘Thinking...‘ axios.get(‘https://yesno.wtf/api‘) .then(function (response) { vm.answer = _.capitalize(response.data.answer) }) .catch(function (error) { vm.answer = ‘Error! Could not reach the API. ‘ + error }) }, // 等待用户停止输入后的时间(毫秒) 500 ) } }) </script>
如此,使用 watch 让我们可以进行异步操作(访问 API),限制操作间隔,并设置中间状态直到获得了真正的答案。
除了使用 watch option,也可以用 vm.$watch API。
Class 和 Style 绑定
除了数据绑定,常见的还有 style、class 的绑定(正如很久以前在 JQuery 中常用的)。
对象语法
我们可以传递一个对象给 v-bind:class 来动态切换 classes:
<div class="static" v-bind:class="{ active: isActive, ‘text-danger‘: hasError }"> </div>
对应的 active 和 text-danger 则通过 data 传递过来。
我们也可直接通过 data 把 class 传递过来
<div v-bind:class="classObject"></div>
data: {
classObject: {
active: true,
‘text-danger‘: false
}
}
当然我们也能使用上面提到的 computed 来进行对应属性,如 active 的计算。
数组语法
可以直接传递一个数组给 v-bind:class:
<div v-bind:class="[activeClass, errorClass]"> data: { activeClass: ‘active‘, errorClass: ‘text-danger‘ } 也可以写成 <div v-bind:class="[isActive ? activeClass : ‘‘, errorClass]"> <div v-bind:class="[{ active: isActive }, errorClass]">
绑定内联样式
跟 class 差不多:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + ‘px‘ }"></div>
或者直接绑定到 style:
<div v-bind:style="styleObject"></div> data: { styleObject: { color: ‘red‘, fontSize: ‘13px‘ } }
类似的,也有数组绑定。
条件绑定
v-if
其实就是个标签啦
<h1 v-if="ok">Yes</h1> <h1 v-if="ok">Yes</h1> <h1 v-else>No</h1>
因为 v-if 必须附加到一个单一 element 上,那如果我们想切换多个元素呢?可以使用 template 元素:
<template v-if="ok"> <h1>Title</h1> <p>Paragraph 1</p> <p>Paragraph 2</p> </template>
v-show
也可以用 v-show 来做条件显示的逻辑,
<h1 v-show="ok">Hello!</h1>
区别在于
- v-show 不支持 template 和 v-else
- v-if 是 lazy 的,不会渲染没有走到的条件。而 v-show 只是简单的基于 CSS 的切换。所以 v-show 的初始 render 代价较高。
- 由于 v-if 是真实的渲染,切换后原来的 dom 会被 destroyed,而新的 dom 会被重新创建。所以切换代价更高。
所以如果切换得较为频繁可以使用 v-show,如果在运行时不太会改变则可以使用 v-if。
列表渲染
v-for
其实就是个循环标签啦:
<ul id="example-2"> <li v-for="(item, index) in items"> {{ parentMessage }} - {{ index }} - {{ item.message }} </li> </ul> 对应的 vm 实例: var example2 = new Vue({ el: ‘#example-2‘, data: { parentMessage: ‘Parent‘, items: [ { message: ‘Foo‘ }, { message: ‘Bar‘ } ] } })
模板 v-for
跟 v-if 类似,我们也能在 template 上使用 v-for:
<ul> <template v-for="item in items"> <li>{{ item.msg }}</li> <li class="divider"></li> </template> </ul>
对象 v-for
也能使用 v-for 遍历对象的属性:
<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 } } })
看到 value,那肯定还有 key 了:
<div v-for="(value, key) in object"> {{ key }} : {{ value }} </div>
如果再加上 index:
<div v-for="(value, key, index) in object"> {{ index }}. {{ key }} : {{ value }} </div>
其他还有像是 v-for="n in 10" 这种用法,就不加上例子了。
组件 v-for
input 输出内容到 newTodoText,每次点击 enter 都会触发 addNewTodo,然后添加 item 到 todos,触发新的 li 添加进去:
(通过这个例子发现这个vue真的太强大了,原谅我一个刚从jquery转过来的人)
<div id="todo-list-example"> <input v-model="newTodoText" v-on:keyup.enter="addNewTodo" placeholder="Add a todo" > <ul> <li is="todo-item" v-for="(todo, index) in todos" v-bind:title="todo" v-on:remove="todos.splice(index, 1)" ></li> </ul> </div> Vue.component(‘todo-item‘, { template: ‘ <li> {{ title }} <button v-on:click="$emit(\‘remove\‘)">X</button> </li> ‘, props: [‘title‘] }) new Vue({ el: ‘#todo-list-example‘, data: { newTodoText: ‘‘, todos: [ ‘Do the dishes‘, ‘Take out the trash‘, ‘Mow the lawn‘ ] }, methods: { addNewTodo: function () { this.todos.push(this.newTodoText) this.newTodoText = ‘‘ } } })
key
当 vue 在更新被 v-for 渲染的列表时候,会使用就地 patch 的策略,而不是根据元素改变的顺序。我们可以提供 key 来做这个排序:
<div v-for="item in items" :key="item.id"> <!-- content --> </div>
如此,item 会根据 id 来做排序。
数组改变监测
替换方法(mutation)
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
这些方法会改变原来的 array,并自动触发 view 的更新。
替换 array
- filter()
- concat()
- slice()
这几个方法会返回新的 array,如:
example1.items = example1.items.filter(function (item) { return item.message.match(/Foo/) })
附加说明
如果
- 直接 set array 的值,如 vm.items[indexOfItem] = newValue
- 修改 array 的长度,如 vm.items.length = newLength
都是没法触发更新的,需要使用
Vue.set(example1.items, indexOfItem, newValue)
// Array.prototype.splice`
example1.items.splice(indexOfItem, 1, newValue)
example1.items.splice(newLength)
过滤/排序
配合 computed 以及 filter,或者也可以使用 v-for 的条件渲染:
<li v-for="n in even(numbers)">{{ n }}</li> data: { numbers: [ 1, 2, 3, 4, 5 ] }, methods: { even: function (numbers) { return numbers.filter(function (number) { return number % 2 === 0 }) } }
事件处理
监听事件
使用 v-on 指令监听 DOM 的各种事件,如:
<div id="example-1"> <button v-on:click="counter += 1">Add 1</button> <p>The button above has been clicked {{ counter }} times.</p> </div> var example1 = new Vue({ el: ‘#example-1‘, data: { counter: 0 } })
除了直接写 JS 语句,也可以直接在 v-on 中调用 methods 中定义的事件,还可以进行传参:
<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) } } })