Vue官方教程笔记(Vue 1.X)

1.安装

可以简单地在页面引入Vue.js作为独立版本,Vue即被注册为全局变量,可以在页面使用了。

如果希望搭建大型应用,则可以使用npm安装Vue.js:

# 最新稳定版本
$ npm install vue
# 最新稳定 CSP 兼容版本
$ npm install vue@csp

甚至可以使用vue官方推荐的项目模板来创建新的大型项目,这就需要使用vue命令行工具vue-cli:

# 全局安装 vue-cli
$ npm install -g vue-cli
# 创建一个基于 "webpack" 模板的新项目
$ vue init webpack my-project
# 安装依赖,走你
$ cd my-project
$ npm install
$ npm run dev

2.概述

Vue的目标是实现响应的数据绑定和组合的视图组件,所以其核心只有两块:

  • 数据绑定系统
  • 组件系统

数据通过在HTML模板中的指令和“Mustache”语法,绑定到对应的HTML元素上,其底层是JavaScript对象的存取器属性和原生javascript事件。

组件系统则通过扩展的Vue实例,来渲染位于HTML中的类似于自定义元素的Vue组件,从而实现独立可复用的组件。

3.Vue实例

每个Vue应用的起步都是从一个Vue的根实例开始:

var vm = new Vue({
    //选项,包含数据、模板、挂载元素等
})

而每个组件,也是一个扩展的vue实例:

var myCompnent = Vue.extend({
    //选项,包含组件的数据、模板、挂载元素等
})

Vue实例会代理设置在data选项中的数据,所有data选项中的数据都可以通过vm本身进行访问:

var vm = new Vue({
    data: {
        name: 'chen'
    }
})
data.name  // -> chen
vm.name = 'wei'
data.name // -> wei

除了数据属性,Vue实例还暴露了一些有用的实例属性与方法,这些属性与方法都有前缀$,区别于数据属性(参考API文档):

vm.$watch
vm.$data
vm.$el

在一个Vue实例的生命周期的不同时间,可以调用相应的回调函数:


实例生命周期

4.数据绑定

有两种方式可以进行数据绑定:插值和指令。

4.1 插值

使用双大括号语法来绑定数据到HTML中,使用三大括号语法来输出HTML字符(不常用)。插值也可以用在HTML属性中:

<span> {{msg }}</span>
<div>{{{ raw_html }}}</div>
<div id="item-{{ id }}"></div>

4.2 指令

使用指令 (Directives,是特殊的带有前缀v-的特性)来将数据绑定到HTML,通常用于应用特殊行为到DOM上:

<p v-if="greeting">Hello!</p>   <!--当greeting为真时p存在-->

指令有三个需要记住的特征:

  • 参数:v-on:click="doSomething" ,这里clickv-on指令的参数
  • 修饰符:v-on:click.stop="doSomething",这里.stop是修饰符,表示阻止冒泡
  • 缩写:v-bind指令可缩写为简单的:号,v-on指令可以缩写为@
<a :href="url"></a>
<a @click="doSomething"></a>

插值的内容和指令的值,都被限定为只能使用绑定表达式,它由一个JavaScript表达式和可选的一个或多个过滤器构成。需要注意JavaScript表达式和语句的区别,而过滤器则是一个JavaScript函数,Vue提供了很多内置的过滤器,也可以自己编写自定义过滤器:

{{ message | filterA | filterB }}
{{ message | filterA 'arg1' arg2 }} // 过滤器也可以接受参数

5.计算属性

绑定表达式被限定只能使用一个JavaScript表达式,目的是为了限制在数据绑定中放入过多的逻辑。如果需要更复杂的数据处理逻辑,则应该使用计算属性:

var vm = new Vue({
  el: '#example',
    data: {
      a: 1
    },
    conmpued: {
      a () {
        return this.a+1
      }
    }
})

5.1 getter和setter

Vue默认将提供的回调函数作为计算属性的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]
    }
  }
}

5.2 $watch

Vue还提供了一个$watch方法用于监视数据变动:

vm.$watch('a', val => {
  console.log(val)
})
// 或者写成:
var vm = new Vue({
  … …
  watch: {
    'a' (val) { // 以需要监测的属性为属性名
      console.log(val)
    }
  }
})

但是因为可读性,推荐使用计算属性而不是watch方法。

6.Class与Style绑定

由于其常用性,Vue增强了classstyle两个HTML属性的绑定,不仅仅可以接受字符串值,还可以接受数组和对象两种类型的值。

6.1 class绑定

v-bind:class="value",接受字符串,也可以接受对象和数组,并且还可以和普通的class属性共存:

<!-- 接受字符串 -->
v-bind:class="classA"
<!-- 接受对象 -->
v-bind:class="{ 'class-a': isA, 'class-b': isB }"
<!-- 直接绑定数据里的对象,或者计算属性返回的对象 -->
v-bind:class="classObject"
<!-- 与普通class属性共存 -->
class="some-class" v-bind:class="{ 'class-a': isA, 'class-b': isB }"
<!-- 接受数组 -->
v-bind:class="[classA, classB]"
<!--直接绑定数据里的数组,或者计算属性返回的数组 -->
v-bind:class="classArray"
<!--数组语法与对象语法混用  1.0.19+ -->
v-bind:class="[classA, { 'class-b': isB, 'class-c': isC}]"

6.2 style绑定

