这章节主要是介绍 Vue 的基础语法,由于官网文档中的语法讲解都是在 HTML 中直接引入 Vue,这让一些刚上来使用 Vue-cli 的初学者很是头痛,因为这两个在写法上还是有一些区别,所以我们要介绍的就是 Vue-cli 中 Vue 的使用方法。

Options / Data

data

Vue 实例的数据对象,Vue 会遍历对象的所有属性,通过 Object.defineProperty 把这些属性全部转换为 getter/setter,但是这些对于用户来说是不可见的,在内部他们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
实例创建后,data 必须声明为一个返回初始数据对象的函数。

  1. export default {
  2. data () {
  3. return {
  4. title: 'Hello Vue'
  5. }
  6. }
  7. }

这里要注意的是,如果你的 data 属性使用了箭头函数,则 this 不会指向这个实例,但是仍然可以将实例作为函数的第一个参数来访问

  1. data: vm => ({ a: vm.myProp })

props

props 可以是数组或对象,用来接收父组件的数据。

  1. // 简单语法
  2. export default {
  3. props: ['title', 'message']
  4. }
  5. // 对象语法,提供校验
  6. export default {
  7. props: {
  8. title: String,
  9. age: {
  10. type: Number,
  11. default: 0,
  12. required: true,
  13. validator: (value) => {
  14. return value >= 0
  15. }
  16. }
  17. }
  18. }

computed

该属性可以理解为计算一个新的属性,并将该属性挂载到 vm (Vue 实例) 上,同样的,如果你为一个计算属性使用了箭头函数,则 this 不会指向这个组件的实例,不过你仍然可以将其实例作为函数的第一个参数来访问。
计算属性的结果会被缓存,多次访问计算属性会立即返回之前的计算结果,除非依赖的 data 返回的属性变化才会重新计算,如果某个依赖在该实例范围之外,则计算属性不会更新。

  1. export default {
  2. data () {
  3. return {
  4. age: 20
  5. }
  6. },
  7. computed: {
  8. ageDouble: function() {
  9. return this.age * 2
  10. },
  11. agePlus: {
  12. get: function() {
  13. return this.age + 1
  14. },
  15. set: function(value) {
  16. this.age = value + 1
  17. }
  18. },
  19. now: function() {
  20. return Date.now() // 不会更新,因为 Date.now
  21. }
  22. }
  23. }

methods

methods 将被混入到 Vue 实例中,可以通过实例直接访问这些方法,或在指令表达式中使用。但是注意不要使用箭头函数来定义 methods 函数,因为这里的 this 不会指向实例。

  1. export default {
  2. data () {
  3. return {
  4. age: 20
  5. }
  6. },
  7. methods: {
  8. add() {
  9. this.age += 1
  10. }
  11. }
  12. }

watch

watch 的结构还是一个对象,key 是要观察的表达式,value 为对应的回调函数,也可以是方法名,或包含选项的对象。不要用箭头函数来定义 watcher 函数

  1. export default {
  2. data() {
  3. a: 1,
  4. b: 2,
  5. c: 3,
  6. d: 4,
  7. e: {
  8. f: {
  9. g: 5
  10. }
  11. }
  12. },
  13. watch: {
  14. a: function (val, oldVal) {
  15. console.log('new: %s, old: %s', val, oldVal)
  16. },
  17. // 方法名
  18. b: 'someMethod',
  19. // 深度 watcher
  20. c: {
  21. handler: function (val, oldVal) { /* ... */ },
  22. deep: true
  23. },
  24. // 该回调将会在侦听开始之后被立即调用
  25. d: {
  26. handler: function (val, oldVal) { /* ... */ },
  27. immediate: true
  28. },
  29. e: [
  30. function handle1 (val, oldVal) { /* ... */ },
  31. function handle2 (val, oldVal) { /* ... */ }
  32. ],
  33. // watch vm.e.f's value: {g: 5}
  34. 'e.f': function (val, oldVal) { /* ... */ }
  35. }
  36. }

Directives

v-text

用来渲染和更新元素的 textContent,但是不如 {{}} 灵活。

  1. <span v-text="msg"></span>
  2. <span>{{ msg }}</span>

v-html

用来更新元素的 innerHTML,这里只是按照普通的 HTML 插入,但是如果插入的 HTML 较为复杂的话,应该考虑是否用组件去代替。

在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击。只在可信内容上使用 v-html永不用在用户提交的内容上。

单文件组件里,scoped 的样式不会应用在 v-html 内部,因为那部分 HTML 没有被 Vue 的模板编译器处理。如果你希望针对 v-html 的内容设置带作用域的 CSS,你可以替换为 CSS Modules 或用一个额外的全局 <style> 元素手动设置类似 BEM 的作用域策略。

  1. <p>Using mustaches: {{ rawHtml }}</p>
  2. <p>Using v-html directive: <span v-html="rawHtml"></span></p>

v-show

根据条件展示元素,要注意的一点是,v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性 display
v-show 不支持 <template> 元素,也不支持 v-else

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

v-if / v-else / v-else-if

根据表达式的值的真假条件渲染元素。和 v-show 不同的一点是,切换元素时,条件块内的事件监听器和子组件适当的被销毁和重建,并且v-if 是惰性的,也就是说如果初始渲染时条件为假,则保持什么都不做直到第一次变成真的,才会开始渲染条件块。
由此可见,v-if 有更高的切换开销,v-show 有更高的初始渲染开销。所以要根据实际情况选择合适的指令。

  1. <h1 v-if="ok">Yes</h1>
  2. <h1 v-else>No</h1>

v-ifv-for 一起使用时,v-for 具有比 v-if 更高的优先级。

v-for

基于源数据多次渲染元素或模板块。

一个数组的 v-for

  1. <div v-for="item in items"></div>
  2. <div v-for="(item, index) in items"></div>
  3. <div v-for="(val, key) in object"></div>
  4. <div v-for="(val, key, index) in object"></div>

v-for 默认行为试着不改变整体,而是替换元素。迫使其重新排序的元素,你需要提供一个 key 的特殊属性:

  1. <div v-for="item in items" :key="item.id">
  2. {{ item.text }}
  3. </div>

这里的 key 可以提升当源数据发生改变时的重绘效率。它默认用“就地复用”策略,如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。这里的 key 就可以便于 Vue 跟踪每个节点的身份,从而重用和重新排列现有数据。

Mutation Methods

这里是 Vue 所支持的修改数组的方法,这些方法会修改原数组,所以他们将会触发视图更新。
方法如下:

  • push()

  • pop()

  • shift()

  • unshift()

  • splice()

  • sort()

  • reverse()

Replacing an Array

既然有修改原数组的方法,那么也就有返回新数组的方法,例如 filter(), concat()slice() 所以当使用这些方法时,可以用新数组替换旧数组。
但是在你替换的时候,Vue 并不会丢弃现有的 DOM 去重新渲染整个列表,而是保留相同的元素,去修改不同的元素。所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。

Caveats

由于 JS 的限制,Vue 不能检测以下变动的数组

  1. 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue

  2. 当你修改数组的长度时,例如:vm.items.length = newLength

问题一可以使用 Vue.set() 来实现

  1. Vue.set(vm.items, indexOfItem, newValue)

问题二可以使用 splice

  1. vm.items.splice(newLength)

v-for with v-if

当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。当你想为仅有的一些项渲染节点时,这种优先级的机制会十分有用,如下:

  1. <li v-for="todo in todos" v-if="!todo.isComplete">
  2. {{ todo }}
  3. </li>

而如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素 (或