原文:http://www.cnblogs.com/xiaohuochai/p/7356084.html
Vue.js(读音 /vjuː/,类似于 view)
1.概述
- 渐进式的框架:
如果只使用Vue最基础的声明式渲染的功能,则完全可以把Vue当做一个模板引擎来使用
如果想以组件化开发方式进行开发,则可以进一步使用Vue里面的组件系统
如果要制作SPA(单页应用),则使用Vue里面的客户端路由功能
如果组件越来越多,需要共享一些数据,则可以使用Vue里的状态管理
如果想在团队里执行统一的开发流程或规范,则使用构建工具
- 暂无
2.组件
- 特点:组件可以扩展HTML元素,封装可重用的代码。每个页面,根据自己所需,使用不同的组件来拼接页面。
- 概述:在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例,组件是一个自定义元素或称为一个模块,包括所需的模板、逻辑和样式。在HTML模板中,组件以一个自定义标签的形式存在,起到占位符的功能。通过Vue.js的声明式渲染后,占位符将会被替换为实际的内容。
- 类型:组件注册包括全局注册和局部注册两种
// 1.创建一个组件构造器---全局 var myComponent = Vue.extend({ template: '<div>This is my first component!</div>' }) // 1.创建一个组件构造器--局部 var myComponent = Vue.extend({ template: '<div>This is my first component!</div>' }) // 2.注册组件,并指定组件的标签,组件的HTML标签为<my-component>--全局 Vue.component('my-component', myComponent) new Vue({ el: '#app' }); // 2.注册组件,并指定组件的标签,组件的HTML标签为<my-component>---局部 new Vue({ el: '#app', components: { // 2. 将myComponent组件注册到Vue实例下 'my-component': myComponent } }); //组件应该挂载到某个Vue实例下,否则它不会生效。 <div id="app2"> <my-component></my-component> </div>
注意:全局注册的组件可以用到不同的实例对象下,局部注册的组件只能应用到指定的实例对象下
- v-once:尽管在 Vue 中渲染 HTML 很快,不过当组件中包含大量静态内容时,可以考虑使用
v-once
将渲染结果缓存起来。Vue.component('my-component', { template: '<div v-once>hello world!...</div>' })
- 模板分离:在组件注册中,使用template选项中拼接HTML元素比较麻烦,这也导致了HTML和JS的高耦合性。庆幸的是,Vue.js提供了两种方式将定义在JS中的HTML模板分离出来。
//第一种利用script标签 //在script标签里使用 text/x-template 类型,并且指定一个 id--模板id <script type="text/x-template" id="hello-world-template"> <p>Hello hello hello</p> </script> Vue.component('hello-world', { template: '#hello-world-template' }) //上面的代码等价于 Vue.component('hello-world', { template: '<p>Hello hello hello</p>' }) //第二种利用template标签 //如果使用<template>标签,则不需要指定type属性 <div id="example"> <my-component></my-component> </div> <template id="hello-world-template"> <div>hello world!</div> </template> <script> // 注册 Vue.component('my-component', { template: '#hello-world-template' }) // 创建根实例 new Vue({ el: '#example' }) </script>
- 命名约定:对于组件的命名,W3C规范是字母小写且包含一个中划线(-),虽然Vue没有强制要求,但最好遵循规范.当注册组件时,使用中划线、小驼峰、大驼峰这三种任意一种都可以.
- 嵌套限制:并不是所有的元素都可以嵌套模板,因为要受到HTML元素嵌套规则的限制,尤其像
<ul>
,<ol>
,<table>
,<select>
限制了能被它包裹的元素,而一些像<option>
这样的元素只能出现在某些其它元素内部。如果必须使用的话,加入is属性<table id="example"> <tr is="my-row"></tr> </table> <script> // 注册 var header = { template: '<div class="hd">我是标题</div>' }; // 创建实例 new Vue({ el: '#example', components: { 'my-row': header } }) </script>
- 根元素:Vue强制要求每一个Vue实例(组件本质上就是一个Vue实例)需要有一个根元素,否则报错
// 注册 Vue.component('my-component', { template: ` <p>第一段</p> <p>第二段</p> `, }) //改为 Vue.component('my-component', { template: ` <div> <p>第一段</p> <p>第二段</p> </div> `, })
- data数据:一般地,我们在Vue实例对象或Vue组件对象中,我们通过data来传递数据,注意的是在组件中
data
必须是一个函数。<script> // 注册 var data = {counter: 0} Vue.component('my-component', { template: '<button v-on:click="counter += 1">{{ counter }}</button>', data:function(){ return data; } }) // 创建根实例 new Vue({ el: '#example' }) </script>
当一个组件被定义,
data
需要声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果data
仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象。通过提供data
函数,每次创建一个新实例后,能够调用data
函数,从而返回初始数据的一个全新副本数据对象因此,可以通过为每个组件返回全新的 data 对象来解决这个问题,各自的内部状态:
<script> // 注册 Vue.component('my-component', { template: '<button v-on:click="counter += 1">{{ counter }}</button>', data:function(){ return {counter: 0}; } }) // 创建根实例 new Vue({ el: '#example' }) </script>
- 原生事件:有时候,可能想在某个组件的根元素上监听一个原生事件。直接使用v-bind指令是不生效的,可以使用
.native
修饰v-on指令即可:
<div id="example"> <my-component @click="doTheThing"></my-component> <p>{{message}}</p> </div> <div id="example"> <my-component @click.native="doTheThing"></my-component> <p>{{message}}</p> </div>
3.实例对象
- 前言:一般地,当模板内容较简单时,使用data选项配合表达式即可。涉及到复杂逻辑时,则需要用到methods、computed、watch等方法。
- data:data是Vue实例的数据对象。Vue将会递归将data的属性转换为getter/setter,从而让data属性能响应数据变化。Vue实例创建之后,可以通过
vm.$data
访问原始数据对象。Vue实例也代理了data对象上所有的属性。也就是<script> var values = {message: 'Hello Vue!'} var vm = new Vue({ el: '#app', data: values }) console.log(vm.$data === values);//true console.log(vm.message);//'Hello Vue!' console.log(vm.$data.message);//'Hello Vue!' </script>
但是,以
_
或$
开头的属性不会被Vue实例代理,因为它们可能和Vue内置的属性或方法冲突。可以使用例如vm.$data._property
的方式访问这些属性<script> var values = { message: 'Hello Vue!', _name: '小火柴' } var vm = new Vue({ el: '#app', data: values }) console.log(vm._name);//undefined console.log(vm.$data._name);//'小火柴' </script>
- computed:计算属性函数computed将被混入到Vue实例中,内部返回值自动挂到实例对象data属性下。所有内部属性getter和setter的this上下文自动地绑定为Vue实例。
<div id="example"> <p>原始字符串: "{{ message }}"</p> <p>反向字符串: "{{ reversedMessage }}"</p> </div> <script> var vm = new Vue({ el: '#example', data: { message: '小火柴' }, computed: { reversedMessage: function () { return this.message.split('').reverse().join('') } } }) </script>
注意的是:vm.reversedMessage依赖于vm.message的值,vm.reversedMessage本身并不能被赋值。另外:计算属性默认只有 getter ,不过在需要时也可以提供一个 setter
<script> var vm = new Vue({ data: { a: 1 }, computed: { // 仅读取,值只须为函数 aDouble: function () { return this.a * 2 }, // 读取和设置 aPlus: { get: function () { return this.a + 1 }, set: function (v) { this.a = v - 1 } } } }) console.log(vm.aPlus);//2 vm.aPlus = 3 console.log(vm.a);//2 console.log(vm.aDouble);//4 </script>
- methods:通过调用表达式中的 methods 也可以达到同样的效果。
然而,不同的是计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。这就意味着只要
message
还没有发生改变,多次访问reversedMessage
计算属性会立即返回之前的计算结果,而不必再次执行函数相比而言,只要发生重新渲染,method 调用总会执行该函数。
<div id="example"> <p>计算属性: "{{ time1 }}"</p> <p>methods方法: "{{ time2() }}"</p> </div> <script> var vm = new Vue({ el: '#example', computed:{ time1: function () { return (new Date()).toLocaleTimeString() } }, methods: { time2: function () { return (new Date()).toLocaleTimeString() } } }) </script>
所以,假设有一个性能开销比较大的的计算属性A,它需要遍历一个极大的数组和做大量的计算。可能有其他的计算属性依赖于 A 。如果没有缓存,将不可避免的多次执行A的getter!如果不希望有缓存,则用 method 替代
- watch:Vue提供了一种通用的方式来观察和响应Vue实例上的数据变动:watch属性。watch属性是一个对象,键是需要观察的表达式,值是对应回调函数,回调函数得到的参数为新值和旧值。值也可以是方法名,或者包含选项的对象。Vue实例将会在实例化时调用
$watch()
,遍历watch对象的每一个属性View Code补充:除了使用数据选项中的watch方法以外,还可以使用实例对象的$watch方法, 该方法的返回值是一个取消观察函数,用来停止触发回调
View Code
4.模板内容
- 概述:Vue.js使用了基于HTML的模板语法,允许声明式地将DOM绑定至底层Vue实例的数据。所有Vue.js的模板都是合法的HTML ,所以能被遵循规范的浏览器和HTML解析器解析.在底层的实现上, Vue将模板编译成虚拟DOM渲染函数。结合响应系统,在应用状态改变时, Vue能够智能地计算出重新渲染组件的最小代价并应用到DOM操作上.一般地,模板内容包括文本内容和元素特性.
- 文本渲染:v-text,v-html
//1.文本插值 //文本渲染最常见的形式是使用双大括号语法来进行文本插值,下面的message相当于一个变量或占位符,最终会表示为真正的文本内容 <div id="app"> {{ message }} </div> <script> new Vue({ el: '#app', data:{ 'message': '<span>测试内容</span>' } }) </script> //2.表达式插值 {{ number + 1 }} {{ ok ? 'YES' : 'NO' }} {{ message.split('').reverse().join('') }} //上面这些表达式会在所属Vue实例的数据作用域下作为JS被解析。有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效 //模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如Math和Date。不应该在模板表达式中试图访问用户定义的全局变量 //3.v-text 实现插值类似效果的另一种写法是使用v-text指令,该指令用于更新元素的innerText。如果要更新部分的innerText,需要使用模板插值 //v-text优先级高于模板插值的优先级 <div id="app" v-text="message"> </div> <script> new Vue({ el: '#app', data:{ message:"This is a <i>simple</i> document" } }) </script> //4.v-htm //如果要输出真正的 HTML ,需要使用 v-html 指令,该指令用于更新元素的 innerHTML //在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击。只在可信内容上使用 v-html,而不用在用户提交的内容上 <div id="app" v-html="message"> </div> <script> new Vue({ el: '#app', data:{ message:"This is a <i>simple</i> document" } }) </script>
- 静态插值:上面介绍了模板插值,一般地,模板插值是动态插值。即无论何时,绑定的数据对象上的占位符内容发生了改变,插值处的内容都会更新。v-once
//v-once // 如果要实现静态插值,即执行一次性插值,数据改变时,插值处内容不会更新,这时需要用到v-once指令 <div id="app" v-once>{{ message }}</div> <script> var vm = new Vue({ el: '#app', data:{ 'message': '测试内容' } }) </script>
- 不渲染:v-pre,如果要跳过这个元素和它的子元素的编译过程,只用来显示原始大括号及标识符,则可以使用v-pre指令。这样,可以减少编译时间
<div id="example" v-pre>{{message}}</div> <script> var vm = new Vue({ el: '#example', data:{ //如果使用v-pre指令,则不会被表示为match message:'match' }, }) </script>
- 隐藏未编译:一般地,使用模板差值时,页面上会显示大括号及占位符。编译完成后,再转换为真正的值。如果在网络条件不好的情况下,这种现象更加明显。v-cloak,这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如
[v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。<style> [v-cloak]{display:none;} </style> <div id="example" v-cloak>{{message}}</div> <script src="https://unpkg.com/vue"></script> <script> var vm = new Vue({ el: '#example', data:{ message:'match' }, }) </script>
- 特性渲染:HTML共有16个全局属性(或称为特性),Vue.js支持对特性的内容进行动态渲染。特性渲染时不能使用双大括号语法。
<div id="app" title={{my-title}}></div> <script> var vm = new Vue({ el: '#app', data:{ 'my-title': '测试内容' } }) </script>
v-bind:通过v-bind指令可以动态地绑定一个或多个特性
<div id="app" v-bind:title="message"></div> // 由于v-bind指令非常常用,可缩写如下 <div id="app" :title="message"></div> <script> new Vue({ el: '#app', data:{ message:"我是小火柴" } }) </script>
对布尔值的属性也有效——如果条件被求值为false,该属性会被移除
<button id="app" :disabled="isButtonDisabled">按钮</button> <script> var vm = new Vue({ el: '#app', data:{ 'isButtonDisabled': true } }) </script>
- class绑定:在
v-bind
用于class
和style
时, Vue.js 专门增强了它。表达式的结果类型除了字符串之外,还可以是对象或数组。绑定class包括对象语法、数组语法和组件绑定.对象语法// 可以传给 v-bind:class 一个对象,以动态地切换 class <div v-bind:class="{ active: isActive }"></div> //上面的语法表示 class active 的更新将取决于数据属性 isActive 是否为真值 //可以在对象中传入更多属性来动态切换多个class。v-bind:class指令可以与普通的class属性共存 <div id="app" class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }"> </div> <script> var app = new Vue({ el: '#app', data:{ isActive:true, hasError:false } }) </script> //也可以直接绑定数据里的一个对象 <div id="app" :class="classObject"></div> <script> var app = new Vue({ el: '#app', data:{ classObject: { active: true, 'text-danger': false } } }) </script> //也可以在这里绑定返回对象的计算属性。这是一个常用且强大的模式 <div id="app" :class="classObject"></div> <script> var app = new Vue({ el: '#app', data: { isActive: true, error: null }, computed: { classObject: function () { return { active: this.isActive && !this.error, 'text-danger': this.error && this.error.type === 'fatal', } } } }) </script>
数组语法:可以把一个数组传给
v-bind:class
,以应用一个 class 列表<div id="app" :class="[activeClass, errorClass]"></div> <script> var app = new Vue({ el: '#app', data: { activeClass: 'active', errorClass: 'text-danger' } }) </script>
//如果要根据条件切换列表中的 class ,可以用三元表达式 <div id="app" :class="[isActive ? activeClass : '', errorClass]"></div> //不过,当有多个条件 class 时这样写有些繁琐。可以在数组语法中使用对象语法 <div id="app" :class="[{ active: isActive }, errorClass]"></div>
组件绑定:在一个定制组件上用到
class
属性时,这些类将被添加到根元素上面,这个元素上已经存在的类不会被覆盖<div id="app" class="test"> <my-component class="baz boo"></my-component> </div> <script> Vue.component('my-component', { template: '<p class="foo bar">Hi</p>' }) var app = new Vue({ el: '#app' }) </script>
同样的适用于绑定 HTML class
<div id="app" class="test"> <my-component :class="{ active: isActive }"></my-component> </div> <script> Vue.component('my-component', { template: '<p class="foo bar">Hi</p>' }) var app = new Vue({ el: '#app', data:{ isActive:true } }) </script>
- style绑定:对象语法:
v-bind:style
的对象语法十分直观——看着非常像 CSS ,其实它是一个JS对象。 CSS属性名可以用驼峰式 (camelCase)或(配合引号的)短横分隔命名 (kebab-case)<div id="app" :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> <script> var app = new Vue({ el: '#app', data: { activeColor: 'red', fontSize: 30 } }) </script>
直接绑定到一个样式对象通常更好,让模板更清晰
<div id="app" :style="styleObject"></div> <script> var app = new Vue({ el: '#app', data: { styleObject: { color: 'red', fontSize: '13px' } } }) </script>
数组语法:
v-bind:style
的数组语法可以将多个样式对象应用到一个元素上<div id="app" :style="[baseStyles, overridingStyles]"></div> <script> var app = new Vue({ el: '#app', data: { baseStyles: { color: 'red', fontSize: '13px' }, overridingStyles:{ height:'100px', '100px' } } }) </script>
前缀:当
v-bind:style
使用需要特定前缀的CSS属性时,如transform
,Vue.js会自动侦测并添加相应的前缀,可以为style
绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }">
这会渲染数组中最后一个被浏览器支持的值。在这个例子中,如果浏览器支持不带浏览器前缀的 flexbox,那么渲染结果会是
display: flex
- 过滤器:Vue.js允许自定义过滤器,可被用作一些常见的文本格式化。过滤器可以用在两个地方:模板插值和
v-bind
表达式。过滤器应该被添加在JS表达式的尾部,由“管道”符指示,过滤器设计目的是用于文本转换。为了在其他指令中实现更复杂的数据变换,应该使用计算属性,两种形式。//一种是使用Vue.filter()方法 // 注册 Vue.filter('my-filter', function (value) { // 返回处理后的值 }) // getter,返回已注册的过滤器 var myFilter = Vue.filter('my-filter')
//另一种是在Vue构造函数或组件中使用filters参数 var app = new Vue({ el: '#app', filters: { 'my-filter': function (value) { // } } })
过滤器函数总接受表达式的值 (之前的操作链的结果) 作为第一个参数。在这个例子中,
capitalize
过滤器函数将会收到message
的值作为第一个参数<div id="app"> {{ message}} {{ message | capitalize }} </div> <script> var app = new Vue({ el: '#app', data:{ message: '小火柴' }, filters: { capitalize: function (value) { if (!value) return '' value = value.toString() return value.split('').reverse().join('') } } }) </script>
过滤器可以串联,,
filterA
拥有单个参数,它会接收message
的值,然后调用filterB
,且filterA
的处理结果将会作为filterB
的单个参数传递进来<div id="app"> {{ message}} {{ message | filterA | filterB }} </div> <script> var app = new Vue({ el: '#app', data:{ message: '小火柴' }, filters: { filterA: function (value) { return value.split('').reverse().join('') }, filterB: function(value){ return value.length } } }) </script>
过滤器是JS函数,因此可以接受参数,
filterA
是个拥有三个参数的函数。message
的值将会作为第一个参数传入。字符串'arg1'
将作为第二个参数传给filterA
,表达式arg2
的值将作为第三个参数<div id="app"> {{ message}} {{ message | filterA('arg1', arg) }} </div> <script> var app = new Vue({ el: '#app', data:{ message: '小火柴', arg: 'abc' }, filters: { filterA: function (value,arg1,arg2) { return value + arg1 + arg2 } } }) </script>
下面是过滤器在v-bind表达式中使用的一个例子:
<div id="app" :class="raw | format"></div> <script> var app = new Vue({ el: '#app', data:{ raw: 'active' }, filters: { format: function (value) { return value.split('').reverse().join('') } } }) </script>
5.模板逻辑
- 条件渲染:在Vue中,实现条件逻辑依靠条件指令,包括v-if、v-else、v-else-if这三个。
//如果"seen"的值为true,则"#app"元素显示,否则将从DOM中移除 <div id="app" v-if="seen"> {{ message }} </div> <script> var app = new Vue({ el: '#app', data: { message: 'Hello Vue!', seen:true } }) </script>
如果想切换多个元素,可以把一个
<template>
元素当做包装元素,并在上面使用v-if
。最终的渲染结果不会包含<template>
元素<div id="app"> <template v-if="ok"> <h1>Title</h1> <p>Paragraph 1</p> <p>Paragraph 2</p> </template> </div> <script> var app = new Vue({ el: '#app', data:{ ok:true } }) </script>
//当type='A'时,显示内容为A的div;当type='B'时,显示内容为B的div;当type='C'时,显示内容为C的div;否则,显示内容为D的div <div v-if="type === 'A'">A</div> <div v-else-if="type === 'B'">B</div> <div v-else-if="type === 'C'">C</div> <div v-else>D</div> <script> var app = new Vue({ el: "#app", data: { type:'A' } }) </script>
- 元素不复用:Vue会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做,除了使 Vue 变得非常快之外,还有一些有用的好处。例如,如果允许用户在不同的登录方式之间切换。
<div id="app"> <template v-if="loginType === 'username'"> <label>Username</label> <input placeholder="输入用户名"> </template> <template v-else> <label>Email</label> <input placeholder="输入邮箱地址"> </template> <div> <button @click="toggle">切换登录方式</button> </div> </div> <script> var app = new Vue({ el: '#app', data:{ loginType:'username' }, methods:{ toggle(){ if(this.loginType === 'username'){ this.loginType = ''; }else{ this.loginType = 'username'; } } } }) </script>
key属性:这样也不总是符合实际需求,所以Vue提供了一种方式来声明“这两个元素是完全独立的——不要复用它们”。只需添加一个具有唯一值的
key
属性即可<div id="app"> <template v-if="loginType === 'username'"> <label>Username</label> <input placeholder="输入用户名" key="username-input"> </template> <template v-else> <label>Email</label> <input placeholder="输入邮箱地址" key="email-input"> </template> <div> <button @click="toggle">切换登录方式</button> </div> </div>
- 元素显隐:v-show,根据表达式的真假值,切换元素的
display
属性。当v-show被赋值为true时,元素显示;否则,元素被隐藏,v-show和v-if指令都有元素显隐的功能,但其原理并不相同。v-if的元素显隐会将元素从DOM删除或插入;而v-show则只是改变该元素的display是否为none,[注意]v-show
不支持<template>
语法,也不支持v-else
<div id="app"> <div v-if="num > 0">if</div> <div v-show="num > 0">show</div> </div> <script> var app = new Vue({ el: "#app", data: { num: 1 } }) </script> //上面代码中,如果num>0,则内容为if和内容为show的div都显示;否则都不显示
一般来说,
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show
较好;如果在运行时条件不太可能改变,则使用v-if
较好 - 循环渲染:v-for,
v-for
指令需要以item in items
形式的特殊语法,items
是源数据数组并且item
是数组元素迭代的别名<ul id="example-1"> <li v-for="item in items"> {{ item.message }} </li> </ul> <script> var example1 = new Vue({ el: '#example-1', data: { items: [ {message: 'Foo' }, {message: 'Bar' } ] } }) </script>
在
v-for
块中,拥有对父作用域属性的完全访问权限。v-for
还支持一个可选的第二个参数为当前项的索引<ul id="example-2"> <li v-for="(item, index) in items"> {{ parentMessage }} - {{ index }} - {{ item.message }} </li> </ul> <script> var example2 = new Vue({ el: '#example-2', data: { parentMessage: 'Parent', items: [ { message: 'Foo' }, { message: 'Bar' } ] } }) </script>
也可以用
of
替代in
作为分隔符,它是最接近JS迭代器的语法<ul id="example-2"> <li v-for="(item, index) of items"> {{ parentMessage }} - {{ index }} - {{ item.message }} </li> </ul>
和v-if
模板一样,也可以用带有v-for
的<template>
标签来渲染多个元素块<ul id="example-2"> <template v-for="item in items"> <li>{{ item.message }}</li> <li>abc</li> </template> </ul> <script> var example2 = new Vue({ el: '#example-2', data: { items: [ { message: 'Foo' }, { message: 'Bar' } ] } }) </script>
- 对象迭代:可以用
v-for
通过一个对象的属性来迭代,第二个参数为键名,第三个参数为索引<ul id="repeat-object" class="demo"> <li v-for="(value, key, index) in object"> {{ index }}. {{ key }} : {{ value }} </li> </ul> <script> new Vue({ el: '#repeat-object', data: { object: { firstName: 'John', lastName: 'Doe', age: 30 } } }) </script>
- 整数迭代:
v-for
也可以取整数。在这种情况下,它将重复多次模板,[注意]整数迭代是从1开始,而不是从0开始的<div id="example"> <span v-for="n in 10">{{ n }} </span> </div> <script> var example = new Vue({ el: '#example' }) </script>
- 组件:在自定义组件里,可以像任何普通元素一样用
然而不能自动传递数据到组件里,因为组件有自己独立的作用域。为了传递迭代数据到组件里,要用v-for,[注意]2.2.0+ 的版本里,当在组件中使用
v-for
时,key
现在是必须的,props,
不自动注入item
到组件里的原因是,因为这使得组件会紧密耦合到v-for
如何运作。在一些情况下,明确数据的来源可以使组件可重用。<div id="example"> <my-component v-for="(item,index) in items" :msg="item.message" :index="index" :key="item.id"></my-component> </div> <script> // 注册 Vue.component('my-component', { template: '<div>{{index}}.{{msg}}</div>', props:['index','msg'] }) // 创建根实例 new Vue({ el: '#example', data(){ return { items: [ {id:1, message: 'Foo' }, {id:2, message: 'Bar' }, {id:3, message: 'Baz' }, ] } } }) </script>
- v-for 与 v-if:当它们处于同一节点,
v-for
的优先级比v-if
更高,这意味着v-if
将分别重复运行于每个v-for
循环中。当想为仅有的一些项渲染节点时,这种优先级的机制会十分有用<ul id="example"> <li v-for="item in items" v-if="item.isShow"> {{ item.message }} </li> </ul>
<script> var example = new Vue({ el: '#example', data: { items: [ {isShow: true,message: 'Foo' }, {isShow: false,message: 'Bar' }, {isShow: true,message: 'Baz' } ] } }) </script>
如果要有条件地跳过循环的执行,那么将
v-if
置于包装元素 (或<template>
)上,<ul id="example" v-if="isShow"> <li v-for="(item,index) in items" > {{ item.message }} </li> </ul> <script> var example = new Vue({ el: '#example', data: { isShow:true, items: [ {message: 'Foo' }, {message: 'Bar' }, {message: 'Baz' } ] } }) </script>
- 关于key:为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,需要为每项提供一个唯一
key
属性。理想的key
值是每项都有唯一id。它的工作方式类似于一个属性,所以需要用v-bind
来绑定动态值.<div v-for="item in items" :key="item.id"> <!-- 内容 --> </div>
建议尽可能使用
v-for
来提供key
,除非迭代DOM内容足够简单,或者要依赖于默认行为来获得性能提升。key是Vue识别节点的一个通用机制,key
并不特别与v-for
关联
6.VUE数组更新及过滤排序
- 变异方法:它们将会触发视图更新
push() 接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度 pop() 从数组末尾移除最后一项,减少数组的length值,然后返回移除的项 shift() 移除数组中的第一个项并返回该项,同时数组的长度减1 unshift() 在数组前端添加任意个项并返回新数组长度 splice() 删除原数组的一部分成员,并可以在被删除的位置添加入新的数组成员 sort() 调用每个数组项的toString()方法,然后比较得到的字符串排序,返回经过排序之后的数组 reverse() 用于反转数组的顺序,返回经过排序之后的数组
<div id="example"> <div> <button @click='push'>push</button> <button @click='pop'>pop</button> <button @click='shift'>shift</button> <button @click='unshift'>unshift</button> <button @click='splice'>splice</button> <button @click='sort'>sort</button> <button @click='reverse'>reverse</button> </div> <ul> <li v-for="item in items" > {{ item.message }} </li> </ul> </div>
<script> var example = new Vue({ el: '#example', data: { items: [ {message: 'Foo' }, {message: 'Bar' }, {message: 'Baz' } ], addValue:{message:'match'} }, methods:{ push(){ this.items.push(this.addValue) }, pop(){ this.items.pop() }, shift(){ this.items.shift() }, unshift(){ this.items.unshift(this.addValue) }, splice(){ this.items.splice(0,1) }, sort(){ this.items.sort() }, reverse(){ this.items.reverse() }, } }) </script>
- 非变异方法:变异方法(mutation method),顾名思义,会改变被这些方法调用的原始数组。相比之下,也有非变异(non-mutating method)方法,例如:
filter()
,concat()
,slice()
。这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组concat() 先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组 slice() 基于当前数组中一个或多个项创建一个新数组,接受一个或两个参数,即要返回项的起始和结束位置,最后返回新数组 map() 对数组的每一项运行给定函数,返回每次函数调用的结果组成的数组 filter() 对数组中的每一项运行给定函数,该函数会返回true的项组成的数组
<div id="example"> <div> <button @click='concat'>concat</button> <button @click='slice'>slice</button> <button @click='map'>map</button> <button @click='filter'>filter</button> </div> <ul> <li v-for="item in items" > {{ item }} </li> </ul> </div>
<script> var example = new Vue({ el: '#example', data: { items: ['Foo','Bar','Baz'], addValue:'match' }, methods:{ concat(){ this.items = this.items.concat(this.addValue) }, slice(){ this.items = this.items.slice(1) }, map(){ this.items = this.items.map(function(item,index,arr){ return index + item; }) }, filter(){ this.items = this.items.filter(function(item,index,arr){ return (index > 0); }) } } }) </script>
- 限制:由于JS的限制, Vue 不能检测以下变动的数组: 利用索引直接设置一个项时,例如:
vm.items[indexOfItem] = newValue,修改数组的长度时,例如:
vm.items.length = newLength
<div id="example"> <div> <button @click='setVal'>setVal</button> <button @click='setLength'>setLength</button> <button @click='pop'>pop</button> </div> <ul> <li v-for="item in items" >{{ item }}</li> </ul> <p>{{ message }}</p> </div>
<script> var watchFunc = function(){ example.message = '数据发生变化'; setTimeout(function(){ example.message = ''; },500); } var example = new Vue({ el: '#example', data: { items: ['Foo','Bar','Baz'], message:'', }, watch:{ items:watchFunc }, methods:{ pop(){ this.items.pop() }, setVal(){ this.items[0]= 'match'; }, setLength(){ this.items.length = 2; } } }) </script>
-
过滤排序:有时,要显示一个数组的过滤或排序副本,而不实际改变或重置原始数据。在这种情况下,可以创建返回过滤或排序数组的计算属性;computed:
<div id="example"> <ul> <li v-for="n in evenNumbers">{{ n }}</li> </ul> </div> <script> var example = new Vue({ el: '#example', data: { numbers: [ 1, 2, 3, 4, 5 ], }, computed: { evenNumbers: function () { return this.numbers.filter(function (number) { return number % 2 === 0 }) } } }) </script>
在计算属性不适用的情况下 (例如,在嵌套
v-for
循环中,会有死循环) 可以使用一个 method 方法<div id="example"> <ul> <li v-for="n in even(numbers)">{{ n }}</li> </ul> </div> <script> var example = new Vue({ el: '#example', data: { numbers: [ 1, 2, 3, 4, 5 ], }, methods: { even: function (numbers) { return numbers.filter(function (number) { return number % 2 === 0 }) } } }) </script>
7.vue事件处理
- 前言:Vue.js事件处理方法和表达式都严格绑定在当前视图的ViewModel上,好处是:
1、扫一眼HTML模板便能轻松定位在JS代码里对应的方法 2、无须在JS里手动绑定事件,ViewModel代码可以是非常纯粹的逻辑,和DOM完全解耦,更易于测试 3、当一个ViewModel被销毁时,所有的事件处理器都会自动被删除。无须担心如何自己清理它们
- 事件监听:通过
v-on
指令来绑定事件监听器<div id="example"> <button v-on:click="counter += 1">增加 1</button> <p>这个按钮被点击了 {{ counter }} 次。</p> </div> <script> var example = new Vue({ el: '#example', data: { counter: 0 } }) </script>
v-on
指令可以接收一个定义的方法来调用,但是需要注意的是不应该使用箭头函数来定义methods函数,因为箭头函数绑定了父级作用域的上下文,所以this将不会按照期望指向 Vue 实例。<div id="example"> <button v-on:click="num">测试按钮</button> <p>{{message}}</p> </div> <script> var example = new Vue({ el: '#example', data:{ counter:0, message:'' }, methods: { num: function (event) { if (event) { this.message = event.target.innerHTML + '被按下' + ++this.counter + '次'; } } } }) </script>
- 内链语句:除了直接绑定到一个方法,也可以用内联JS语句
<div id="example"> <button v-on:click="say('hi')">Say hi</button> <button v-on:click="say('what')">Say what</button> <p>{{message}}</p> </div> <script> var example = new Vue({ el: '#example', data:{ message:'' }, methods: { say: function (message) {this.message = message;} } }) </script>
有时也需要在内联语句处理器中访问原生 DOM 事件。可以用特殊变量
$event
把它传入方法<div id="example"> <button v-on:click="say('hi',$event)">Say hi</button> <button v-on:click="say('what',$event)">Say what</button> <p>{{message}}</p> </div> <script> var example = new Vue({ el: '#example', data:{ message:'' }, methods: { say: function (message,event) { if(event){ event.preventDefault(); } this.message = message; } } }) </script>
- 事件修饰符:在事件处理程序中调用
event.preventDefault()
或event.stopPropagation()
是非常常见的需求。尽管可以在methods中轻松实现这点,但更好的方式:methods只有纯粹的数据逻辑,而不是去处理 DOM 事件细节, 为了解决这个问题, Vue.js 为v-on
提供了事件修饰符。通过由点(.)表示的指令后缀来调用修饰符。.stop 阻止冒泡 .prevent 阻止默认事件 .capture 使用事件捕获模式 .self 只在当前元素本身触发 .once 只触发一次
<!-- 阻止单击事件冒泡 --> <a v-on:click.stop="doThis"></a> <!-- 提交事件不再重载页面 --> <form v-on:submit.prevent="onSubmit"></form> <!-- 修饰符可以串联 --> <a v-on:click.stop.prevent="doThat"></a> <!-- 只有修饰符 --> <form v-on:submit.prevent></form> <!-- 添加事件侦听器时使用事件捕获模式 --> <div v-on:click.capture="doThis">...</div> <!-- 只当事件在该元素本身(比如不是子元素)触发时触发回调 --> <div v-on:click.self="doThat">...</div> <!-- 点击事件将只会触发一次 --> <a v-on:click.once="doThis"></a> - 修饰符举例:
//stop:阻止冒泡(点击子级是否触发父级点击事件) <div id="example" @click="setVal1" style="border:1px solid black;300px;"> <button @click="setVal">普通按钮</button> <button @click.stop="setVal">阻止冒泡</button> <button @click="reset">还原</button> <div>{{result}}</div> </div> <script>var example = new Vue({ el: '#example', data:{ result:'' }, methods:{ setVal(event){ this.result+=' 子级 '; }, setVal1(){ this.result+=' 父级 '; }, reset(){ history.go() } } }) </script>
// prevent:取消默认事件 <div id="example"> <a href="http://cnblogs.com" target="_blank">普通链接</a> <a @click.prevent href="http://cnblogs.com" target="_blank">取消默认行为</a> </div> <script> var example = new Vue({ el: '#example' }) </script>
//capture:事件捕获模式 <div id="example" @click.capture="setVal1" style="border:1px solid black;300px;"> <button @click.capture="setVal">事件捕获</button> <button @click="reset">还原</button> <div>{{result}}</div> </div> <script>var example = new Vue({ el: '#example', data:{ result:'' }, methods:{ setVal(event){ this.result+=' 子级 '; }, setVal1(){ this.result+=' 父级 '; }, reset(){ history.go() } } }) </script>
//self:捕获? <div id="example"> <div @click="setVal" :style="styleObj1"> <div :style="styleObj2">普通</div> <button @click="reset">还原</button> </div> <div @click.self="setVal" :style="styleObj1"> <div :style="styleObj2">self</div> <button @click="reset">还原</button> </div> </div> <script> var styleObj1 = { display:'inline-block', height:'60px', '120px', 'background-color': 'lightblue' }; var styleObj2 = { display:'inline-block', height:'30px', '60px', 'background-color': 'lightgreen' }; var example = new Vue({ el: '#example', data:{ styleObj1:styleObj1, styleObj2:styleObj2 }, methods:{ setVal(event){ event.target.style.outline="solid" }, reset(){ history.go() } } }) </script>
//once只触发一次 <div id="example"> <button @click="setVal">普通按钮</button> <button @click.once="setVal">触发一次</button> <button @click="reset">还原</button> <div>{{result}}</div> </div> <script> var example = new Vue({ el: '#example', data:{ result:'' }, methods:{ setVal(event){ this.result+=' 内容 '; }, reset(){ history.go() } } }) </script>
-
鼠标修饰符:这些修饰符会限制处理程序监听特定的滑鼠按键
.left 左键 .right 右键 .middle 滚轮
<div id="example"> <button @mouseup.right="right" @mouseup.middle="middle" @mouseup.left="left">{{message}}</button> </div> <script> var example = new Vue({ el: '#example', data:{ message:'分别用左、中、右键进行点击,会出现不同的效果' }, methods:{ left(){ this.message = 'left' }, right(){ this.message = 'right' }, middle(){ this.message = 'middle' }, } }) </script>
-
键值修饰符:在监听键盘事件时,经常需要监测常见的键值。 Vue 允许为
v-on
在监听键盘事件时添加关键修饰符<!-- 只有在 keyCode 是 13 时调用 vm.submit() --> <input v-on:keyup.13="submit">
.enter 回车 .tab 制表键 .delete (捕获 “删除” 和 “退格” 键) .esc 返回 .space 空格 .up 上 .down 下 .left 左 .right 右
<div id="example"> <button @keyup.enter="enter" @keyup.tab="tab" @keyup.delete="delete1" @keyup.esc="esc" @keyup.space="space" @keyup.up="up" @keyup.down="down" @keyup.left="left" @keyup.right="right">{{message}}</button> </div> <script> var example = new Vue({ el: '#example', data:{ message:'将光标置于按钮上后,按下键盘上不同的按键,会有不同的效果' }, methods:{ enter(){ this.message = 'enter' }, tab(){ this.message = 'tab' }, delete1(){ this.message = 'delete' }, esc(){ this.message = 'esc' }, space(){ this.message = 'space' }, up(){ this.message = 'up' }, down(){ this.message = 'down' }, left(){ this.message = 'left' }, right(){ this.message = 'right' }, } }) </script>
可以通过全局
config.keyCodes
对象自定义键值修饰符别名// 可以使用 v-on:keyup.a Vue.config.keyCodes.a = 65
-
修饰键:
.ctrl .alt .shift .meta
8.vue表单控件绑定
- 基础语法:可以用
v-model
指令在表单控件元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。v-model
本质上不过是语法糖,它负责监听用户的输入事件以更新数据。实际上v-model是:value和@input事件的语法糖 [注意]v-model
会忽略所有表单元素的value
、checked
、selected
特性的初始值。因为它会选择Vue实例数据来作为具体的值。应该通过JS组件的data
选项中声明初始值。<div id="example"> <input v-model="message" placeholder="edit me"> <p>Message is: {{ message }}</p> </div> <script> var example = new Vue({ el: '#example', data:{ message:'' } }) </script>
- 在文本区域插值(
<textarea></textarea>
) 并不会生效,应用v-model
来代替 -
<div id="example"> <div> <span>Multiline message is:</span> <p style="white-space: pre-line">{{ message }}</p> </div> <textarea v-model="message" placeholder="add multiple lines"></textarea> </div> <script> var example = new Vue({ el: '#example', data:{ message:'' } }) </script>
type:checkbox
<div id="example"> <input type="checkbox" id="checkbox" v-model="checked"> <label for="checkbox">{{ checked }}</label> </div> <script> var example = new Vue({ el: '#example', data:{ checked:false } }) </script>
<div id="example"> <div> <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> </div> <div> <span>Checked names: {{ checkedNames }}</span> </div> </div> <script> var example = new Vue({ el: '#example', data:{ checkedNames:[] } }) </script>
-
type:radio
<div id="example"> <div> <input type="radio" id="one" value="One" v-model="picked"> <label for="one">One</label> </div> <div> <input type="radio" id="two" value="Two" v-model="picked"> <label for="two">Two</label> </div> <div>Picked: {{ picked }}</div> </div> <script> var example = new Vue({ el: '#example', data:{ picked:'' } }) </script>
-
select单选列表
<div id="example"> <select v-model="selected"> <option disabled value="">请选择</option> <option>A</option> <option>B</option> <option>C</option> </select> <span>Selected: {{ selected }}</span> </div> <script> var example = new Vue({ el: '#example', data:{ selected: '' } }) </script>
[注意]如果
v-model
表达初始的值不匹配任何的选项,<select>
元素就会以”未选中”的状态渲染。在iOS中,这会使用户无法选择第一个选项,因为这样的情况下,iOS不会引发change事件。因此,像以上提供disabled选项是建议的做法。动态选项,用v-for渲染数据选项
//用v-for渲染 <div id="example"> <select v-model="selected"> <option v-for="option in options" :value="option.value"> {{ option.text }} </option> </select> <span>Selected: {{ selected }}</span> </div> <script> var example = new Vue({ el: '#example', data:{ selected: 'A', options: [ { text: 'One', value: 'A' }, { text: 'Two', value: 'B' }, { text: 'Three', value: 'C' } ] } }) </script>
-
绑定value:通常v-modal帮定的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
实现,并且这个属性的值可以不是字符串。//单选按钮 <div id="example"> <input type="radio" v-model="pick" :value="a"> <span>{{ pick }}</span> </div> <script> var example = new Vue({ el: '#example', data:{ pick:'', a:true } }) </script>
//选择列表 <div id="example"> <select v-model="selected"> <option :value="{ number: 123 }">123</option> <option :value="{ number: 234 }">234</option> <option :value="{ number: 345 }">345</option> </select> <span>Selected: {{ selected.number }}</span> </div> <script> var example = new Vue({ el: '#example', data:{ selected:'' } }) </script>
//复选框 <div id="example"> <input type="checkbox" v-model="toggle" :true-value="a" :false-value="b"> <span>{{ toggle }}</span> </div> <script> var example = new Vue({ el: '#example', data:{ toggle:'', a:true, b:false } }) </script>
- 修饰符:【.lazy】:在默认情况下,
v-model
在input
事件中同步输入框的值与数据,但可以添加一个修饰符lazy
,从而转变为在change
事件中同步。下列例子中,光标移出输入框时,才同步数据<div id="example"> <input v-model.lazy="message" placeholder="edit me"> <p>Message is: {{ message }}</p> </div> <script> var example = new Vue({ el: '#example', data:{ message:'' } }) </script>
【.number】:如果想自动将用户的输入值转为Number类型(如果原值的转换结果为 NaN 则返回原值),可以添加一个修饰符
number
给v-model
来处理输入值,这通常很有用,因为在type="number"
时 HTML 中输入的值也总是会返回字符串类型<div id="example"> <div> <input v-model="age1" type="number"> <span>{{type1}}</span> <p>普通输入: {{ age1 }}</p> </div> <div> <input v-model.number="age2" type="number"> <span>{{type2}}</span> <p>number修饰符输入: {{ age2 }}</p> </div> </div> <script> var example = new Vue({ el: '#example', data:{ age1:'', age2:'', }, computed:{ type1:function(){ return typeof(this.age1) }, type2:function(val){ return typeof(this.age2) }, } }) </script>
【.trim】:如果要自动过滤用户输入的首尾空格,可以添加
trim
修饰符到v-model
上过滤输入<div id="example"> <input v-model.trim="msg"> <p>msg is: {{ msg }}</p> </div> <script> var example = new Vue({ el: '#example', data:{ msg:'' } }) </script>
9.vue实例生命周期
- 前言:Vue实例在创建时有一系列的初始化步骤,例如建立数据观察,编译模板,创建数据绑定等。在此过程中,我们可以通过一些定义好的生命周期钩子函数来运行业务逻辑.
- 图示:
- 详解:
【beforeCreate】在实例开始初始化时同步调用。此时数据观测、事件等都尚未初始化
【created】在实例创建之后调用。此时已完成数据观测、事件方法,但尚未开始DOM编译,即未挂载到document中
【beforeMount】在mounted之前运行
【mounted】在编译结束时调用。此时所有指令已生效,数据变化已能触发DOM更新,但不保证$el已插入文档
【beforeUpdate】在实例挂载之后,再次更新实例(例如更新 data)时会调用该方法,此时尚未更新DOM结构
【updated】在实例挂载之后,再次更新实例并更新完DOM结构后调用
【beforeDestroy】在开始销毁实例时调用,此刻实例仍然有效
【destroyed】在实例被销毁之后调用。此时所有绑定和实例指令都已经解绑,子实例也被销毁
【activated】需要配合动态组件keep-live属性使用。在动态组件初始化渲染的过程中调用该方法
【deactivated】需要配合动态组件keep-live属性使用。在动态组件初始化移出的过程中调用该方法
- 例子:
<div id="example">{{message}}</div> <script> var vm = new Vue({ el: '#example', data:{ message:'match' }, beforeCreate(){ console.log('beforeCreate'); }, created(){ console.log('created'); }, beforeMount(){ console.log('beforeMount'); }, mounted(){ console.log('mounted'); }, beforeUpdate(){ console.log('beforeUpdate'); }, updated(){ console.log('updated'); //组件更新后调用$destroyed函数,进行销毁 this.$destroy(); }, beforeDestroy(){ console.log('beforeDestroy'); }, destroyed(){ console.log('destroyed'); }, }) </script>
10.vue自定义指令
- 前言:在Vue里,代码复用的主要形式和抽象是组件。然而,有的情况下,仍然需要对纯 DOM 元素进行底层操作,这时候就会用到自定义指令。
- 指令注册:以一个input元素自动获得焦点为例,当页面加载时,使用autofocus可以让元素将获得焦点 。但是autofocus在移动版Safari上不工作。现在注册一个使元素自动获取焦点的指令,指令注册类似于组件注册,包括全局指令和局部指令两种。
//全局指令---使用Vue.diretive()来全局注册指令 // 注册一个全局自定义指令 v-focus Vue.directive('focus', { // 当绑定元素插入到 DOM 中。 inserted: function (el) { // 聚焦元素 el.focus() } })
//局部指令---也可以注册局部指令,组件或Vue构造函数中接受一个 directives 的选项 var vm = new Vue({ el: '#example', directives:{ focus:{ inserted: function (el) { el.focus() } } } })
<div id="example"> <input v-focus> </div> <script> // 注册一个全局自定义指令 v-focus Vue.directive('focus', { // 当绑定元素插入到 DOM 中。 inserted: function (el) { // 聚焦元素 el.focus() } }) var vm = new Vue({ el: '#example', }) </script>
- 钩子函数:指令定义函数提供了几个钩子函数(可选)
【bind】只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作
【inserted】被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)
【update】所在组件的 VNode 更新时调用,但是可能发生在其孩子的 VNode 更新之前。指令的值可能发生了改变也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新
【componentUpdated】所在组件的 VNode 及其孩子的 VNode 全部更新时调用
【unbind】只调用一次, 指令与元素解绑时调用
- 钩子函数参数:钩子函数被赋予了以下参数,
【el】指令所绑定的元素,可以用来直接操作 DOM
【binding】一个对象,包含以下属性:
name: 指令名,不包括 v- 前缀。 value: 指令的绑定值, 例如: v-my-directive="1 + 1", value 的值是 2。 oldValue: 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。 expression: 绑定值的字符串形式。 例如 v-my-directive="1 + 1" , expression 的值是 "1 + 1"。 arg: 传给指令的参数。例如 v-my-directive:foo, arg 的值是 "foo"。 modifiers: 一个包含修饰符的对象。 例如: v-my-directive.foo.bar, 修饰符对象 modifiers 的值是 {
foo: true, bar: true }。【vnode】Vue 编译生成的虚拟节点
【oldVnode】上一个虚拟节点,仅在
update
和componentUpdated
钩子中可用[注意]除了
el
之外,其它参数都是只读的,尽量不要修改他们。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行<div id="example" v-demo:foo.a.b="message"></div> <script> Vue.directive('demo', { bind: function (el, binding, vnode) { var s = JSON.stringify el.innerHTML = 'name: ' + s(binding.name) + '<br>' + 'value: ' + s(binding.value) + '<br>' + 'expression: ' + s(binding.expression) + '<br>' + 'argument: ' + s(binding.arg) + '<br>' + 'modifiers: ' + s(binding.modifiers) + '<br>' + 'vnode keys: ' + Object.keys(vnode).join(', ') } }) new Vue({ el: '#example', data: { message: 'hello!' } }) </script>
【函数简写】大多数情况下,可能想在
bind
和update
钩子上做重复动作,并且不想关心其它的钩子函数。可以这样写:Vue.directive('color-swatch', function (el, binding) { el.style.backgroundColor = binding.value })
【对象字面量】 如果指令需要多个值,可以传入一个JS对象字面量。指令函数能够接受所有合法类型的JS表达式
<div v-demo="{ color: 'white', text: 'hello!' }"></div> Vue.directive('demo', function (el, binding) { console.log(binding.value.color) // => "white" console.log(binding.value.text) // => "hello!" })
11.vue风格指南精简版
- 组件名称:
【组件名为多个单词】(必要)组件名应该始终是多个单词的,根组件 App 除外。 这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的。
//bad Vue.component('todo', {}) //good Vue.component('todo-item', {})
【单文件组件文件名应该要么始终是单词大写开头 (PascalCase),要么始终横线连接 (kebab-case)】(强烈推荐)
//bad mycomponent.vue //good MyComponent.vue //good my-component.vue
【基础组件名要有一个特定前缀开头】(强烈推荐)
应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 Base、App 或 V
//bad components/ |- MyButton.vue |- VueTable.vue |- Icon.vue //good components/ |- BaseButton.vue |- BaseTable.vue |- BaseIcon.vue
【只应该拥有单个活跃实例的组件应该以
The
前缀命名,以示其唯一性】(强烈推荐)这不意味着组件只可用于一个单页面,而是每个页面只使用一次,这些组件永远不接受任何 prop
//bad components/ |- Heading.vue |- MySidebar.vue //good components/ |- TheHeading.vue |- TheSidebar.vue
【和父组件紧密耦合的子组件应该以父组件名作为前缀命名】(强烈推荐)
//bad components/ |- TodoList.vue |- TodoItem.vue |- TodoButton.vue //good components/ |- SearchSidebar.vue |- SearchSidebarNavigation.vue
【组件名应该以高级别的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾】(强烈推荐)
//bad components/ |- ClearSearchButton.vue |- ExcludeFromSearchInput.vue |- LaunchOnStartupCheckbox.vue |- RunSearchButton.vue |- SearchInput.vue |- TermsCheckbox.vue //good components/ |- SearchButtonClear.vue |- SearchButtonRun.vue |- SearchInputQuery.vue |- SearchInputExcludeGlob.vue |- SettingsCheckboxTerms.vue |- SettingsCheckboxLaunchOnStartup.vue
【单文件组件和字符串模板中组件名应总是PascalCase——但在DOM模板中总是kebab-case】(强烈推荐)
//bad <!-- 在单文件组件和字符串模板中 --> <mycomponent/> <myComponent/> <!-- 在 DOM 模板中 --> <MyComponent></MyComponent> //good <!-- 在单文件组件和字符串模板中 --> <MyComponent/> <!-- 在 DOM 模板中 --> <my-component></my-component>
【组件名应该倾向于完整单词而不是缩写】(强烈推荐)
//bad components/ |- SdSettings.vue |- UProfOpts.vue //good components/ |- StudentDashboardSettings.vue |- UserProfileOptions.vue
-
组件相关
【单文件组件、字符串模板和JSX中没有内容的组件应该自闭合——但在DOM模板里不要这样做】(强烈推荐)自闭合组件表示它们不仅没有内容,而且刻意没有内容
//bad <!-- 在单文件组件、字符串模板和 JSX 中 --> <MyComponent></MyComponent> <!-- 在 DOM 模板中 --> <my-component/> //good <!-- 在单文件组件、字符串模板和 JSX 中 --> <MyComponent/> <!-- 在 DOM 模板中 --> <my-component></my-component>
【为组件样式设置作用域】(必要)
这条规则只和单文件组件有关。不一定要使用
scoped
特性。设置作用域也可以通过 CSS Modules,或者使用其它的库或约定//bad <template><button class="btn btn-close">X</button></template> <style> .btn-close {background-color: red;} </style> //good <template><button class="btn btn-close">X</button></template> <style scoped> .btn-close {background-color: red;} </style> //good <template><button :class="[$style.button, $style.buttonClose]">X</button></template> <style module> .btn-close {background-color: red;} </style>
【单文件组件应该总是让 <script>、<template> 和 <style> 标签的顺序保持一致】(推荐)
//good <!-- ComponentA.vue --> <script>/* ... */</script> <template>...</template> <style>/* ... */</style> <!-- ComponentB.vue --> <script>/* ... */</script> <template>...</template> <style>/* ... */</style>
【一个文件中只有一个组件】(强烈推荐)
//bad Vue.component('TodoList', {}) Vue.component('TodoItem', {}) //good components/ |- TodoList.vue |- TodoItem.vue
【组件选项默认顺序】(推荐)
1>、副作用 (触发组件外的影响)
el
2>、全局感知 (要求组件以外的知识)
name parent
3>、组件类型 (更改组件的类型)
functional
4>、模板修改器 (改变模板的编译方式)
delimiters comments
5>、模板依赖 (模板内使用的资源)
components directives filters
6>、组合 (向选项里合并属性)
extends mixins
7>、接口 (组件的接口)
inheritAttrs model props/propsData
8>、本地状态 (本地的响应式属性)
data computed
9>、事件 (通过响应式事件触发的回调)
watch
生命周期钩子 (按照它们被调用的顺序)10>、非响应式的属性 (不依赖响应系统的实例属性)
methods
11>、渲染 (组件输出的声明式描述)
template/render renderError
-
prop
【Prop 定义应该尽量详细】(必要)
细致的 prop 定义有两个好处: 1、它们写明了组件的 API,所以很容易看懂组件的用法; 2、在开发环境下,如果向一个组件提供格式不正确的 prop,Vue 将会告警,以帮助你捕获潜在的错误来源
//bad props: ['status'] //good props: { status: String } //better props: { status: { type: String, required: true } }
【声明prop时,其命名应始终使用camelCase,而在模板和JSX中应始终使用kebab-case】(强烈推荐)
//bad props: {'greeting-text': String} <WelcomeMessage greetingText="hi"/> //good props: {greetingText: String} <WelcomeMessage greeting-text="hi"/>
-
指令以及特性
【总是用 key 配合 v-for】(必要)
//bad <li v-for="todo in todos"> //good <li v-for="todo in todos":key="todo.id">
【不要把 v-if 和 v-for 同时用在同一个元素上】(必要)
//bad <li v-for="user in users" v-if="user.isActive" :key="user.id" > {{ user.name }} <li> //good <li v-for="user in users" v-if="shouldShowUsers" :key="user.id" > {{ user.name }} <li>
【多个特性的元素应该分多行撰写,每个特性一行】(强烈推荐)
//bad <img src="https://vuejs.org/images/logo.png" alt="Vue Logo"> //good <img src="https://vuejs.org/images/logo.png" alt="Vue Logo" >
【元素特性默认顺序】(推荐)
1>、定义 (提供组件的选项)
is
2>、列表渲染 (创建多个变化的相同元素)
v-for
3>、条件渲染 (元素是否渲染/显示)
v-if v-else-if v-else v-show v-cloak
4>、渲染方式 (改变元素的渲染方式)
v-pre v-once
5>、全局感知 (需要超越组件的知识)
id
6>、唯一的特性 (需要唯一值的特性)
ref key slot
7>、双向绑定 (把绑定和事件结合起来)
v-model
8>、其它特性 (所有普通的绑定或未绑定的特性)
9>、事件 (组件事件监听器)
v-on
10>、内容 (复写元素的内容)
v-html v-text
- 属性:
【私有属性名】(必要)
在插件、混入等扩展中始终为自定义的私有属性使用 $_ 前缀,并附带一个命名空间以回避和其它作者的冲突 (比如 $_yourPluginName_)
//bad methods: {update: function () { }} //bad methods: {_update: function () { } } //bad methods: {$update: function () { }} //bad methods: {$_update: function () { }} //good methods: { $_myGreatMixin_update: function () { }}
【组件的data必须是一个函数】(必要)
当在组件中使用 data 属性的时候 (除了 new Vue 外的任何地方),它的值必须是返回一个对象的函数
//bad Vue.component('some-comp', { data: { foo: 'bar' } }) //good Vue.component('some-comp', { data: function () { return { foo: 'bar' } } })
【组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法】(强烈推荐)
//bad {{ fullName.split(' ').map(function (word) { return word[0].toUpperCase() + word.slice(1) }).join(' ') }} //good computed: { normalizedFullName: function () { return this.fullName.split(' ').map(function (word) { return word[0].toUpperCase() + word.slice(1) }).join(' ') } }
【应该把复杂计算属性分割为尽可能多的更简单的属性】(强烈推荐)
//bad computed: { price: function () { var basePrice = this.manufactureCost / (1 - this.profitMargin) return ( basePrice - basePrice * (this.discountPercent || 0) ) } } //good computed: { basePrice: function () { return this.manufactureCost / (1 - this.profitMargin) }, discount: function () { return this.basePrice * (this.discountPercent || 0) }, finalPrice: function () { return this.basePrice - this.discount } }
【当组件开始觉得密集或难以阅读时,在多个属性之间添加空行可以让其变得容易】(推荐)
//good props: { value: { type: String, required: true }, focused: { type: Boolean, default: false } }
- 注意谨慎使用:
1、元素选择器应该避免在 scoped 中出现
在
scoped
样式中,类选择器比元素选择器更好,因为大量使用元素选择器是很慢的//bad <style scoped> button { background-color: red; } </style> //good <style scoped> .btn-close { background-color: red; } </style>
2、应该优先通过 prop 和事件进行父子组件之间的通信,而不是
this.$parent
或改变 prop3、应该优先通过 Vuex 管理全局状态,而不是通过
this.$root
或一个全局事件总线4、如果一组
v-if
+v-else
的元素类型相同,最好使用key
(比如两个<div>
元素)//bad <div v-if="error"> 错误:{{ error }} </div> <div v-else> {{ results }} </div> //good <div v-if="error" key="search-status" > 错误:{{ error }} </div> <div v-else key="search-results" > {{ results }} </div>
12.vue组件选项props
- 在 Vue 中,父子组件的关系可以总结为 props down, events up。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。
- 父子级组件:在介绍props之前,先介绍父子级组件的写法:在一个良好定义的接口中尽可能将父子组件解耦是很重要的。这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性
【错误写法】
现在来介绍两种父子级组件的错误写法
下面这种形式的写法是错误的,因为当子组件注册到父组件时,Vue.js会编译好父组件的模板,模板的内容已经决定了父组件将要渲染的HTML
<parent>...</parent>
运行时,它的一些子标签只会被当作普通的HTML来执行,<child></child>不是标准的HTML标签,会被浏览器直接忽视掉//错误 <div id="example"> <parent> <child></child> <child></child> </parent> </div> <div id="example"> <parent></parent> <child></child> </div> //正确写法 <div id="example"> <parent></parent> </div> <script> var childNode = { template: '<div>childNode</div>', } var parentNode = { template: ` <div class="parent"> <child></child> <child></child> </div> `, components: { 'child': childNode } }; // 创建根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
- 静态props:组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。要让子组件使用父组件的数据,需要通过子组件的 props 选项
使用Prop传递数据包括静态和动态两种形式,下面先介绍静态props
子组件要显式地用
props
选项声明它期待获得的数据,静态Prop通过为子组件在父组件中的占位符添加特性的方式来达到传值的目的<script> var childNode = { template: '<div>{{message}}</div>', props:['message'] } var parentNode = { template: ` <div class="parent"> <child message="aaa"></child> <child message="bbb"></child> </div>`, components: { 'child': childNode } }; // 创建根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
- 命名约定:对于props声明的属性来说,在父级HTML模板中,属性名需要使用中划线写法,子级props属性声明时,使用小驼峰或者中划线写法都可以;而子级模板使用从父级传来的变量时,需要使用对应的小驼峰写法
var parentNode = { template: ` <div class="parent"> <child my-message="aaa"></child> <child my-message="bbb"></child> </div>`, components: { 'child': childNode } }; var childNode = { template: '<div>{{myMessage}}</div>', props:['myMessage'] } var childNode = { template: '<div>{{myMessage}}</div>', props:['my-message'] }
- 动态props:在模板中,要动态地绑定父组件的数据到子模板的 props,与绑定到任何普通的HTML特性相类似,就是用
v-bind
。每当父组件的数据变化时,该变化也会传导给子组件var childNode = { template: '<div>{{myMessage}}</div>', props:['myMessage'] } var parentNode = { template: ` <div class="parent"> <child :my-message="data1"></child> <child :my-message="data2"></child> </div>`, components: { 'child': childNode }, data(){ return { 'data1':'aaa', 'data2':'bbb' } } };
-
传递数字:初学者常犯的一个错误是使用字面量语法传递数值
<!-- 传递了一个字符串 "1" --> <comp some-prop="1"></comp> <div id="example"> <my-parent></my-parent> </div> <script> var childNode = { template: '<div>{{myMessage}}的类型是{{type}}</div>', props:['myMessage'], computed:{ type(){ return typeof this.myMessage } } } var parentNode = { template: ` <div class="parent"> <my-child my-message="1"></my-child> </div>`, components: { 'myChild': childNode } }; // 创建根实例 new Vue({ el: '#example', components: { 'MyParent': parentNode } }) </script>
因为它是一个字面 prop,它的值是字符串
"1"
而不是 number。如果想传递一个实际的 number,需要使用v-bind
,从而让它的值被当作JS表达式计算<!-- 传递实际的 number --> <comp v-bind:some-prop="1"></comp> var parentNode = { template: ` <div class="parent"> <my-child :my-message="1"></my-child> </div>`, components: { 'myChild': childNode } };
或者可以使用动态props,在data属性中设置对应的数字1
var parentNode = { template: ` <div class="parent"> <my-child :my-message="data"></my-child> </div>`, components: { 'myChild': childNode }, data(){ return { 'data': 1 } } };
- props验证:可以为组件的 props 指定验证规格。如果传入的数据不符合规格,Vue会发出警告。当组件给其他人使用时,这很有用,要指定验证规格,需要用对象的形式,而不能用字符串数组
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,Symbol,type
也可以是一个自定义构造器函数,使用instanceof
检测。当 prop 验证失败,Vue 会在抛出警告 (如果使用的是开发版本)。props会在组件实例创建之前进行校验,所以在default
或validator
函数里,诸如data
、computed
或methods
等实例属性还无法使用<div id="example"> <parent></parent> </div> <script> var childNode = { template: '<div>{{message}}</div>', props:{ 'message':Number } } var parentNode = { template: ` <div class="parent"> <child :message="msg"></child> </div>`, components: { 'child': childNode }, data(){ return{ msg: '123' } } }; // 创建根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
传入数字123时,则无警告提示。传入字符串'123'时,会有警告;将上面代码中,子组件的内容修改如下,可自定义验证函数,当函数返回为false时,则输出警告提示
var childNode = { template: '<div>{{message}}</div>', props:{ 'message':{ validator: function (value) { return value > 10 } } } } // 在父组件中传入msg值为1,由于小于10,则输出警告提示 var parentNode = { template: ` <div class="parent"> <child :message="msg"></child> </div>`, components: { 'child': childNode }, data(){ return{ msg:1 } } };
- 单向流数据:prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着不应该在子组件内部改变 prop。如果这么做了,Vue 会在控制台给出警告
<div id="example"> <parent></parent> </div> <script> var childNode = { template: ` <div class="child"> <div> <span>子组件数据</span> <input v-model="childMsg"> </div> <p>{{childMsg}}</p> </div> `, props:['childMsg'] } var parentNode = { template: ` <div class="parent"> <div> <span>父组件数据</span> <input v-model="msg"> </div> <p>{{msg}}</p> <child :child-msg="msg"></child> </div> `, components: { 'child': childNode }, data(){ return { 'msg':'match' } } }; // 创建根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
父组件数据变化时,子组件数据会相应变化;而子组件数据变化时,父组件数据不变,并在控制台显示警告
- 修改prop数据:修改prop中的数据,通常有以下两种原因1、prop 作为初始值传入后,子组件想把它当作局部数据来用2、prop 作为初始值传入,由子组件处理成其它数据输出,[注意]JS中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。对于这两种情况,正确的应对方式是
// 1、定义一个局部变量,并用 prop 的值初始化它 props: ['initialCounter'], data: function () { return { counter: this.initialCounter } } //但是,定义的局部变量counter只能接受initialCounter的初始值,当父组件要传递的值发生变化时,counter无法接收到最新值,下面示例中,除初始值外,父组件的值无法更新到子组件中 <div id="example"> <parent></parent> </div> <script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <div> <span>子组件数据</span> <input v-model="temp"> </div> <p>{{temp}}</p> </div> `, props:['childMsg'], data(){ return{ temp:this.childMsg } }, }; var parentNode = { template: ` <div class="parent"> <div> <span>父组件数据</span> <input v-model="msg"> </div> <p>{{msg}}</p> <child :child-msg="msg"></child> </div> `, components: { 'child': childNode }, data(){ return { 'msg':'match' } } }; // 创建根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
另一种是定义一个计算属性,处理 prop 的值并返回,下面示例中,由于子组件使用的是计算属性,所以,子组件的数据无法手动修改
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } } //但是,由于是计算属性,则只能显示值,而不能设置值 <script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <div> <span>子组件数据</span> <input v-model="temp"> </div> <p>{{temp}}</p> </div> `, props:['childMsg'], computed:{ temp(){ return this.childMsg } }, }; var parentNode = { template: ` <div class="parent"> <div> <span>父组件数据</span> <input v-model="msg"> </div> <p>{{msg}}</p> <child :child-msg="msg"></child> </div> `, components: { 'child': childNode }, data(){ return { 'msg':'match' } } }; // 创建根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
更加妥帖的方案是,使用变量储存prop的初始值,并使用watch来观察prop的值的变化。发生变化时,更新变量的值
<div id="example"> <parent></parent> </div> <script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <div> <span>子组件数据</span> <input v-model="temp"> </div> <p>{{temp}}</p> </div> `, props:['childMsg'], data(){ return{ temp:this.childMsg } }, watch:{ childMsg(){ this.temp = this.childMsg } } }; var parentNode = { template: ` <div class="parent"> <div> <span>父组件数据</span> <input v-model="msg"> </div> <p>{{msg}}</p> <child :child-msg="msg"></child> </div> `, components: { 'child': childNode }, data(){ return { 'msg':'match' } } }; // 创建根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
13.vue自定义事件
- 前言:父组件使用props传递数据给子组件,子组件怎么跟父组件通信呢?这时,Vue的自定义事件就派上用场了。
- 事件绑定:每个 Vue 实例都实现了事件接口 (Events interface),即
使用 $on(eventName) 监听事件 使用 $emit(eventName) 触发事件
[注意]Vue 的事件系统分离自浏览器的EventTarget API。尽管它们的运行类似,但是
$on
和$emit
不是addEventListener
和dispatchEvent
的别名另外,父组件可以在使用子组件的地方直接用
v-on
来监听子组件触发的事件[注意]不能用
$on
侦听子组件抛出的事件,而必须在模板里直接用v-on
绑定<div id="example"> <parent></parent> </div> <script> var childNode = { template: `<button @click="incrementCounter">{{ counter }}</button>`, data(){ return { counter: 0 } }, methods:{ incrementCounter(){ this.counter ++; this.$emit('increment'); } }, } var parentNode = { template: ` <div class="parent"> <p>{{total}}</p> <child @increment="incrementTotal"></child> <child @increment="incrementTotal"></child> </div> `, components: { 'child': childNode }, data(){ return { 'total':0 } }, methods:{ incrementTotal(){ this.total ++; } } }; // 创建根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
- 命名约定:自定义事件的命名约定与组件注册及props的命名约定都不相同,由于自定义事件实质上也是属于HTML的属性,所以其在HTML模板中,最好使用中划线形式。
<child @pass-data="getData"></child> //而子组件中触发事件时,同样使用中划线形式 this.$emit('pass-data',this.childMsg)
-
数据传递:子组件通过$emit可以触发事件,第一个参数为要触发的事件,第二个事件为要传递的数据
this.$emit('pass-data',this.childMsg)
父组件通过$on监听事件,事件处理函数的参数则为接收的数据
getData(value){ this.msg = value; }
下面示例中,修改子组件中的input值,则父组件到接收到相同值,则显示出来
<div id="example"> <parent></parent> </div> <script> var childNode = { template: ` <div class="child"> <div> <span>子组件数据</span> <input v-model="childMsg" @input="data"> </div> <p>{{childMsg}}</p> </div> `, data(){ return{ childMsg:'' } }, methods:{ data(){ this.$emit('pass-data',this.childMsg) } } } var parentNode = { template: ` <div class="parent"> <div> <span>父组件数据</span> <input v-model="msg"> </div> <p>{{msg}}</p> <child @pass-data="getData"></child> </div> `, components: { 'child': childNode }, data(){ return { 'msg':'match' } }, methods:{ getData(value){ this.msg = value; } } }; // 创建根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
14.vue内容分发slot
- 前言:为了让组件可以组合,需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为 内容分发 (或 “transclusion” )。Vue实现了一个内容分发 API,参照了当前 Web 组件规范草案,使用特殊的
<slot>
元素作为原始内容的插槽。 - 编译作用域:在深入内容分发 API 之前,先明确内容在哪个作用域里编译。假定模板为
<child-component> {{ message }} </child-component>
message
应该绑定到父组件的数据,还是绑定到子组件的数据?答案是父组件。组件作用域简单地说是:父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译。一个常见错误是试图在父组件模板内将一个指令绑定到子组件的属性/方法:<!-- 无效 --> <child-component v-show="someChildProperty"></child-component>
假定
someChildProperty
是子组件的属性,上例不会如预期工作。父组件模板不应该知道子组件的状态如果要绑定作用域内的指令到一个组件的根节点,应当在组件自己的模板上做,类似地,分发内容是在父作用域内编译:
Vue.component('child-component', { // 有效,因为是在正确的作用域内 template: '<div v-show="someChildProperty">Child</div>', data: function () { return { someChildProperty: true } } })
- 默认丢弃:一般地,如果子组件模板不包含
<slot>
插口,父组件的内容将会被丢弃,如下,<child>所包含的<p>测试内容</p>被丢弃<div id="example"> <parent></parent> </div> <script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <p>子组件</p> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <p>测试内容</p> </child> </div> `, components: { 'child': childNode }, }; // 创建根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
-
内联模板: 如果子组件有
inline-template
特性,组件将把它的内容当作它的模板,而忽略真实的模板内容, 但是inline-template
让模板的作用域难以理解.var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child inline-template> <p>测试内容</p> </child> </div> `, components: { 'child': childNode }, };
- 匿名slot:当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身。如果出现多于1个的匿名slot,vue将报错
var childNode = { template: ` <div class="child"> <p>子组件</p> <slot></slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <p>测试内容</p> </child> </div> `, components: { 'child': childNode }, };
【默认值】 最初在
<slot>
标签中的任何内容都被视为备用内容,或者称为默认值。备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容,当slot存在默认值,且父元素在<child>中没有要插入的内容时,显示默认值。当slot存在默认值,且父元素在<child>中存在要插入的内容时,则显示设置值。var childNode = { template: ` <div class="child"> <p>子组件</p> <slot><p>我是默认值</p></slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <p>我是设置值</p> </child> </div> `, components: { 'child': childNode }, };
var childNode = { template: ` <div class="child"> <p>子组件</p> <slot><p>我是默认值</p></slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child></child> </div> `, components: { 'child': childNode }, };
- 具名slot:
<slot>
元素可以用一个特殊的属性name
来配置如何分发内容。多个 slot 可以有不同的名字。具名 slot 将匹配内容片段中有对应slot
特性的元素。var childNode = { template: ` <div class="child"> <p>子组件</p> <slot name="my-header">头部默认值</slot> <slot name="my-body">主体默认值</slot> <slot name="my-footer">尾部默认值</slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <p slot="my-header">我是头部</p> <p slot="my-footer">我是尾部</p> </child> </div> `, components: { 'child': childNode }, };
仍然可以有一个匿名 slot,它是默认 slot,作为找不到匹配的内容片段的备用插槽。匿名slot只能作为没有slot属性的元素的插槽,有slot属性的元素如果没有配置slot,则会被抛弃.以下中的<p slot="my-body">插入<slot name="my-body">中,<p>我是其他内容</p>插入<slot>中,而<p slot="my-footer">被丢弃;如果没有默认的 slot,这些找不到匹配的内容片段也将被抛弃;以下的<p>我是其他内容</p>和<p slot="my-footer">都被抛弃
var childNode = { template: ` <div class="child"> <p>子组件</p> <slot name="my-body">主体默认值</slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <p slot="my-body">我是主体</p> <p>我是其他内容</p> <p slot="my-footer">我是尾部</p> </child> </div> `, components: { 'child': childNode }, };
var childNode = { template: ` <div class="child"> <p>子组件</p> <slot name="my-body">主体默认值</slot> <slot></slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <p slot="my-body">我是主体</p> <p>我是其他内容</p> <p slot="my-footer">我是尾部</p> </child> </div> `, components: { 'child': childNode }, };
- 作用域插槽:作用域插槽是一种特殊类型的插槽,用作使用一个 (能够传递数据到) 可重用模板替换已渲染元素。在子组件中,只需将数据传递到插槽,就像将 props 传递给组件一样。在父级中,具有特殊属性
scope
的<template>
元素必须存在,表示它是作用域插槽的模板。scope
的值对应一个临时变量名,此变量接收从子组件中传递的 props 对象。var childNode = { template: ` <div class="child"> <p>子组件</p> <slot xxx="hello from child"></slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <template scope="props"> <p>hello from parent</p> <p>{{ props.xxx }}</p> </template> </child> </div> `, components: { 'child': childNode }, };
如果渲染以上结果,得到的输出是:
【列表组件】作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表每一项:
var childNode = { template: ` <ul> <slot name="item" v-for="item in items" :text="item.text">默认值</slot> </ul> `, data(){ return{ items:[ {id:1,text:'第1段'}, {id:2,text:'第2段'}, {id:3,text:'第3段'}, ] } } }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <template slot="item" scope="props"> <li>{{ props.text }}</li> </template> </child> </div> `, components: { 'child': childNode }, };
14.vue动态组件
- 定义:让多个组件使用同一个挂载点,并动态切换,这就是动态组件。
- 概述:通过使用保留的
<component>
元素,动态地绑定到它的is
特性,可以实现动态组件。<div id="example"> <button @click="change">切换页面</button> <component :is="currentView"></component> </div> <script> var home = {template:'<div>我是主页</div>'}; var post = {template:'<div>我是提交页</div>'}; var archive = {template:'<div>我是存档页</div>'}; new Vue({ el: '#example', components: { home, post, archive, }, data:{ index:0, arr:['home','post','archive'], }, computed:{ currentView(){ return this.arr[this.index]; } }, methods:{ change(){ this.index = (++this.index)%3; } } }) </script>
也可以直接绑定到组件对象上:
<div id="example"> <button @click="change">切换页面</button> <component :is="currentView"></component> </div> <script> new Vue({ el: '#example', data:{ index:0, arr:[ {template:`<div>我是主页</div>`}, {template:`<div>我是提交页</div>`}, {template:`<div>我是存档页</div>`} ], }, computed:{ currentView(){ return this.arr[this.index]; } }, methods:{ change(){ this.index = (++this.index)%3; } } }) </script>
- 缓存:
<keep-alive>
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和<transition>
相似,<keep-alive>
是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。<div id="example"> <button @click="change">切换页面</button> <keep-alive> <component :is="currentView"></component> </keep-alive> </div> <script> new Vue({ el: '#example', data:{ index:0, arr:[ {template:`<div>我是主页</div>`}, {template:`<div>我是提交页</div>`}, {template:`<div>我是存档页</div>`} ], }, computed:{ currentView(){ return this.arr[this.index]; } }, methods:{ change(){ let len = this.arr.length; this.index = (++this.index)% len; } } }) </script>
【条件判断】 如果有多个条件性的子元素,
<keep-alive>
要求同时只有一个子元素被渲染<div id="example"> <button @click="change">切换页面</button> <keep-alive> <home v-if="index===0"></home> <posts v-else-if="index===1"></posts> <archive v-else></archive> </keep-alive> </div> <script> new Vue({ el: '#example', components:{ home:{template:`<div>我是主页</div>`}, posts:{template:`<div>我是提交页</div>`}, archive:{template:`<div>我是存档页</div>`}, }, data:{ index:0, }, methods:{ change(){ let len = Object.keys(this.$options.components).length; this.index = (++this.index)%len; } } }) </script>
【
activated
和deactivated
】activated
和deactivated
在<keep-alive>
树内的所有嵌套组件中触发<div id="example"> <button @click="change">切换页面</button> <keep-alive> <component :is="currentView" @pass-data="getData"></component> </keep-alive> <p>{{msg}}</p> </div> <script> new Vue({ el: '#example', data:{ index:0, msg:'', arr:[ { template:`<div>我是主页</div>`, activated(){ this.$emit('pass-data','主页被添加'); }, deactivated(){ this.$emit('pass-data','主页被移除'); }, }, {template:`<div>我是提交页</div>`}, {template:`<div>我是存档页</div>`} ], }, computed:{ currentView(){ return this.arr[this.index]; } }, methods:{ change(){ var len = this.arr.length; this.index = (++this.index)% len; }, getData(value){ this.msg = value; setTimeout(()=>{ this.msg = ''; },500) } } }) </script>
【
include和
exclude
】include
和exclude
属性允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示<!-- 逗号分隔字符串 --> <keep-alive include="a,b"> <component :is="view"></component> </keep-alive> <!-- 正则表达式 (使用 v-bind) --> <keep-alive :include="/a|b/"> <component :is="view"></component> </keep-alive> <!-- Array (use v-bind) --> <keep-alive :include="['a', 'b']"> <component :is="view"></component> </keep-alive>
匹配首先检查组件自身的
name
选项,如果name
选项不可用,则匹配它的局部注册名称(父组件components
选项的键值)。匿名组件不能被匹配<keep-alive include="home,archive"> <component :is="currentView"></component> </keep-alive>
上面的代码,表示只缓存home和archive,不缓存posts
<div id="example"> <button @click="change">切换页面</button> <keep-alive include="home,archive"> <component :is="currentView"></component> </keep-alive> </div> <script src="https://unpkg.com/vue"></script> <script> new Vue({ el: '#example', data:{ index:0, arr:[ {name:'home',template:`<div>我是主页</div>`}, {name:'posts',template:`<div>我是提交页</div>`}, {name:'archive',template:`<div>我是存档页</div>`} ], }, computed:{ currentView(){ return this.arr[this.index]; } }, methods:{ change(){ var len = this.arr.length; this.index = (++this.index)% len; }, } }) </script>
15.vue组件实例间的直接访问
- 前言:有时候需要父组件访问子组件,子组件访问父组件,或者是子组件访问根组件。 在组件实例中,Vue提供了相应的属性,包括$parent、$children、$refs和$root,这些属性都挂载在组件的this上。
- $parent:$parent表示父组件的实例,该属性只读
<div id="example"> <parent-component></parent-component> </div> <template id="parent-component"> <div class="parent"> <h3>我是父组件</h3> <input v-model="parentMsg"> <p>{{parentMsg}}</p> <child-component></child-component> </div> </template> <template id="child-component"> <div class="child"> <h3>我是子组件</h3> <p>{{msg}}</p> <button v-on:click="showData">显示父组件数据</button> </div> </template> <script> // 注册 Vue.component('parent-component', { template: '#parent-component', data(){ return{ parentMsg:'我是父组件的数据' } }, components:{ 'child-component':{ template:'#child-component', data(){ return{ msg:'' } }, methods:{ showData(){ this.msg = this.$parent.parentMsg; } } } } }) // 创建根实例 new Vue({ el: '#example' }) </script>
- $root:表示当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。
<div id="example"> <h3>我是根组件</h3> <input v-model="rootMsg"> <p>{{rootMsg}}</p> <parent-component></parent-component> </div> <template id="parent-component"> <div class="parent"> <h3>我是父组件</h3> <input v-model="parentMsg"> <p>{{parentMsg}}</p> <child-component></child-component> </div> </template> <template id="child-component"> <div class="child"> <h3>我是子组件</h3> <p> <button v-on:click="showRootData">显示根组件数据</button><span>{{rootMsg}}</span> </p> <p> <button v-on:click="showParentData">显示父组件数据</button><span>{{parentMsg}}</span> </p> </div> </template> <script> // 注册 Vue.component('parent-component', { template: '#parent-component', data(){ return{ parentMsg:'我是父组件的数据' } }, components:{ 'child-component':{ template:'#child-component', data(){ return{ parentMsg:'', rootMsg:'' } }, methods:{ showParentData(){ this.parentMsg = this.$parent.parentMsg; }, showRootData(){ this.rootMsg = this.$root.rootMsg; }, } } } }) // 创建根实例 new Vue({ el: '#example', data:{ rootMsg:'我是根组件数据' } }) </script>
- $children:表示当前实例的直接子组件。需要注意
$children
并不保证顺序,也不是响应式的。如果正在尝试使用$children
来进行数据绑定,考虑使用一个数组配合v-for
来生成子组件,并且使用Array作为真正的来源<div id="example"> <parent-component></parent-component> </div> <template id="parent-component"> <div class="parent"> <h3>我是父组件</h3> <button @click="getData">获取子组件数据</button> <br> <div v-html="msg"></div> <child-component1></child-component1> <child-component2></child-component2> </div> </template> <template id="child-component1"> <div class="child"> <h3>我是子组件1</h3> <input v-model="msg"> <p>{{msg}}</p> </div> </template> <template id="child-component2"> <div class="child"> <h3>我是子组件2</h3> <input v-model="msg"> <p>{{msg}}</p> </div> </template> <script> // 注册 Vue.component('parent-component', { template: '#parent-component', data(){ return{ msg:'', } }, methods:{ getData(){ let html = ''; let children = this.$children; for(var i = 0; i < children.length;i++){ html+= '<div>' + children[i].msg + '</div>'; } this.msg = html; } }, components:{ 'child-component1':{ template:'#child-component1', data(){ return{ msg:'', } }, }, 'child-component2':{ template:'#child-component2', data(){ return{ msg:'', } }, }, } }) // 创建根实例 new Vue({ el: '#example', }) </script>
- $refs:组件个数较多时,难以记住各个组件的顺序和位置,通过序号访问子组件不是很方便,在子组件上使用ref属性,可以给子组件指定一个索引ID:
<child-component1 ref="c1"></child-component1> <child-component2 ref="c2"></child-component2>
在父组件中,则通过
$refs.索引ID
访问子组件的实例this.$refs.c1 this.$refs.c2
<div id="example"> <parent-component></parent-component> </div> <template id="parent-component"> <div class="parent"> <h3>我是父组件</h3> <div> <button @click="getData1">获取子组件c1的数据</button> <p>{{msg1}}</p> </div> <div> <button @click="getData2">获取子组件c2的数据</button> <p>{{msg2}}</p> </div> <child-component1 ref="c1"></child-component1> <child-component2 ref="c2"></child-component2> </div> </template> <template id="child-component1"> <div class="child"> <h3>我是子组件1</h3> <input v-model="msg"> <p>{{msg}}</p> </div> </template> <template id="child-component2"> <div class="child"> <h3>我是子组件2</h3> <input v-model="msg"> <p>{{msg}}</p> </div> </template> <script> // 注册 Vue.component('parent-component', { template: '#parent-component', data(){ return{ msg1:'', msg2:'', } }, methods:{ getData1(){ this.msg1 = this.$refs.c1.msg; }, getData2(){ this.msg2 = this.$refs.c2.msg; }, }, components:{ 'child-component1':{ template:'#child-component1', data(){ return{ msg:'', } }, }, 'child-component2':{ template:'#child-component2', data(){ return{ msg:'', } }, }, } }) // 创建根实例 new Vue({ el: '#example', }) </script>
- 总结:虽然vue提供了以上方式对组件实例进行直接访问,但并不推荐这么做。这会导致组件间紧密耦合,且自身状态难以理解,所以尽量使用props、自定义事件以及内容分发slot来传递数据
16.vue单文件组件
- 概述:在很多 Vue 项目中,使用
Vue.component
来定义全局组件,紧接着用new Vue({ el:'#container '})
在每个页面内指定一个容器元素。这种方式在很多中小规模的项目中运作的很好,在这些项目里 JavaScript 只被用来加强特定的视图。但当在更复杂的项目中,或者前端完全由 JavaScript 驱动的时候,下面这些缺点将变得非常明显:
1>、全局定义 (Global definitions) 强制要求每个 component 中的命名不得重复
2>、字符串模板 (String templates) 缺乏语法高亮,在 HTML 有多行的时候,需要用到丑陋的
3>、不支持 CSS (No CSS support) 意味着当 HTML 和 JavaScript 组件化时,CSS 明显被遗漏
4>、没有构建步骤 (No build step) 限制只能使用 HTML 和 ES5 JavaScript, 而不能使用预处理器,如 Pug (formerly Jade) 和 Babel
文件扩展名为
.vue
的 single-file components(单文件组件) 为以上所有问题提供了解决方法,并且还可以使用 Webpack 或 Browserify 等构建工具
17.vue过渡之css过渡
- 初始:如果不加入过渡效果:
<div id="demo"> <button v-on:click="show = !show">Toggle</button> <p v-if="show">小火柴的蓝色理想</p> </div> <script> new Vue({ el: '#demo', data: { show: true } }) </script>
- 过渡组件:Vue提供了
transition
的封装组件,下面代码中,该过渡组件的名称为'fade'<transition name="fade"> <p v-if="show">小火柴的蓝色理想</p> </transition>
当插入或删除包含在
transition
组件中的元素时,Vue会自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名 -
过渡类名:总共有6个(CSS)类名在enter/leave的过渡中切换
【v-enter】
定义进入过渡的开始状态。在元素被插入时生效,在下一个帧移除
【v-enter-active】
定义过渡的状态。在元素整个过渡过程中作用,在元素被插入时生效,在
transition 或 animation
完成之后移除。 这个类可以被用来定义过渡的过程时间,延迟和曲线函数【v-enter-to】
定义进入过渡的结束状态。在元素被插入一帧后生效(与此同时
v-enter
被删除),在transition 或 animation
完成之后移除【v-leave】
定义离开过渡的开始状态。在离开过渡被触发时生效,在下一个帧移除
【v-leave-active】
定义过渡的状态。在元素整个过渡过程中作用,在离开过渡被触发后立即生效,在
transition 或 animation
完成之后移除。 这个类可以被用来定义过渡的过程时间,延迟和曲线函数【v-leave-to】
定义离开过渡的结束状态。在离开过渡被触发一帧后生效(与此同时
v-leave
被删除),在transition 或 animation
完成之后移除对于这些在
enter/leave
过渡中切换的类名,v-
是这些类名的前缀,表示过渡组件的名称。比如,如果使用<transition name="my-transition">
,则v-enter
替换为my-transition-enter
- transition:常用的Vue过渡效果都是使用CSS过渡transition,下面增加一个enter时透明度变化,leave时位移变化的效果
<style> .fade-enter{ opacity:0; } .fade-enter-active{ transition:opacity .5s; } .fade-leave-active{ transition:transform .5s; } .fade-leave-to{ transform:translateX(10px); } </style> <div id="demo"> <button v-on:click="show = !show">Toggle</button> <transition name="fade"> <p v-if="show">小火柴的蓝色理想</p> </transition> </div> <script> new Vue({ el: '#demo', data: { show: true } }) </script>
- animation:CSS动画animation用法同CSS过渡transition,区别是在动画中
v-enter
类名在节点插入 DOM 后不会立即删除,而是在animationend
事件触发时删除<style> .bounce-enter-active{ animation:bounce-in .5s; } .bounce-leave-active{ animation:bounce-in .5s reverse; } @keyframes bounce-in{ 0%{transform:scale(0);} 50%{transform:scale(1.5);} 100%{transform:scale(1);} } </style> <div id="demo"> <button v-on:click="show = !show">Toggle</button> <transition name="bounce"> <p v-if="show">小火柴的蓝色理想</p> </transition> </div> <script> new Vue({ el: '#demo', data: { show: true } }) </script>
- 同时出现:Vue 为了知道过渡的完成,必须设置相应的事件监听器。它可以是
transitionend
或animationend
,这取决于给元素应用的 CSS 规则。如果使用其中任何一种,Vue 能自动识别类型并设置监听。但是,在一些场景中,需要给同一个元素同时设置两种过渡动效,比如animation
很快的被触发并完成了,而transition
效果还没结束。在这种情况中,就需要使用type
特性并设置animation
或transition
来明确声明需要 Vue 监听的类型<style> .fade-enter,.fade-leave-to{ opacity:0; } .fade-enter-active,.fade-leave-active{ transition:opacity 1s; animation:bounce-in 5s; } @keyframes bounce-in{ 0%{transform:scale(0);} 50%{transform:scale(1.5);} 100%{transform:scale(1);} } </style> <div id="demo"> <button v-on:click="show = !show">Toggle</button> <transition name="fade" type="transition"> <p v-if="show">小火柴的蓝色理想</p> </transition> </div> <script> new Vue({ el: '#demo', data: { show: true, }, }) </script>
- 自定义类名:
enter-class enter-active-class enter-to-class leave-class leave-active-class leave-to-class
自定义类名的优先级高于普通的类名,这对于Vue的过渡系统和其他第三方CSS动画库,如 Animate.css 结合使用十分有用。
<link rel="stylesheet" href="https://unpkg.com/animate.css@3.5.2/animate.min.css"> <div id="example"> <button @click="show = !show"> Toggle render </button> <transition name="xxx" enter-active-class="animated tada" leave-active-class="animated bounceOutRight"> <p v-if="show">小火柴的蓝色理想</p> </transition> </div> <script src="https://unpkg.com/vue"></script> <script> new Vue({ el: '#example', data: { show: true } }) </script>
-
初始渲染过渡:可以通过appear特性设置节点在初始渲染的过渡
<transition appear> <!-- ... --> </transition> //这里默认和进入和离开过渡一样,同样也可以自定义 CSS 类名 <transition appear appear-class="custom-appear-class" appear-to-class="custom-appear-to-class" appear-active-class="custom-appear-active-class" > <!-- ... --> </transition>
例子:
<style> .custom-appear-class{ opacity:0; background-color:pink; transform:translateX(100px); } .custom-appear-active-class{ transition: 2s; } </style> <div id="demo"> <button @click="reset">还原</button> <transition appear appear-class="custom-appear-class" appear-to-class="custom-appear-to-class" appear-active-class="custom-appear-active-class"> <p>小火柴的蓝色理想</p> </transition> </div> <script> new Vue({ el: '#demo', methods:{ reset(){ history.go(); } } }) </script>
-
过渡时间:在很多情况下,Vue可以自动得出过渡效果的完成时机。默认情况下,Vue会等待其在过渡效果的根元素的第一个
transitionend
或animationend
事件。然而也可以不这样设定——比如,可以拥有一个精心编排的一序列过渡效果,其中一些嵌套的内部元素相比于过渡效果的根元素有延迟的或更长的过渡效果。在这种情况下可以用<transition>
组件上的duration
属性定制一个显性的过渡效果持续时间 (以毫秒计)下面的代码意味着元素在进入enter和离开leave时,持续时间都为1s,而无论在样式中它们的设置值为多少
<transition :duration="1000">...</transition> // 也可以分别定制进入和移出的持续时间 <transition :duration="{ enter: 500, leave: 800 }">...</transition>
比如,下面的代码中,进入和移出的效果都为animate.css里面的shake效果,但持续时间分别是0.5s和1s
<div id="demo"> <button v-on:click="show = !show">Toggle</button> <transition :duration="{ enter: 500, leave: 1000 }" name="xxx" enter-active-class="animated shake" leave-active-class="animated shake"> <p v-if="show">小火柴的蓝色理想</p> </transition> </div> <script> new Vue({ el: '#demo', data: { show: true } }) </script>
-
过渡条件:一般地,在Vue中满足下列任意一个过渡条件,即可添加过渡效果
【条件渲染(使用v-if)】
常见的条件是使用条件渲染,使用v-if
<style> .fade-enter,.fade-leave-to{ opacity:0; } .fade-enter-active,.fade-leave-active{ transition:opacity 1s; } </style> <style> .fade-enter,.fade-leave-to{ opacity:0; } .fade-enter-active,.fade-leave-active{ transition:opacity 1s; } </style> <script> new Vue({ el: '#demo', data: { show: true } }) </script>
【条件展示(使用v-show)】
使用条件展示,即使用v-show时,也可以添加过渡效果
<div id="demo"> <button v-on:click="show = !show">Toggle</button> <transition name="fade"> <p v-show="show">小火柴的蓝色理想</p> </transition> </div>
【动态组件】
使用is属性实现的动态组件,可以添加过渡效果
<div id="demo"> <button v-on:click="show = !show">Toggle</button> <transition name="fade"> <component :is="view"></component> </transition> </div> <script> new Vue({ el: '#demo', components:{ 'home':{template:'<div>小火柴的蓝色理想</div>'} }, data: { show: true, }, computed:{ view(){ return this.show ? 'home' : ''; } } }) </script>
18.vue过渡之JS过渡
- 前言:与CSS过渡不同,JS过渡主要通过事件进行触发。
- 事件钩子:JS过渡主要通过事件监听事件钩子来触发过渡,共包括如下的事件钩子
<transition v-on:before-enter="beforeEnter" v-on:enter="enter" v-on:after-enter="afterEnter" v-on:enter-cancelled="enterCancelled" v-on:before-leave="beforeLeave" v-on:leave="leave" v-on:after-leave="afterLeave" v-on:leave-cancelled="leaveCancelled" > <!-- ... --> </transition>
下面各个方法中,函数中的参数el表示要过渡的元素,可以设置不同情况下,el的位置、颜色等来控制其动画的改变
// ... methods: { // -------- // 进入中 // -------- beforeEnter: function (el) { // ... }, // 此回调函数是可选项的设置 // 与 CSS 结合时使用 enter: function (el, done) { // ... done() }, afterEnter: function (el) { // ... }, enterCancelled: function (el) { // ... }, // -------- // 离开时 // -------- beforeLeave: function (el) { // ... }, // 此回调函数是可选项的设置 // 与 CSS 结合时使用 leave: function (el, done) { // ... done() }, afterLeave: function (el) { // ... }, // leaveCancelled 只用于 v-show 中 leaveCancelled: function (el) { // ... } }
上面方法中,有两个方法比较特殊,是enter()和leave()方法,它们接受了第二个参数done。当进入完毕或离开完毕后,会调用done()方法来进行接下来的操作
[注意]对于仅使用JS过渡的元素添加
v-bind:css="false"
,Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响<div id="demo"> <button @click="show = !show">Toggle</button> <transition v-on:before-enter="beforeEnter" v-on:enter="enter" v-on:leave="leave" :css="false"> <p v-if="show">Demo</p> </transition> </div> <script> new Vue({ el: '#demo', data: { show: false }, methods: { beforeEnter: function (el) { el.style.opacity = 0 el.style.transformOrigin = 'left' }, enter: function (el, done) { Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 }) Velocity(el, { fontSize: '1em' }, { complete: done }) }, leave: function (el, done) { Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 }) Velocity(el, { rotateZ: '100deg' }, { loop: 2 }) Velocity(el, {rotateZ: '45deg',translateY: '30px',translateX: '30px',opacity: 0}, {complete: done }) } } }) </script>
- 初始过渡渲染:可以通过
appear
特性设置节点的在初始渲染的过渡,自定义 JavaScript 钩子<transition appear v-on:before-appear="customBeforeAppearHook" v-on:appear="customAppearHook" v-on:after-appear="customAfterAppearHook" v-on:appear-cancelled="customAppearCancelledHook" > <!-- ... --> </transition>
<div id="demo"> <button @click="reset">还原</button> <transition appear :appear="customAppearHook"> <p>小火柴的蓝色理想</p> </transition> </div> <script> new Vue({ el: '#demo', methods:{ reset(){ history.go(); }, customAppearHook(el, done) { Velocity(el, {backgroundColor:"#ddd",translateX:200}); Velocity(el,"reverse",{complete:done}) }, } }) </script>
19.vue过渡之多元素过渡
- 常见示例:最常见的多标签过渡是一个列表和描述列表为空消息的元素:
<transition> <table v-if="items.length > 0"> <!-- ... --> </table> <p v-else>Sorry, no items found.</p> </transition>
<style> .fade-enter,.fade-leave-to{opacity:0;} .fade-enter-active,.fade-leave-active{transition:opacity .5s;} </style> <div id="demo"> <button @click="clear">清空数据</button> <button @click="reset">重置</button> <transition name="fade"> <ul v-if="items.length > 0"> <li v-for="item in items">{{item}}</li> </ul> <p v-else>Sorry, no items found.</p> </transition> </div> <script> new Vue({ el: '#demo', data: { items: ['html','css','js'] }, methods:{ clear(){ this.items.splice(0); }, reset(){ history.go(); } } }) </script>
-
同标签名称:如果是相同标签名的元素切换时,Vue 为了效率只会替换相同标签内部的内容,两个相同的p元素切换时,无过渡效果
<style> .fade-enter,.fade-leave-to{opacity:0;} .fade-enter-active,.fade-leave-active{transition:opacity .5s;} </style> <div id="demo"> <button @click="show = !show">toggle</button> <transition name="fade"> <p v-if="show">我是小火柴</p> <p v-else>我不是小火柴</p> </transition> </div> <script> new Vue({ el: '#demo', data: { show:true }, }) </script>
因此,对于具有相同标签名的元素切换的情况,需要通过
key
特性设置唯一的值来标记以让 Vue 区分它们<div id="demo"> <button @click="show = !show">toggle</button> <transition name="fade"> <p v-if="show" key="trueMatch">我是小火柴</p> <p v-else key="falseMatch">我不是小火柴</p> </transition> </div>
-
替代if:在一些场景中,可以给通过给同一个元素的
key
特性设置不同的状态来代替v-if
和v-else
<transition> <button v-if="isEditing" key="save">Save</button> <button v-else key="edit">Edit</button> </transition> //上边的例子可以重写为: <transition> <button v-bind:key="isEditing"> {{ isEditing ? 'Save' : 'Edit' }} </button> </transition>
<style> .fade-enter,.fade-leave-to{opacity:0;} .fade-enter-active,.fade-leave-active{transition:opacity .5s;} </style> <div id="demo"> <button @click="isEditing = !isEditing">toggle</button> <transition name="fade"> <p v-bind:key="isEditing"> {{ isEditing ? 'Save' : 'Edit' }} </p> </transition> </div> <script> new Vue({ el: '#demo', data: { isEditing:true }, }) </script>
使用多个
v-if
的多个元素的过渡可以重写为绑定了动态属性的单个元素过渡:<transition> <button v-if="docState === 'saved'" key="saved">Edit</button> <button v-if="docState === 'edited'" key="edited">Save</button> <button v-if="docState === 'editing'" key="editing">Cancel</button> </transition> //重写为 <transition> <button v-bind:key="docState">{{ buttonMessage }}</button> </transition> computed: { buttonMessage: function () { switch (this.docState) { case 'saved': return 'Edit' case 'edited': return 'Save' case 'editing': return 'Cancel' } } }
<style> .fade-enter,.fade-leave-to{opacity:0;} .fade-enter-active,.fade-leave-active{transition:opacity .5s;} </style> <div id="demo"> <button @click="change">change</button> <transition name="fade"> <p v-bind:key="docState">{{ message }}</p> </transition> </div> <script> new Vue({ el: '#demo', data: { index:0, isEditing:true, arr:['saved','edited','editing'] }, computed: { docState(){ return this.arr[this.index]; }, message() { switch (this.docState) { case 'saved': return 'Edit' case 'edited': return 'Save' case 'editing': return 'Cancel' } } }, methods:{ change(){ this.index = (++this.index)%3; } } }) </script>
-
过渡模式: 初始例子
<style> .fade-enter,.fade-leave-to{opacity:0;} .fade-enter-active,.fade-leave-active{transition:opacity .5s;} </style> <div id="demo"> <transition name="fade"> <button :key="isOn" @click="isOn = !isOn">{{ isOn ? 'On' : 'Off' }}</button> </transition> </div> <script> new Vue({ el: '#demo', data: { isOn: true }, }) </script>
在 “on” 按钮和 “off” 按钮的过渡中,两个按钮都被重绘了,一个离开过渡的时候另一个开始进入过渡。这是
<transition>
的默认行为 - 进入和离开同时发生,同时生效的进入和离开的过渡不能满足所有要求,所以 Vue 提供了过渡模式in-out: 新元素先进行过渡,完成之后当前元素过渡离开。 out-in: 当前元素先进行过渡,完成之后新元素过渡进入。
【in-out】下面使用in-out来重写之前的开关按钮过渡
<div id="demo"> <transition name="fade" mode="in-out"> <button :key="isOn" @click="isOn = !isOn">{{ isOn ? 'On' : 'Off' }}</button> </transition> </div>
【out-in】下面使用out-in来重写之前的开关按钮过渡
<div id="demo"> <transition name="fade" mode="out-in"> <button :key="isOn" @click="isOn = !isOn">{{ isOn ? 'On' : 'Off' }}</button> </transition> </div>
-
滑动过渡:当元素设置为绝对定位,并互相覆盖,实现透明度过渡效果
<style> #demo{position:relative;} #demo button{position:absolute;left:40px;} .fade-enter,.fade-leave-to{opacity:0;} .fade-enter-active,.fade-leave-active{transition: 1s;} </style> <div id="demo"> <transition name="fade" > <button :key="isOn" @click="isOn = !isOn">{{ isOn ? 'On' : 'Off' }}</button> </transition> </div> <script> new Vue({ el: '#demo', data: { isOn: true }, }) </script>
下面是一个使用absolute和translate实现的类似滑动
<style> #demo{position:relative;} #demo button{position:absolute;left:40px;} .fade-enter,.fade-leave-to{opacity:0;} .fade-enter{transform:translateX(30px);} .fade-leave-to{transform:translateX(-30px);} .fade-enter-active,.fade-leave-active{transition: 1s;} </style>
如果设置in-out模式,将实现更酷的滑动效果
<style> #demo{position:relative;} #demo button{position:absolute;left:40px;} .fade-enter,.fade-leave-to{opacity:0;} .fade-enter{transform:translateX(30px);} .fade-leave-to{transform:translateX(-30px);} .fade-enter-active,.fade-leave-active{transition: 1s;} </style> <div id="demo"> <transition name="fade" mode="in-out"> <button :key="isOn" @click="isOn = !isOn">{{ isOn ? 'On' : 'Off' }}</button> </transition> </div> <script> new Vue({ el: '#demo', data: { isOn: true }, }) </script>
-
动态组件:多个组件的过渡简单很多,不需要使用
key
特性。相反,只需要使用动态组件<style> .fade-enter,.fade-leave-to{opacity:0;} .fade-enter-active,.fade-leave-active{transition: .5s;} </style> <div id="example"> <button @click="change">切换页面</button> <transition name="fade" mode="out-in"> <component :is="currentView"></component> </transition> </div> <script> new Vue({ el: '#example', data:{ index:0, arr:[ {template:`<div>ComponentA</div>`}, {template:`<div>ComponentB</div>`}, {template:`<div>ComponentC</div>`} ], }, computed:{ currentView(){ return this.arr[this.index]; } }, methods:{ change(){ this.index = (++this.index)%3; } } }) </script>
20.vue过渡之列表过渡
- 概述:同时渲染整个列表,需要使用<transition-group>组件
【<transition-group>】
<transition-group>不同于
<transition>
, 它会以一个真实元素呈现:默认为一个<span>
。也可以通过tag
特性更换为其他元素。而且其内部元素总是需要提供唯一的key
属性值<transition-group name="list" tag="p"> <!-- ... --> </transition-group>
- 普通过渡:
<style> .list-item {display: inline-block;margin-right: 10px;} .list-enter-active, .list-leave-active {transition: all 1s;} .list-enter, .list-leave-to{opacity: 0;transform: translateY(30px);} </style> <div id="list-demo" class="demo"> <button @click="add">Add</button> <button @click="remove">Remove</button> <transition-group name="list" tag="p"> <span v-for="item in items" :key="item" class="list-item">{{item}}</span> </transition-group> </div> <script> new Vue({ el: '#list-demo', data: { items: [1,2,3,4,5,6,7,8,9], nextNum: 10 }, methods: { randomIndex() { return Math.floor(Math.random() * this.items.length) }, add() { this.items.splice(this.randomIndex(), 0, this.nextNum++) }, remove() { this.items.splice(this.randomIndex(), 1) }, } }) </script>
- 平滑过渡:上面这个例子有个问题,当添加和移除元素的时候,周围的元素会瞬间移动到他们的新布局的位置,而不是平滑的过渡。
【v-move】
<transition-group>
组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的v-move
特性,它会在元素的改变定位的过程中应用。像之前的类名一样,可以通过name
属性来自定义前缀,也可以通过move-class
属性手动设置在上面代码中基础上,做出如下改进:
1、增加.list-move的样式,使元素在进入时实现过渡效果
2、在.list-leave-active中设置绝对定位,使元素在离开时实现过渡效果
<style> .list-item {display: inline-block;margin-right: 10px;} .list-move,.list-enter-active, .list-leave-active {transition: 1s;} .list-leave-active{position:absolute;} .list-enter, .list-leave-to{opacity: 0;transform: translateY(30px);} </style>
- 变换过渡:下面接着利用move属性,进行变换过渡,即一个列表中的列表项既不增加也不减少,只是不断地变换其位置
<style> .list-move{transition: transform 1s;} </style> <div id="list-demo" class="demo"> <button @click="shuffle">shuffle</button> <transition-group name="list" tag="ul"> <li v-for="item in items" :key="item">{{item}}</li> </transition-group> </div> <script> new Vue({ el: '#list-demo', data: { items: [1,2,3,4,5,6,7,8,9], }, methods: { shuffle(){ this.items = this.items.sort(()=>{return Math.random() - 0.5;}) }, } }) </script>
效果看起来很神奇,内部的实现,Vue 使用了一个叫 FLIP 简单的动画队列,使用 transforms 将元素从之前的位置平滑过渡新的位置。
下面将进入离开的例子和这个技术结合, 使列表的一切变动都会有动画过渡
[注意]使用 FLIP 过渡的元素不能设置为
display: inline
。作为替代方案,可以设置为display: inline-block
或者放置于 flex 中<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> .list-item {display: inline-block;margin-right: 10px;transition: 1s;} .list-leave-active{position:absolute;} .list-enter, .list-leave-to{opacity: 0;transform: translateY(30px);} </style> </head> <body> <div id="list-demo" class="demo"> <button @click="shuffle">shuffle</button> <button @click="add">Add</button> <button @click="remove">Remove</button> <transition-group name="list" tag="p"> <span v-for="item in items" :key="item" class="list-item">{{item}}</span> </transition-group> </div> <script type="text/javascript" src="http://sandbox.runjs.cn/uploads/rs/26/ddzmgynp/vue.js"></script> <script> new Vue({ el: '#list-demo', data: { items: [1,2,3,4,5,6,7,8,9], nextNum: 10 }, methods: { randomIndex() { return Math.floor(Math.random() * this.items.length) }, add() { this.items.splice(this.randomIndex(), 0, this.nextNum++) }, remove() { this.items.splice(this.randomIndex(), 1) }, shuffle(){ this.items = this.items.sort(()=>{return Math.random() - 0.5;}) }, } }) </script> </body> </html>
以上代码中,由于move、enter和leave都需要设置transition。因此,直接在元素上设置transition即可
- 多维列表:FLIP 动画不仅可以实现单列过渡,多维网格的过渡也同样简单
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> .container { 270px;margin-top: 10px;line-height:30px;text-align:center;} .cell {display: inline-block; 30px;height: 30px;outline: 1px solid #aaa;} .cell-move {transition:1s;} </style> </head> <body> <div id="list-demo" class="demo"> <button @click="shuffle">shuffle</button> <transition-group name="cell" tag="div" class="container"> <span v-for="cell in cells" :key="cell.id" class="cell">{{ cell.number }}</span> </transition-group> </div> <script type="text/javascript" src="http://sandbox.runjs.cn/uploads/rs/26/ddzmgynp/vue.js"></script> <script> new Vue({ el: '#list-demo', data: { cells: Array.apply(null, { length: 81 }) .map(function (_, index) { return { id: index, number: index % 9 + 1 } }) }, methods: { shuffle(){ this.cells = this.cells.sort(()=>{return Math.random() - 0.5;}) }, } }) </script> </body> </html>
- 渐进过渡:通过 data 属性与 JavaScript 通信 ,就可以实现列表的渐进过渡
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> .list-move,.list-enter-active, .list-leave-active {transition: 1s;} .list-leave-active{position:absolute;} .list-enter,.list-leave-to{opacity: 0;height:0;} </style> </head> <body> <div id="list-demo" class="demo"> <input v-model="query"> <transition-group name="list" tag="ul"> <li v-for="(item, index) in computedList" :key="item" :data-index="index">{{item}}</li> </transition-group> </div> <script type="text/javascript" src="http://sandbox.runjs.cn/uploads/rs/26/ddzmgynp/vue.js"></script> <script> new Vue({ el: '#list-demo', data: { query: '', list: ['HTML','CSS','Javascript','jQuery','Vue'] }, computed: { computedList() { return this.list.filter((item)=>{ return item.toLowerCase().indexOf(this.query.toLowerCase()) !== -1 }) } }, }) </script> </body> </html>
上面的效果中,列表项是一齐运动的。如果要实现依次运动的效果,则需要使用JS过渡来实现
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="list-demo" class="demo"> <input v-model="query"> <transition-group name="list" tag="ul" :css="false" @before-enter="beforeEnter" @enter="enter" @leave="leave"> <li v-for="(item, index) in computedList" :key="item" :data-index="index">{{item}}</li> </transition-group> </div> <script type="text/javascript" src="http://sandbox.runjs.cn/uploads/rs/26/ddzmgynp/velocity.min.js"></script> <script type="text/javascript" src="http://sandbox.runjs.cn/uploads/rs/26/ddzmgynp/vue.js"></script> <script> new Vue({ el: '#list-demo', data: { query: '', list: ['HTML','CSS','Javascript','jQuery','Vue'] }, computed: { computedList() { return this.list.filter((item)=>{ return item.toLowerCase().indexOf(this.query.toLowerCase()) !== -1 }) } }, methods: { beforeEnter(el) { el.style.opacity = el.style.height = 0 }, enter(el, done) { setTimeout(()=>{ Velocity(el,{ opacity: 1, height: '1.6em' },{ complete: done }) }, el.dataset.index * 150) }, leave(el, done) { setTimeout(()=>{ Velocity(el,{ opacity: 0, height: 0 },{ complete: done }) }, el.dataset.index * 150) } }, }) </script> </body> </html>
21.vue过渡之可复用过渡和动态过渡
- 可复用过渡:过渡可以通过 Vue 的组件系统实现复用。要创建一个可复用过渡组件,需要做的就是将
<transition>
或者<transition-group>
作为根组件,然后将任何子组件放置在其中就可以了Vue.component('my-transition', { template: ` <transition name="transition1" mode="out-in" @before-enter="beforeEnter" @after-enter="afterEnter"> <slot></slot> </transition> `, methods: { beforeEnter: function (el) { // ... }, afterEnter: function (el) { // ... } } })
函数组件跟适合完成这个任务:
Vue.component('my-special-transition', { functional: true, render: function (createElement, context) { var data = { props: { name: 'very-special-transition', mode: 'out-in' }, on: { beforeEnter: function (el) { // ... }, afterEnter: function (el) { // ... } } } return createElement('transition', data, context.children) } })
- 动态过渡:在 Vue 中即使是过渡也是数据驱动的!动态过渡最基本的例子是通过
name
特性来绑定动态值<transition v-bind:name="transitionName"> <!-- ... --> </transition>
用 Vue 的过渡系统来定义的 CSS 过渡/动画 在不同过渡间切换会非常有用
所有的过渡特性都是动态绑定。它不仅是简单的特性,通过事件的钩子函数方法,可以在获取到相应上下文数据。这意味着,可以根据组件的状态通过 JavaScript 过渡设置不同的过渡效果
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="dynamic-fade-demo" class="demo"> Fade In: <input type="range" v-model="fadeInDuration" min="0" :max="maxFadeDuration"> Fade Out: <input type="range" v-model="fadeOutDuration" min="0" :max="maxFadeDuration"> <transition :css="false" @before-enter="beforeEnter" @enter="enter" @leave="leave"> <p v-if="show">小火柴的蓝色理想</p> </transition> <button v-if="stop" @click="stop = show = false">运行动画</button> <button v-else @click="stop = true">停止动画</button> </div> <script type="text/javascript" src="velocity.min.js"></script> <script type="text/javascript" src="vue.js"></script> <script> new Vue({ el: '#dynamic-fade-demo', data: { show: true, fadeInDuration: 1000, fadeOutDuration: 1000, maxFadeDuration: 1500, stop: true }, mounted() { this.show = false }, methods: { beforeEnter(el) { el.style.opacity = 0 }, enter(el, done) { Velocity(el,{ opacity: 1 },{duration: this.fadeInDuration,complete:()=>{ done(); if (!this.stop){ this.show = false; } } }) }, leave(el, done) { Velocity(el,{ opacity: 0 },{duration: this.fadeOutDuration,complete:()=>{ done(); this.show = true; } }) }, }, }) </script> </body> </html>
22.vue过渡之过渡状态
- 概述:Vue 的过渡系统提供了非常多简单的方法设置进入、离开和列表的动效。那么对于数据元素本身的动效呢?包括数字和运算、颜色的显示、SVG 节点的位置、元素的大小和其他的属性等。所有的原始数字都被事先存储起来,可以直接转换到数字。做到这一步,我们就可以结合 Vue 的响应式和组件系统,使用第三方库来实现切换元素的过渡状态
- 状态动画:通过watcher,能监听到任何数值属性的数值更新
<div id="animated-number-demo"> <input v-model.number="number" type="number" step="20"> <p>{{ animatedNumber }}</p> </div> <script src="Tween.js"></script> <script src="vue.js"></script> <script> new Vue({ el: '#animated-number-demo', data: { number: 0, animatedNumber: 0 }, watch: { number: function(newValue, oldValue) { var vm = this; function animate () { if (TWEEN.update()) { requestAnimationFrame(animate) } } new TWEEN.Tween({ tweeningNumber: oldValue }) .easing(TWEEN.Easing.Quadratic.Out) .to({ tweeningNumber: newValue }, 500) .onUpdate(function () { vm.animatedNumber = this.tweeningNumber.toFixed(0) }) .start(); animate() } } }) </script>
当把数值更新时,就会触发动画。这个是一个不错的演示,但是对于不能直接像数字一样存储的值,比如 CSS 中的 color 的值,通过下面的例子来通过 Color.js 实现一个例子:
<div id="example"> <input v-model="colorQuery" @keyup.enter="updateColor" placeholder="Enter a color"> <button @click="updateColor">Update</button> <p>Preview:</p> <span :style="{ backgroundColor: tweenedCSSColor }" style="display: inline-block; 50px;height: 50px;"></span> <p>{{ tweenedCSSColor }}</p> </div> <script src="Tween.js"></script> <script src="vue.js"></script> <script src="color.js"></script> <script> var Color = net.brehaut.Color new Vue({ el: '#example', data: { colorQuery: '', color: { red: 0, green: 0, blue: 0, alpha: 1 }, tweenedColor: {} }, created: function () { this.tweenedColor = Object.assign({}, this.color) }, watch: { color: function () { function animate () { if (TWEEN.update()) { requestAnimationFrame(animate) } } new TWEEN.Tween(this.tweenedColor) .to(this.color, 750) .start() animate() } }, computed: { tweenedCSSColor: function () { return new Color({ red: this.tweenedColor.red, green: this.tweenedColor.green, blue: this.tweenedColor.blue, alpha: this.tweenedColor.alpha }).toCSS() } }, methods: { updateColor: function () { this.color = new Color(this.colorQuery).toRGB() this.colorQuery = '' } } }) </script>
- 动态状态转换:就像 Vue 的过渡组件一样,数据背后状态转换会实时更新,这对于原型设计十分有用。当修改一些变量,即使是一个简单的 SVG 多边形也可以实现很多难以想象的效果
<style> svg,input[type="range"]{display:block;} </style> <div id="app"> <svg width="200" height="200"> <polygon :points="points" fill="#41B883"></polygon> <circle cx="100" cy="100" r="90" fill=" transparent" stroke="#35495E"></circle> </svg> <label>Sides: {{ sides }}</label> <input type="range" min="3" max="500" v-model.number="sides"> <label>Minimum Radius: {{ minRadius }}%</label> <input type="range" min="0" max="90" v-model.number="minRadius"> <label>Update Interval: {{ updateInterval }} milliseconds</label> <input type="range" min="10" max="2000" v-model.number="updateInterval"> </div> <script type="text/javascript" src="vue.js"></script> <script src="TweenLite.min.js"></script> <script> new Vue({ el: '#app', data: function () { //默认有10条边 var defaultSides = 10; //默认地,stats = [100, 100, 100, 100, 100, 100, 100, 100, 100, 100] var stats = Array.apply(null, { length: defaultSides }) .map(function () { return 100 }) return { stats: stats, points: generatePoints(stats), sides: defaultSides, minRadius: 50, interval: null, updateInterval: 500 } }, watch: { sides: function (newSides, oldSides) { //计算设置的边数与默认的边数的差值 var sidesDifference = newSides - oldSides //如果大于默认边数 if (sidesDifference > 0) { //增加相应数量的随机值到stats数组中 for (var i = 1; i <= sidesDifference; i++) { this.stats.push(this.newRandomValue()) } }else{ //否则,计算出差值 var absoluteSidesDifference = Math.abs(sidesDifference) //从stats数组末尾减少相应数量的数组值 for (var i = 1; i <= absoluteSidesDifference; i++) { this.stats.shift() } } }, stats: function (newStats) { TweenLite.to( this.$data, this.updateInterval / 1000, { points: generatePoints(newStats) } ) }, updateInterval: function () { this.resetInterval() } }, mounted: function () { this.resetInterval() }, methods: { //将stats里面的值都变成50-100的随机值 randomizeStats: function () { var vm = this this.stats = this.stats.map(function () { return vm.newRandomValue() }) }, newRandomValue: function () { //产生一个50-100的随机半径 return Math.ceil(this.minRadius + Math.random() * (100 - this.minRadius)) }, //重启定时器 resetInterval: function () { var vm = this; clearInterval(this.interval); this.randomizeStats(); this.interval = setInterval(function () { vm.randomizeStats(); }, this.updateInterval) } } }) function valueToPoint (value, index, total) { var x = 0 var y = -value * 0.9 var angle = Math.PI * 2 / total * index var cos = Math.cos(angle) var sin = Math.sin(angle) var tx = x * cos - y * sin + 100 var ty = x * sin + y * cos + 100 return { x: tx, y: ty } } //计算polygon中的路径点的值 function generatePoints (stats) { var total = stats.length return stats.map(function (stat, index) { var point = valueToPoint(stat, index, total) return point.x + ',' + point.y }).join(' ') } </script>
- 组件组织过渡:管理太多的状态转换会很快的增加 Vue 实例或者组件的复杂性,幸好很多的动画可以提取到专用的子组件
<div id="example"> <input v-model.number="firstNumber" type="number" step="20"> + <input v-model.number="secondNumber" type="number" step="20"> = {{ result }} <p> <animated-integer :value="firstNumber"></animated-integer> + <animated-integer :value="secondNumber"></animated-integer> = <animated-integer :value="result"></animated-integer> </p> </div> <script type="text/javascript" src="vue.js"></script> <script type="text/javascript" src="Tween.js"></script> <script> Vue.component('animated-integer', { template: '<span>{{ tweeningValue }}</span>', props: { value: { type: Number, required: true } }, data: function () { return { tweeningValue: 0 } }, watch: { value: function (newValue, oldValue) { this.tween(oldValue, newValue) } }, mounted: function () { this.tween(0, this.value) }, methods: { tween: function (startValue, endValue) { var vm = this; function animate () { if (TWEEN.update()) { requestAnimationFrame(animate) } } new TWEEN.Tween({ tweeningValue: startValue }) .to({ tweeningValue: endValue }, 500) .onUpdate(function () { vm.tweeningValue = this.tweeningValue.toFixed(0) }) .start() animate() } } }) new Vue({ el: '#example', data: { firstNumber: 20, secondNumber: 40 }, computed: { result: function () { return this.firstNumber + this.secondNumber } } }) </script>
23.vue插件之plugins的基本操作
- 开发插件:插件通常会为 Vue 添加全局功能。插件的范围没有限制——一般有下面几种:
1>、添加全局方法或者属性,如: vue-custom-element
2>、添加全局资源:指令/过滤器/过渡等,如 vue-touch
3>、通过全局 mixin 方法添加一些组件选项,如: vue-router
4>、添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现
5>、一个库,提供自己的 API,同时提供上面提到的一个或多个功能,如 vue-router
Vue.js 的插件应当有一个公开方法
install
。这个方法的第一个参数是Vue
构造器,第二个参数是一个可选的选项对象:MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或属性 Vue.myGlobalMethod = function () { // 逻辑... } // 2. 添加全局资源 Vue.directive('my-directive', { bind (el, binding, vnode, oldVnode) { // 逻辑... } ... }) // 3. 注入组件 Vue.mixin({ created: function () { // 逻辑... } ... }) // 4. 添加实例方法 Vue.prototype.$myMethod = function (methodOptions) { // 逻辑... } }
- 使用插件:通过全局方法 Vue.use() 使用插件:
// 调用 `MyPlugin.install(Vue)` Vue.use(MyPlugin) //也可以传入一个选项对象: Vue.use(MyPlugin, { someOption: true })
Vue.use
会自动阻止注册相同插件多次,届时只会注册一次该插件Vue.js 官方提供的一些插件 (例如
vue-router
) 在检测到Vue
是可访问的全局变量时会自动调用Vue.use()
。然而在例如 CommonJS 的模块环境中,应该始终显式地调用Vue.use()
:// 用 Browserify 或 webpack 提供的 CommonJS 模块环境时 var Vue = require('vue') var VueRouter = require('vue-router') // 不要忘了调用此方法 Vue.use(VueRouter)
awesome-vue 集合了来自社区贡献的数以千计的插件和库
24.vue插件之vue-rounter
- 介绍:在Web开发中,路由是指根据URL分配到对应的处理程序。对于大多数单页面应用,都推荐使用官方支持的vue-router。Vue-router通过管理URL,实现URL和组件的对应,以及通过URL进行组件之间的切换。
- 安装:在使用vue-router之前,首先需要安装该插件
npm install vue-router