内联样式绑定也可以接受对象或者数组,当接受对象时语法非常直观(看着非常像CSS语法),当接受数组时可以将多个样式对象应用到元素上。对象和数组都可以直接来自数据data或者computed或者props

同时内联样式的绑定Vue会自动侦测浏览器并添加相应浏览器前缀。

<!-- 接受对象 -->
v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"
<!-- 直接绑定数据里的对象,或者计算属性返回的对象 -->
v-bind:style="styleObject"

v-bind:style="[styleObjectA, styleObjectB]"

7.条件渲染

Vue可以根据指定的条件来决定是否渲染元素,有两个指令v-ifv-show,用法基本一致。

7.1 条件渲染一个元素

<h1 v-if="isOk">Hello</h1>

7.2 条件渲染多个元素

使用一个template 元素包裹多个元素并条件渲染它。

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

7.3 v-else

还可以使用v-else来为条件渲染添加一个else块:

<div v-if="isOk">hello</div>
<!-- v-else指令必须紧跟v-if或v-show否则不生效 -->
<div v-else>fuck off</div>
<!-- 这两个元素仅会显示一个 -->

7.4 组件中的条件渲染

组件也可使用条件渲染,但是将v-show用在组件中时,由于指令的优先级问题,不能在后面使用v-else指令,而应该使用另一个v-show来替代:

<my-compnent v-show="condition"></my-component>
<p v-else>这可能也是一个组件</p> <!--错误用法-->
        
<my-compnent v-show="condition"></my-component>
<p v-show="!condition">这可能也是一个组件</p> <!--正确用法-->

7.5 v-if和v-show的区别

编译区别v-if是惰性的,如果初始条件为假则这个元素不会被编译,只有当条件第一次为真时,Vue才编译并缓存编译结果以供后续使用;v-show的元素则直接编译。

显示区别v-if是真实的在HTML中重建或者销毁这个元素,v-show则只是通过display来控制,简单得多。

结论v-if有更高的切换消耗而v-show有更高的初始渲染消耗,频繁切换用v-show,条件稳定用v-if

8.列表渲染

使用v-for指令,Vue可以根据一个数组,渲染一组列表元素。

8.1 数组渲染

<li v-for="item in items">{{ item.message + ":" + $index }}</li>
<!-- items是数据中的一个数组,item是数组元素的别名 -->
<!-- 有一个特殊变量:$index,是数组元素的索引 -->

8.2 变异方法:

Vue会更新被观察的数组items的大部分修改自身的方法:push()pop()shift()unshift()splice()sort()reverse()调用这些方法时数组的变动会被实时反映在数据绑定中,而那些不会修改原数组而是返回新数组的方法:filter()concat()slice()则需要使用其结果替换原数组,数据变动才能反映在数据绑定中。

8.3 追踪指令:

数组替换并不会完全重新渲染整个列表,Vue使用了一些启发算法提高了性能。默认这个算法是根据数组元素本身的值来追踪和复用DOM,这会导致数组中重复的值只会渲染一次,这时可以使用track-by="$index"指令强制Vue进入原位更新模式(只有$index位置的值与上次位置的值一致时才复用DOM元素,否则立刻重新生成并渲染新的DOM元素),但在需要同步临时状态和组件的私有状态时需谨慎使用。

如果数组中每个元素都有一个相同但值唯一的属性,比如下面这样

{
  items: [
    { _uid: '88f869d', ... },
    { _uid: '7496c10', ... }
  ]
}

则可以指定track-by="_uid"来让Vue尽可能地复用DOM以提高性能。

另外,如果数组元素是已经被Object.freeze()方法冻结的对象,则需要使用track-by指令明确指定追踪属性,否则将不能自动追踪(被冻结的对象无法设置存取器属性)

8.4 检测盲点:

由于JavaScript本身的限制,Vue不能检测到两种数据变动:

  1. 直接使用索引赋值:vm.items[0] = {}
  2. 修改数组的长度:vm.items.length = 0

应该使用下面两种技术进行替换:

  1. 数组的Vue扩展方法:
var item = { changeMsg: 'changed!' }
vm.items.$set(0, item)
vm.items.$remove(item)
  1. 使用空数组替换:vm.items = []

8.5 对象渲染

遍历对象时可访问一个特殊变量$key,表示对象的键名

<ul>
  <li v-for="value in object"> <!-- value是对象键值的别名,object是数据对象的别名 -->
    {{ $key }} : {{ value }}
  </li>
</ul>

也可以给对象的键名提供一个别名

<ul>
  <li v-for="(key, value) in object"> <!-- value是对象键值的别名,object是数据对象的别名 -->
    {{ key }} : {{ value }}
  </li>
</ul>

对象遍历的结果是按Object.keys()的结果来的,不保证在所有引擎中都一致。

8.6 值域渲染

v-for也可以接受一个整数表示值域,即简单重复渲染元素:

<div>
  <span v-for="n in 10">{{ n }}</span> /* 渲染10个span */
</div>

8.7 渲染多个元素

将v-for指令用在<template></template>元素上,可以重复渲染多个元素:

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

8.8 过滤和排序

有时我们想显示过滤/排序过的数组,同时不实际修改或重置原始数据。有两个办法:

  1. 创建一个计算属性,返回过滤/排序过的数组;
  2. 使用内置的过滤器filterByorderBy

计算属性有更好的控制力,也更灵活,因为它是全功能 JavaScript;但是通常过滤器更方便。

9.方法与事件处理器

9.1 用法

使用v-on指令监听DOM事件,v-on后接冒号传入事件类型名称,指令的值为事件处理器,事件处理器只能指定为methods中的一个方法或者一个JavaScript语句(通常是方法调用语句,方便传值):

<button v-on:click="sayHi">click me</button>
<button v-on:click="say('Hello!')">click me</button>

9.2 事件对象

事件处理器可以接受一个特殊参数即为事件对象,使用$event表示:

<button v-on:click="say('hello', $event)">click me</button>

9.3 修饰符

v-on指令支持两类修饰符:事件修饰符和按键修饰符

事件修饰符有四个(1.0.16+):

  • prevent用于阻止默认事件(相当于event.preventDefault());
  • stop用于阻止冒泡(相当于event.stopPropagation());
  • capture表示添加侦听事件时使用捕获模式;
  • self表示只当事件是从侦听器绑定的元素本身触发时才触发回调。

按键修饰符则表示只在指定按键上触发事件(通常在监听键盘事件时使用,keyupkeydownkeypress

<input v-on:keyup.13="submit">

除了使用keycode,按键修饰符还可以使用vue提供的按键别名:

entertabdeleteescspaceupdownleft``right

1.0.8+可以使用单字母别名,所有26个字母都可以作为按键别名使用,1.0.17可以自定义按键别名:

Vue.directive('on').keyCodes.f1 = 112

10.表单控件绑定

10.1 用法

使用v-model指令在表单控件中创建双向数据绑定。

  1. 文本输入框input中的输入与model.text的值实时双向同步
<input type="text" v-model="model.text" />
  1. 单选按钮:选中则model.ratio的值为true,取消选中则model.ratio值变为false;反过来model.ratio值也改变选中状态
<input type="ratio" v-model="model.ratio" />
  1. 多选框:单个多选框与单选框一样,多个多选框则需要绑定到一个数组上,并写好它们的value值以便填充到数组或从数组中删除
<input type="checkbox" value="jack" v-model="model.anArray" />
<input type="checkbox" value="john" v-model="model.anArray" />
<input type="checkbox" value="awey" v-model="model.anArray" />
  1. 下拉框:单选的下拉框直接绑定即可,选中的选项的value或text也即该下拉框的value会与绑定的数据实时双向同步,多选的下拉框则需要绑定到一个数组上,选中的选项的value会添加到数组中。即使选项是v-for动态生成的,也不影响数据的双向绑定。
<select v-model="model.anArray" multiple>
  <option selected>A</option>
  <option>B</option>
  <option>C</option>
</select>

10.2 绑定value

所有表单控件都只能使用v-model而不能使用v-bind:value来进行数据双向绑定,因为v-bind指令在绑定普通属性(除了子组件的prop,它可以使用async修饰符)时,是数据到HTML的单向数据绑定,HTML中的变化是不会同步到数据中的。

但是如果希望v-model双向绑定的HTML和数据属性,指向的是vue实例中的另一个数据属性(比如有两个属性a和b,其中a绑定到v-mode上,b是另一个属性,如果希望radio和checkbox选中时,a的值为b,不选中时a的值为默认值),则可以使用v-bind:value来实现

  1. 单选按钮:如果单选按钮选中,则isPicked指向anotherDataProperty(两个属性都是vue实例的data)
<input type="radio" v-model="isPicked" v-bind:value="anotherDataProperty">
  1. 多选框:如果勾选则isSelected指向a,否则指向b,多选则是数组的元素指向a或b、
<input type="checkbox" v-model="isSelected" v-bind:true-value="a" v-bind:false-value="b">
  1. 下拉框:如果选中a项,则selected的值指向a,多选则是数组的元素指向a
<select v-model="selected">
  …
  <option v-bind:value="a">a</option>
  …
</select>

在这种情况下使用v-bind:value指令,其值不限于字符串,可以是任意JavaScript值:

<input type="radio" v-model="isPicked" v-bind:value="{ name: awey, age: 26 }">

10.3 三个特性

v-model可以使用三个特性,lazydebouncenumber

  • lazy:v-model在同步数据时使用的是input事件(可能是keyup事件,待考证)来进行同步,如果觉得太过频繁可使用lazy特性将同步改到change事件中
<input type="text" v-model="inputValue" lazy />
  • debounce:设置一个最小延时,在每次敲击之后延时同步输入框的值与数据,(如果已经使用lazy则无效)
<input type="text" v-model="inputValue" debounce="500"/>
  • number:将用户输入的值转为number,不能转为Number类型则返回原值
<input type="text" v-model="inputValue" number />

表单控件有一些诸如checkeddisabled这类HTML属性,无论赋值与否只要存在该属性即可生效。但是在使用v-bind绑定这类属性时,vue对其做了处理,值为真假值即可让属性生效或不生效,无需再有上述顾虑。

11.过渡动画

在插入和移除或者显示和隐藏DOM元素时,通过Vue的过渡动画系统,可以给元素添加三种类型的动画:CSS过渡(trasition或者animateframe),JavaScript过渡和渐进过渡。v-if、v-show、v-for(可以使用 vue-animated-list插件)三个指令可以使用过渡动画。

只需要在元素上添加transition属性即可应用过渡动画

<div v-if="show" transition="my-transition"></div>

11.1 CSS transition

在元素上添加transition属性

<div v-if="show" transition="expand"></div>

然后添加CSS过渡动画

/* 必需 */
.expand-transition {
  transition: all .3s ease;
  height: 30px;
  padding: 10px;
  background-color: #eee;
  overflow: hidden;
}
/* .expand-enter 定义进入的开始状态 */
/* .expand-leave 定义离开的结束状态 */
.expand-enter, .expand-leave {
  height: 0;
  padding: 0 10px;
  opacity: 0;
}

如果transition属性没有值,则默认的CSS类名是.v-transition, .v-enter.v-leave。默认的进入和退出动画都是*-leave的形式,如果希望使用其它的CSS类名,则可以自定义,这样便能很好配合第三方CSS库比如animate.css

Vue.transition('bounce', {
    enterClass: 'bounceInLeft',
    leaveClass: 'bounceOutRight'
});
<div v-show="ok" class="animated" transition="bounce">Watch me bounce</div>

11.2 CSS animation

用法与CSS transition一样,唯一的区别是CSS transition依靠删除类名来触发transition,元素一插入文档便即刻删除类名;而CSS animation则是在CSS动画的animationend事件中来删除类名,因为它需要等到动画运行结束才能删除,否则动画不起效

<span v-show="show" transition="bounce">Look at me!</span>
.bounce-transition {
  display: inline-block; /* 否则 scale 动画不起作用 */
}
.bounce-enter {
  animation: bounce-in .5s;
}
.bounce-leave {
  animation: bounce-out .5s;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}
@keyframes bounce-out {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(0);
  }
}

11.3 显示地声明是CSS过渡还是CSS动画

如果只使用了css transtion或者css animation其中一个来实现动画,则vue会根据css样式自动判断是哪种类型的动画,从而监听对应的事件(transitionend或者animationend)。

但是有时会出现冲突,比如你使用了css animation来实现动画,但是又定义了鼠标悬停时的css transition动画,这时这两类事件都会被该元素触发,则你需要显示地声明vue应该监听哪类事件:

Vue.transition('bounce', {
  // 该过渡效果将只侦听 `animationend` 事件
  type: 'animation'
})

11.4 动画各阶段回调函数

在动画的各个阶段都可以提供回调函数,vue会在相应的动画阶段调用它们:

Vue.transition('expand', {
  beforeEnter: function (el) {
    el.textContent = 'beforeEnter'
  },
  enter: function (el) {
    el.textContent = 'enter'
  },
  afterEnter: function (el) {
    el.textContent = 'afterEnter'
  },
  enterCancelled: function (el) {
    // handle cancellation
  },

  beforeLeave: function (el) {
    el.textContent = 'beforeLeave'
  },
  leave: function (el) {
    el.textContent = 'leave'
  },
  afterLeave: function (el) {
    el.textContent = 'afterLeave'
  },
  leaveCancelled: function (el) {
    // handle cancellation
  }
})

11.5 JavaScript过渡

既然在动画的各个阶段都有回调函数,自然,我们完全可以不定义任何CSS,直接使用JavaScript来定义元素动画。注意,enter和leave需要调用done回调函数,否则动画将立即结束。

Vue.transition('fade', {
  css: false, // 显示地声明为非css回调,vue将跳过css检测,同时也能防止css规则干扰过渡
  enter: function (el, done) {
    // 元素已被插入 DOM
    // 在动画结束后调用 done
    $(el)
      .css('opacity', 0)
      .animate({ opacity: 1 }, 1000, done)
  },
  enterCancelled: function (el) {
    $(el).stop()
  },
  leave: function (el, done) {
    // 与 enter 相同
    $(el).animate({ opacity: 0 }, 1000, done)
  },
  leaveCancelled: function (el) {
    $(el).stop()
  }
})

11.6 动态绑定transition

你可以在同一元素上通过动态绑定实现不同的过渡:

<div v-if="show" :transition="transitionName">hello</div>
new Vue({
  el: '...',
  data: {
    show: false,
    transitionName: 'fade'
  }
})

11.7 渐进过渡

transitionv-for一起用时可以创建渐近过渡。所谓渐进过渡,即每个元素进入或退出都比上一元素延迟一点,不是同时进入或退出。只需要给过渡元素添加一个特性staggerenter-staggerleave-stagger,即可添加渐进过渡:

<div v-for="item in list" transition="stagger" stagger="100"></div>

或者提供一个钩子 stagger, enter-stagger 或 leave-stagger,以更好的控制:

Vue.transition('stagger', {
  stagger: function (index) {
    // 每个过渡项目增加 50ms 延时
    // 但是最大延时限制为 300ms
    return Math.min(300, index * 50)
  }
})

12.组件

使用组件来封装可重用的代码,使用Vue.extend()来构造组件,使用Vue.component()或者选项对象中的components属性来注册组件,使用自定义标签<component-name></component-name>来使用组件

12.1 基本使用

组件的使用分为3个步骤:

  1. 定义组件
var MyComponent = Vue.extend({
          template: '<div>A custom component!</div>'
        })
  1. 注册组件
Vue.component('my-component', MyComponent) // 全局注册
var Parent = Vue.extend({
  template: '...',
  components: {
    // <my-component> 只能用在父组件模板内
    'my-component': Child
  }
})

组件可以注册在全局,也可以注册在另一个组件内。在哪注册则仅能在该作用域使用

Vue.extend()可以将组件的注册和定义一次写完,只需要将定义组件时传入给extend()的对象直接传递给components内即可

var Parent = Vue.extend({
  template: '...',
  components: {
    // <my-component> 只能用在父组件模板内
    'my-component': {
        template: '<div>A custom component!</div>'
    }
  }
})
  1. 使用组件
<div id="example">
  <my-component></my-component>
</div>

12.2 选项和模板解析注意事项:

  • 组件选项。大部分用于new Vue()的选项都可以在构造组件时使用,但有两个特例:datael,它们在vue()构造器中是对象或者对象的引用,如果组件也这么用,将造成所有组件共享一个对象引用的结果,组件之间存在干扰,所以这两个选项需要使用函数返回新对象
var MyComponent = Vue.extend({
  data: function () {
    return { a: 1 }
  }
})
  • Vue使用的是DOM模板而非字符串模板,也就是说Vue内部处理的是真正的DOM元素而不是处理字符串。而DOM模板的一个重要限制就是它必须是有效的HTML片段,而很多DOM元素都对内部的子元素类型有限制,比如:
a 不能包含其它的交互元素(如按钮,链接)
ul 和 ol 只能直接包含 li
select 只能包含 option 和 optgroup
table 只能直接包含 thead, tbody, tfoot, tr, caption, col, colgroup
tr 只能直接包含 th 和 td

所以对模板的使用限制就很明显了,自定义元素和特殊元素不能作为像table这样对内部元素有限制的元素的直接子元素,也不能作为像option这样只能作为select子元素的元素的父元素(违反这个规则将渲染不正确)

当需要将某个限制类的元素用作模板时,使用is指令来规避以上限制:

<table>
  <tr is='my-component'></tr>
</table>

12.3 向组件传递数据

12.3.1 基本用法

组件实例的作用域是孤立的,但可以通过组件的props属性向组件传递数据:

Vue.component('child', {
  template: "<p>msg</p>",
  props: ['msg'] // props中数据的读写与data属性中的数据一致
});
<child msg='hello!'></child> <!--在父组件中向子组件传递数据-->
12.3.2 驼峰和短横线

当使用驼峰写法声明props属性时,在使用该属性传递数据时需要写为短横线格式:

...
props: ['myProp'],
...
<child my-prop='hello!'></child>
12.3.3 动态绑定和单向/双向绑定

使用v-bind可以在父组件向子组件中传递动态数据或者实际的值而不是字符串:

<child :my-prop="someDataFromFather"></child> <!--传递动态数据-->
<child :my-prop="{ a:1, b:2 }"></child> <!--传递实际的值-->

绑定动态数据时,props默认是单向绑定,父组件的数据变化会传递并反映在子组件中,也可以使用.async修饰符强制双向绑定,子组件中改变props的值也会反映在父组件中,单并不推荐,因为会让数据流不好理解

<child :my-prop.async="someDataFromFather"></child> <!--传递实际的值-->
12.3.4 验证规则

可以为props指定验证规则,props设置不再是数组而是对象,每个prop都是一个规定了验证要求的对象,可以设置的验证要求包括:
* type:基础类型检测,可以设置为String、Number、Boolean、Function、Object、Array,当只需要这一个验证时可直接设置prop: String,如果可以是多种类型,则可设置prop: [Number, String]
* required:是否必须,boolean
* default:默认值
* twoWay:是否双向
* validator:自定义验证函数
* coerce:自定义转换函数,在设置之前转换值

当prop验证失败了,Vue将拒绝在子组件上设置此值,如果使用的是开发版本会抛出一条警告
另外需要注意,propsdefalut可以使用一个function动态返回一个值,所以如果期望一个prop是function类型,则应当在匿名函数中返回这个function

12.4 组件树通信

除了props属性能够让父子组件之间通信外,还有下面的一些方式能够在组件树种进行通信

12.4.1 父链——父子组件互相访问
* 子组件可以通过this.$parent访问父组件
* 父组件通过this.$children访问包含了所有子组件的数组
* 根实例后代通过this.$root访问根实例(也即new Vue())
* 尽可能避免使用父链进行通信,应当尽可能使用props显示声明
12.4.2 自定义事件——异步事件通信机制

Vue实现了一套自定义事件接口用于组件树通信,独立于DOM事件
* 触发事件:
* $emit() 触发事件,子组件事件只能在子组件中被监听
* $dispatch() 派发事件,事件沿着父链冒泡,子组件事件可以在父组件中监听
* $broadcast() 广播事件,事件向下传递给所有后代,父组件事件可以在子组件中被监听
* 监听事件:
* $on() 监听事件
* events选项监听事件
* v-on指令监听事件

注意,Vue事件会在冒泡后第一次触发执行事件处理函数后停止冒泡,除非在事件处理函数中显式返回true

Vue官方推荐当一个子组件派发事件后,在父组件中使用子组件的时候v-on监听,是最直观的方式,因为这样能够直观知道事件来自哪里:

<child :msg="hello!" v-on:customEvent="handler"></child>
12.4.3 子组件索引——父组件直接访问子组件

有时仍需要在JavaScript中直接访问子组件,可以使用v-ref指令指定子组件索引

<child v-ref:profile></child>

然后在注册它的父级中直接访问:

var child =  parent.$refs.profile

当和v-for一起使用时是一个数组或者对象,包含相应的子组件

12.4.4 使用slot分发内容——父组件向子组件下发内容

当你的子组件有容器的功能时,子组件的部分内容(通常是html结构)就不确定了,比如你定义了一个列表组件,但是你希望你的列表中每一项的内容能够在使用组件的时候给定,而不是写死在组件中,这时就需要用到slot来向组件内分发内容,唯一需要记住的是:

父组件模板的内容在父组件作用域内编译,子组件模板中的内容在子组件作用域内编译
slot中的分发内容是在父组件中编译的

  • 定义slot接口(在组件内):
<!-- 单个slot -->
<template>
  <div>
    <slot></slot>
  </div>
</template>
<template>
<!-- 多个slot(具名slot) -->
  <div>
    <slot name="title"></slot>
    <p>ajsf  ah  askhjdf a askjfh a fhasdkfu9fa ajsh ia uiasd gak jgasdfh la ioa akjsflai a</p>
    <slot name="footer"></slot>
  </div>
</template>
  • 使用slot接口(使用组件时):
<!-- 单个slot -->
<my-component>
  <p>这里的所有内容都将插入组件的slot中<p>
  <div>这里的所有内容都将插入组件的slot中</div>
</my-component>
<!-- 多个slot(具名slot) -->
<my-component>
  <p slot="title">这里的内容将插入组件的name="title"的slot中<p>
  <div slot="footer">这里的所有内容将插入组件的name="footer"的slot中</div>
  <p>这里的内容将被插入组件中未署名的slot中,如果没有未署名的slot则将被忽略</p>
</my-component>
12.4.5 动态组件

可以将多个组件挂在到同一个挂载点上,然后动态切换它们。使用保留的<component>元素,将组件动态地绑定到它的is属性上:

new Vue({
  el: 'body',
  data: {
    currentView: 'home'
  },
  components: {
    home: { /* ... */ },
    posts: { /* ... */ },
    archive: { /* ... */ }
  }
})
<component :is="currentView">
  <!-- 组件在 vm.currentview 变化时改变 -->
</component>

可以使用keep-alive指令将切换出去的组件保留在内存中,避免重新渲染并且保留它的状态

<component :is="currentView" keep-alive></component>

组件中可以使用activate钩子来在切入前做一些事情,通常是异步操作比如加载数据等等:

Vue.component('activate-example', {
  activate: function (done) {
    var self = this
    loadDataAsync(function (data) {
      self.someData = data
      done()
    })
  }
})

组件还可以使用transitiontransition-mode两个属性来指定动态组件的过场动画,transition的使用和普通元素一致,trnasition-mode为可选项,不指定时默认是进入与离开的组件同时开始过渡,指定为in-outout-in则分别是先进后出或先出后进。唯一需要注意的是,动态组件不能是片段实例(没有唯一的父级元素),否则transition将不起作用

vue-router的<route-view></route-view>便是利用了动态组件的特性,所有用于动态组件的属性都可以用于route-view

12.5 杂项

12.5.1 组件和v-for

组件可以使用v-forv-for中的item数据应当也只能使用组件的props属性传递给组件

12.5.2 编写可复用组件

如果你的组件是需要复用的,则应当尽可能与其它组件解耦,定义好清晰的公开接口:props、事件、slot,使用组件时使用v-bindv-on的简写@:,并清晰的进行缩进

12.5.3 异步组件

可以让组件只在需要时从服务器下载,vue允许将组建定义为一个工厂函数,只在第一次需要渲染时触发工厂函数从服务器下载并缓存结果供多次渲染使用

Vue.component('async-component-example', (resolve, reject) => {
  setTimeout(() => { // 假装是在从服务器下载 O(∩_∩)O哈哈哈~
    resolve({ // 将下载好的组件传递出去
      template: '<div>I am async!</div>'
    })
    // 当然也可以调用reject()提示加载失败
  }, 1000)
})

这个功能可以与Webpack的代码分隔功能配合使用

Vue.component('async-webpack-example', function (resolve) {
  // 这个特殊的 require 语法告诉 webpack
  // 自动将编译后的代码分割成不同的块,
  // 这些块将通过 ajax 请求自动下载。
  require(['./my-async-component'], resolve)
})
12.5.4 命名约定

HTML中元素的属性名是不区分大小写的,但是当在定义组件挂载名称、props属性和事件名等等需要在HTML中使用的资源时,也可以使用驼峰命名(camelCase),只是使用的时候需要转成中划线命名(kebab-case)

12.5.5 递归调用

组件可以在自己的template中递归调用自己,但是需要注意,只有拥有name属性的组件才能递归调用自己(否则在自己的模板中没有挂载名可使用),并且要确保递归调用有终止条件

12.5.6 片段实例

当一个组件满足以下条件时,即为片段实例

  • 模板包含多个顶级元素。
  • 模板只包含普通文本。
  • 模板只包含其它组件(其它组件可能是一个片段实例)。
  • 模板只包含一个元素指令,如 <partial> 或 vue-router 的 <router-view>。
  • 模板根节点有一个流程控制指令,如 v-if 或 v-for。

片段实例虽然可以正确渲染,但是存在如下问题:

  • 非流程控制指令将被忽略
  • 非props特性将被忽略
  • transition属性将被忽略
    内联模板

如果组件有inline-template属性,则挂载组件时组件标签之间中内容将被作为组件的模板进行渲染,也就是说,你可以在调用组件时动态地定义组件的模板,这给了组件极大的灵活性。但缺点是模板编译结果不能被缓存,并且模板的作用域也不好理解了

13.深入响应式原理

13.1 追踪数据变化

在初始化vue实例时,vue会遍历实例的data选项并使用Object.defineProperty()来将data的各个属性转换为getter/setter(存取器属性,这也是vue不支持IE8及更早版本的原因)

使用vm.$log()方法可以将实例的data更为友好地打印到控制台

绑定到DOM中的每个数据或指令都有一个对应的watcher,它将数据和DOM中的绑定联系起来(把属性记录为依赖):每次数据变化时,它的setter都会通知watcher,watcher再通知DOM进行更新

13.2 检测数据变化的问题

实例化之后响应式属性的添加:

vue只能在初始化vue实例的时候将数据转换为响应式的,如果初始化之后再向data中添加属性,则该属性不会是响应的而是普通属性,所以vue提供了在实例创建后继续向data添加响应属性的方法:vm.$set()Vue.set()
vue实例可以使用$set()来继续添加响应属性

vm.$set(propName, value)

对于普通的数据对象,可以使用全局方法Vue.set()来添加响应属性

Vue.set(object, key, value)

另外,如果向data中已有的对象上添加新的属性,比如使用Object.assign()或_.extend()添加属性,新添加的属性也不会是动态的,这时可以创建一个包含该属性的新对象,替换原有的data中对象

13.3 事先声明所有属性(数据)

尽可能不要使用$set在实例化后再次添加新的属性,而是应该在vue实例选项中事先全部声明好,这会让代码更易于理解。

另外添加一个顶级响应属性会强制所有watcher重新计算,因为添加之前它不存在,也没有watcher追踪它,添加它以后之前的依赖需要重新计算,虽然这对比Angular1的脏检查性能还是可以接受的,但仍旧可以在初始化之前全部声明好来避免这部分性能开销

13.4 异步队列更新

vue内部使用一个队列来异步地更新DOM,如果一个watcher被多次触发,只会推入一次到队列中,只进行必要的DOM更新。异步队列中优先使用MutationObserver,如果不支持则使用setTimeout(fn, 0)

本次队列里的所有DOM更新操作,不会立即执行,而是在下次事件循环清空队列前更新。明白这一点,我们便可以合理地使用vue给出的Vue.nextTick(callback)方法,在更新完成DOM后做一些事情

该方法在vue实例上也有,并且更方便,因为回调的this自动指向了当前的vue实例对象

13.5 计算属性

计算属性不是简单的getter,计算属性持续追踪它的依赖,只要依赖变化了,计算属性就变化。

并且计算属性会缓存计算结果,除非依赖发生变化,否则读取计算属性时只从缓存读取(性能最优)

明白这一点,就能明白为什么计算属性的getter不是每次都会被调用:当你在计算属性的getter中new Date()时,并不是每次的值都是当前时间,它可能不会变化,还是过去的某个时间。如果不希望缓存,可以在设置计算属性时关闭缓存:

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

然而即使关闭缓存,也仅仅是在js中读取这个属性时是实时的,DOM中的数据绑定仍旧是依赖驱动的,只有计算属性的依赖发生了变化,DOM才会更新。

14.自定义指令

除了内置指令,还可以自定义指令。使用Vue.directive(id, definition)方法注册全局自定义指令
使用组件的directives选项注册局部自定义指令

14.1 钩子函数

  • bind:只调用一次,在指令第一次绑定到元素时调用
  • update:当绑定值变化时调用,参数为新值与旧值,第一次调用(bind之后会立即调用一次)时只有初始值,没有旧值
    *unbind:只调用一次,在指令从元素上解绑是调用
Vue.directive('my-directive', {
  bind: function () {
    // 准备工作
    // 例如,添加事件处理器或只需要运行一次的高耗任务
  },
  update: function (newValue, oldValue) {
    // 值更新时的工作
    // 也会以初始值为参数调用一次
  },
  unbind: function () {
    // 清理工作
    // 例如,删除 bind() 添加的事件监听器
  }
})

当注册以后,便可以在模板中像使用内置指令一样使用自定义指令:

<div v-my-directive="someValue"></div>

当只需要update函数时,可以只传入一个函数替代definition对象

14.2 指令实例属性

在钩子函数中,this指向当前的指令实例,指令实例上暴露了一些有用的属性:

  • el: 指令绑定的元素。
  • vm: 拥有该指令的上下文 ViewModel。
  • expression: 指令的表达式,不包括参数和过滤器,表达式可以是任意合法的JavaScript表达式
  • arg: 指令的参数。
  • name: 指令的名字,不包含前缀。
  • modifiers: 一个对象,包含指令的修饰符。
  • descriptor: 一个对象,包含指令的解析结果。

这些属性应当被当做只读的,不应该修改他们,即使你给指令对象添加自定义属性,也要注意不要覆盖这些内部属性

14.3 元素指令

元素指令可以看做是一个轻量的组件,它是以自定义元素的形式使用指令,而不是以特性的形式。使用Vue.elementDirective(id, discription)来注册自定义指令

Vue.elementDirective('my-directive', {
  // API 同普通指令
  bind: function () { // 注意使用简写函数时this的指向
    // 操作this.el
  }
})

使用时便如同自定义元素:

<my -directive></my-directive>

所以元素指令不接受参数或表达式,只能读取元素属性来进行操作

元素指令是终结性的,一旦Vue遇到一个元素指令它将跳过该院故事及其子元素,而将它们留给指令本身去操作

14.4 高级选项

14.4.1 params特性列表

自定义指令可以接收一个params数组,指定一个特性列表,Vue编译器将自动提取这些特性:

<div v-example a="hi"></div>
Vue.directive('example', {
  params: ['a'],
  bind: function () {
    console.log(this.params.a)
  }
})

params中的属性也支持动态属性:

<div v-example :a="msg"></div>
Vue.directive('example', {
  params: ['a'],
  paramWatchers: {
  a: function (val, oldVal) {
    console.log('a changed!')
  }
  }
})
14.4.2 deep深度检测

如果自定义属性的值为一个对象,当希望对象内部属性发生变化时触发update,则在定义指令时指定deep:true

<div v-my-directive="obj"></div>
Vue.directive('my-directive', {
  deep: true,
  update: function (obj) {
    // 在obj的内部属性变化时调用
  }
})
14.4.3 twoWay回写数据

默认情况下指令中是不能向vm写入数据的,但是可以使用twoWay选项开启,这样指令中就可以使用set()方法向vm回写数据,前提是指令绑定的是vm中的数据属性

Vue.directive('example', {
  towWay: true,
  bind: function () {
    this.handler = function () {
      // 将数据写回 vm
      // 如果指令这样绑定 v-example="a.b.c"
      // 它将用给定值设置 `vm.a.b.c`
      this.set(this.value)
    }.bind(this)
    this.el.addEventListener('input', this.handler)
  },
  unbind: function () {
    this.el.removeEventListener('input', this.handler)
  }
})
14.4.4 acceptStatement

acceptStatement:true可以让自定义指令接受内联js语句(比如v-on),但需要注意内联语句的副作用

Vue.directive('my-directive', {
  acceptStatement: true,
  update: function (fn) {
    // 传入值是一个函数
    // 在调用它时将在所属实例作用域内计算 "a++" 语句
  }
})

14.4.5 terminal

(1.0.19+)terminal: true指定由指令来接管元素的编译(比如v-ifv-for)。可能还需要Vue.FragmentFactory来编译partial。需要较好了解Vue的编译流程(2.X中编译流程略有变化),建议通读v-ifv-for源码来写terminal指令

14.4.6 优先级

普通指令默认1000,terminal指令默认2000。在API文档中查看指令优先级。流程控制指令使用用友最高优先级。

15. 自定义过滤器

15.1 注册单向过滤器

使用Vue.filter()来注册自定义过滤器,第一个参数为过滤器名称,第二个参数为过滤器函数:

Vue.filter('wrap', function (value, begin, end) { // 过滤器函数接受任意数量参数
  return begin + value + end
})
<!-- 'hello' => 'before hello after' -->
<span v-text="message | wrap 'before' 'after'"></span>

这个过滤器会将来自vm的数据处理后交给view显示。

15.2 注册双向过滤器

过滤器不仅可以处理从vm到view的数据,也可以处理从view到vm的数据。传递给Vue.filter()的第二个参数变为一个含有readwrite方法的对象:

Vue.filter('currencyDisplay', {
  // model -> view
  // 在更新 `<input>` 元素之前格式化值
  read: function(val) {
    return '$'+val.toFixed(2)
  },
  // view -> model
  // 在写回数据之前格式化值
  write: function(val, oldVal) {
    var number = +val.replace(/[^\\\\\\\\d.]/g, '')
    return isNaN(number) ? 0 : parseFloat(number.toFixed(2))
  }
})

注意:

  • 使用过滤器时的参数如果没有使用引号,则会在vm作用域内编译,使用引号则作为字符串传递给过滤器
  • 过滤器只推荐简单逻辑使用,复杂的逻辑应当使用计算属性
  • 过滤器函数中的this始终指向当前vm实例
  • 内置过滤器filterByorderBy根据指定的方法过滤/排序传入的数组

16. 混合

16.1 基本使用

定义一个选项对象,然后在实例化vue时使用mixins: [optionObj1, optionObj12]来将选项混合到当前的vue实例选项中:

// 定义一个混合对象
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// 定义一个组件,使用这个混合对象
var Component = Vue.extend({
  mixins: [myMixin]
})

var component = new Component() // -> "hello from mixin!"

默认的合并策略是:

  • 同名钩子函数被并入一个数组,依次执行,混合的钩子先于组件自己的钩子执行
  • 值为对象的选项将合并到同一个对象内,键名冲突则组件自己的选项优先
  • Vue.extend()同样使用上述策略进行合并

16.2 自定义选项

所谓自定义选项,是除开类似datamethods这样vue官方提供的选项,你希望使用的由你自己命名的选项。
通常情况下你在某个vue实例中使用了自定义选项,你可能对选项进行处理。但是如果你希望所有的vue实例在使用这个自定义选项时都做同样处理,这时你可以使用全局混合。

16.2.1 全局混合

可以使用Vue.mixin()来注册全局混合。所谓全局混合就是一旦注册,会应用到之后创建的所有vue实例上,所以需要小心使用。但这个特性特别适合用于为自定义选项注入处理逻辑,让它表现得像官方选项一样:

// 为 `myOption` 自定义选项注入一个处理器
Vue.mixin({
  created: function () {
    var myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})

new Vue({
  myOption: 'hello!'
})
// -> "hello!"

16.2.2 自定义选项合并策略

混合时对自定义选项的处理只是简单地覆盖已有值,如果想用自定义逻辑合并自定义选项,则向Vue.config.optionMergeStrategies添加一个函数:

Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
  // 返回 mergedVal
}

对于多数值为对象的选项,可以简单地使用 methods 所用的合并策略:

var strategies = Vue.config.optionMergeStrategies
strategies.myOption = strategies.methods

17.其它

插件、构建和对比其它框架三个主题略过,可去官方文档查看详细信息

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容