10

第1章--Vue.js简介

一、有关于Vue.js特性的例子

1.第一个特性是数据绑定。绑定用户输入的数据,视图会随着用户的输入而变化。

<div id="app">
    <h1>Your input is {{message}}</h1>
    <input type="text" v-model="message">
</div>

2.第二个特性是组件化,简单来说我们可以自己定义HTML标签,并在模板中使用它。

<div id="app">
    <message content="Hello World"></message>
</div>
<script>
    var Message = Vue.extend({
        props: ['content'],
        template: '<h1>{{content}}</h1>'
    })
    Vue.component('message', Message);
    new Vue({
        el: '#app',
    })
</script>
HTML结果为:
    <div id="app">
        <h1>Hello World</h1>
    </div>

第2章--基础特性

一、数据

Vue.js实例中可以通过data属性定义数据,如果传入data的是一个对象,Vue实例会代理起data对象里的所有属性,而不会对传入的对象进行深拷贝。另外,我们可以引用Vue实例vm中的$data来获取声明的数据,例如:

var data = {
    a: 1
};
var vm = new Vue({
    data: data
});
console.log(vm.$data === data); // true
console.log(vm.$data.a === data.a); // true
console.log(vm.a === data.a); // true
// 设置属性也会影响到原始数据
vm.a = 2;
console.log(data.a);    // 2
// 反之亦然
data.a = 3;
console.log(vm.a);  // 3

在模板中使用{{a}}就会输出vm.a的值,并且修改vm.a的值,模板中的值会随之改变。我们称这个数据为响应式数据。
注意:只有初始化时传入的对象才是响应式的,即在生命完实例后,再加上vm.$data.b = '2',并在模板中使用{{b}},是不会输出字符串'2'的。例如:

<div id="app">
    <p>{{a}}</p>
    <p>{{b}}</p>
</div>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            a: 1
        }
    });
    vm.$data.b = 2; // 报错
</script>

如果需要在实例化之后加入响应式变量,需要调用实例方法$set,例如:vm.$set('b',2);不过Vue.js并不推荐这么做,会抛出异常。所以我们应尽量在初始化的时候,把所有的变量都设定好,如果没有值,可以用undefined或null占位。
另外,组件类型的实例也可以通过props获取数据,同data一样,也需要在初始化时预设好。例如:

<div id="app">
    <my-component title="myTitle" content="myContent"></my-component>
</div>
<script>
    var MyComponent = Vue.component('my-component', {
        props: ['title', 'content'],
        template: '<h1>{{title}}</h1><p>{{content}}</p>'
    });
</script>
也可以在上述组件类型实例中同时使用data,但需注意:
1.data的值必须是一个函数,并且返回值不是原始对象。如果传给组件的data是一个原始对象,则在建议多个组件实例时它们就会共用这个data对象,修改其中一个组件实例的数据就会影响到其他组件实例的数据,这显然不是我们所期望的。
2.data中的属性和props中的不能重名。
这两者均会抛出异常。
正确使用方法:
var MyComponent = Vue.component('my-component', {
        props: ['title', 'content'],
        data: function () {
            return {
                desc: '描述'
            }
        },
        template: `<div>
                        <h1>{{title}}</h1>
                        <p>{{content}}</p>
                        <p>{{desc}}</p>
                    </div>`
    });

二、生命周期
通过一个简单的demo来清楚地了解内部的运行机制,代码如下:

var vm = new Vue({
    el: '#app',
    init: function () {
        // 在实例开始初始化时同步调用。此时数据观测、事件等都尚未初始化。2.0中更名为beforeCreate
        console.log('init');
    },
    created: function () {
        // 在实例创建之后调用。此时已完成数据绑定、事件方法,但尚未开始DOM编译,即未挂载到document中。
        console.log('created');
    },
    beforeCompile: function () {
        // 在DOM编译前调用。2.0废弃了该方法,推荐使用created。
        console.log('beforeCompile');
    },
    beforeMount: function () {
        // 2.0新增的生命周期钩子,在mounted之前运行。
        console.log('beforeMount');
    },
    compiled: function () {
        // 在编译结束时调用。此时所有指令已生效,数据变化已能触发DOM更新,但不保证$el已插入文档。2.0中更名为mounted。
        console.log('compiled');
    },
    mounted: function () {
        // 2.0中compiled更名。
        console.log('mounted');
    },
    attached: function () {
        // 在vm.$el插入DOM时调用,ready会在第一次attached后调用。操作$el必须使用指令或实例方法(例如$appendTo()),直接操作$el不会触发这个钩子。2.0废弃了该方法,推荐在其它钩子中自定义方法检查是否已挂载。
        console.log('attached');
    },
    dettached: function () {
        // 同attached类似,该钩子在vm.$el从DOM删除时调用,而且必须是指令或实例方法。2.0中同样废弃了该方法。
        console.log('dettached');
    },
    beforeDestroy: function () {
        // 在开始销毁实例时调用,此时实例仍然有效。
        console.log('beforeDestory');
    },
    destroyed: function () {
        // 在实例被销毁之后调用。此时所有绑定和实例指令都已经解绑,子实例也被销毁。
        console.log('destroyed');
    },
    ready: function () {
        // 在编译结束和$el第一次插入文档之后调用。2.0废弃了该方法,推荐使用mounted。这个变化其实已经改变了ready这个生命周期状态,相当于取消了在$el首次插入文档后的钩子函数。
        console.log('ready');
        // 组件完成后调用$destroy()函数,进行销毁
        this.$destroy();
    },
    beforeUpdate: function () {
        // 2.0新增的生命周期钩子,在实例挂载之后,再次更新实例(例如更新data)时会调用该方法,此时尚未更新DOM结构。
        console.log('beforeUpdate');
    },
    update: function () {
        // 2.0新增的生命周期钩子,在实例挂载之后,再次更新实例并更新完DOM结构后调用。
        console.log('update');
    },
    activated: function () {
        // 2.0新增的生命周期钩子,需要配合动态组件keep-live属性使用。在动态组件初始化渲染的过程中调用该方法。
        console.log('activated');
    },
    deactivated: function () {
        // 2.0新增的生命周期钩子,需要配合动态组件keep-live属性使用。在动态组件移出的过程中调用该方法。
        console.log('deactivated');
    }
});

三、数据绑定
Vue.js作为数据驱动视图的框架,核心是一个响应式的数据绑定系统,建立绑定后,DOM将和数据保持同步,这样就无需手动维护DOM,使代码能给更加简洁易懂、提升效率。
数据绑定语法:
js代码:

var vm = new Vue({
    el: '#app',
    data: {
        id: 1,
        index: 0,
        name: 'Vue',
        avatar: 'http://...',
        count: [1, 2, 3, 4, 5],
        names: ['Vue1.0', 'Vue2.0'],
        items: [
            {name: 'Vue1.0', version: '1.0'},
            {name: 'Vue2.0', version: '2.0'}
        ]
    }
});

1.文本插值

<span>Hello {{name}}</span>    // Hello Vue

修改数据对象中的name属性,DOM也会随之更新。// console中修改name属性为Vue2.0后输出 Hello Vue2.0
模板语法同时也支持单次插值,即首次赋值后再更改vm实例属性值不会引起DOM变化,例如:

<span>Hello {{* name}}</span>

Vue.js 2.0去除了{{*}}这种写法,采用v-once代替。

<span>Hello {{* name}}</span>    // console中修改name属性后依然输出 Hello Vue

2.HTML属性

Mustache标签也同样适用于HTML属性中,例如:

<div id="id-{{id}}"></div>    // <div id="id-1"></div>

Vue.js 2.0废弃了这种写法,用v-bind指令代替。

<div v-bind:id="'id-' + id"></div>    // <div id="id-1"></div>
或简写为:
<div :id="'id-' + id"></div>    // <div id="id-1"></div>

3.绑定表达式

放在Mustache标签内的文本内容称为绑定表达式。除了直接输出属性值之外,一段绑定表达式可以由一个简单的JavaScript表达式和可选的一个或多个过滤器构成。例如:

<div>{{index + 1}}</div>    // <div>1</div>
<div>{{index == 0 ? 'a' : 'b'}}</div>    // <div>a</div>    
<div>{{name.split('').join('|')}}</div>    // <div>V|u|e</div>

每个绑定中只能包含单个表达式,并不支持JavaScript语句,否则Vue.js就会抛出warning异常。并且绑定表达式里不支持正则表达式,如果需要进行复杂的转换,可以使用过滤器或者计算属性来进行处理。

4.过滤器

Vue.js允许在表达式后添加可选的过滤器,以管道符""指示。示例:

<div>{{name | uppercase}}</div>    // VUE

Vue.js将name的值传入给uppercase这个过滤器中(本质是一个函数),返回字符串中的大写值。同时也允许多个过滤器链式使用。例如:

<div>{{name | filterA | filterB}}</div>    // VUE

也允许入多个参数,例如:

<div>{{name | filterA arg1 arg2}}</div>    // VUE

注意:Vue.js 2.0中已经去除了内置的过滤器,不过可以声明自定义过滤器。

5.指令

修饰符:修饰符(Modifiers)是以半角句号.开始的特殊后缀,用于表示指令应该以特殊方式绑定。

<button v-on:click.stop="doClick"></button>

v-on的作用是在对应的DOM元素上绑定事件监听器,doClick为函数名,而stop即为修饰符,作用是停止冒泡,相当于调用了e.stopPropagation()。

二、计算属性

在项目开发中,展示的数据往往需要经过一些处理。除了在模板中绑定表达式或者利用过滤器外,Vue.js还提供了计算属性方法。

1.基础例子

<div id="app">
    <div>{{firstName}}</div> 
    <div>{{lastName}}</div> 
    <div>{{fullName}}</div>
</div>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            firstName: 'Gavin',
            lastName: 'CLY'
        },
        computed: {
            fullName: function () {
                return this.firstName + ' ' + this.lastName
            }
        }
    });
</script>
输出结果:
Gavin
CLY
Gavin CLY

对vm.firstName和vm.lastName进行修改,始终会影响vm.fulllName。

2.Setter
计算属性的Setter方法,在更新属性时带来了便利。例如:

<div id="app">
    <div>&yen;{{price}}</div> 
</div>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            cents: 100
        },
        computed: {
            price: {
                set: function (newValue) {
                    this.cents = newValue * 100;
                },
                get: function () {
                    return (this.cents / 100).toFixed(2);
                }
            }
        }
    });
</script>
输出结果:¥1.00

在处理商品价格的时候,后端往往会把价钱定义成以分为单位的整型,避免在处理浮点类型数据时产生的问题。而前端则需要把价钱再转成元进行展示,如果需要对价钱进行修改,要把输入的价格再恢复到分传给后端,很繁琐。使用Vue.js的计算属性,可以将vm.cents设置为后端所存的数据,计算属性price为前端展示和更新的数据。
此时更改vm.price=2,vm.cents会被更新为200,在传递给后端时无需再手动转化一遍数据。

三、表单控件

Vue.js中提供v-model指令对表单元素进行双向数据绑定。
js代码:

var vm = new Vue({
    el: '#app',
    data: {
        message: '',
        gender: '',
        checked: '',
        multiChecked: [],
        selected: '',
        multiSelected: [],
        a: 'checked',
        b: 'checked'
    }
});

1.Text 输入框示例,用户输入的内容和vm.message直接绑定:

<input type="text" v-model="message"> 
<div>Your input is : {{message}}</div>

图片描述

2.Radio 单选框示例:

<label><input type="radio" value="male" v-model="gender">男</label>
<label><input type="radio" value="female" v-model="gender">女</label> 
<div>{{gender}}</div>

gener值即为选择的radio元素的value值。

3.Checkbox 分两种情况:单个勾选框和多个勾选框。
单个勾选框,v-model即为布尔值,此时input的value值并不影响v-model的值。

<input type="checkbox" v-model="checked">
<div>checked: {{ checked }}</div>

图片描述

多个勾选框,v-model使用相同的属性名称,且属性为数组。

<label><input type="checkbox" value="1" v-model="multiChecked">1</label>
<label><input type="checkbox" value="2" v-model="multiChecked">2</label> 
<label><input type="checkbox" value="3" v-model="multiChecked">3</label> 
<div>multiChecked: {{ multiChecked }}</div>

图片描述

请输入代码

4.Select 同checkbox元素一样,也分单选和多选两种,多选的时候也需要绑定到一个数组。
单选:

<select v-model="selected">
    <option>A</option>
    <option>B</option>
    <option>C</option>
</select>
<div>selected: {{ selected }}</div>

图片描述

多选:

<select v-model="multiSelected" multiple>
    <option>A</option>
    <option>B</option>
    <option>C</option>
</select>
<div>multiSelected: {{ multiSelected }}</div>

图片描述

注意:multiple 属性规定可同时选择多个选项。
在不同操作系统中,选择多个选项的差异:

对于 windows:按住 Ctrl 按钮来选择多个选项
对于 Mac:按住 command 按钮来选择多个选项

由于上述差异的存在,同时由于需要告知用户可以使用多项选择,对用户更友好的方式是使用复选框。
提示:可以把 multiple 属性与 size 属性配合使用,来定义可见选项的数目。

5.参数特性
默认情况下,v-model在input事件中同步输入框值与数据,加lazy属性后会在change事件中同步。number会自动将用户输入转为Number类型,如果原值转换结果为NaN则返回原值。不过Vue.js 2.0中取消了lazy和number作为参数,用修饰符来代替。新增了trim修饰符,去掉输入值首尾空格。

<input type="text" v-model.lazy="message" @change="changeFn">
<input type="text" v-model.number="message">
<input type="text" v-model.trim="message">

四、模板渲染

1.前后端渲染对比:两种方式各有自己的优缺点,需要根据自己的业务场景来选择技术方案。
前端渲染的优点在于:
① 业务分离,后端只需要提供数据接口,前端在开发时也不需要部署对应的后端环境,通过一些代理服务器工具就能远程获取后端数据进行开发,能够提升开发效率。
② 计算量转移,原本需要后端渲染的任务转移给了前端,减轻了服务器的压力。
后端渲染的优点在于:
① 对搜索引擎友好。
② 首页加载时间短,后端渲染加载完成后就直接显示HTML,但前端渲染在加载完成后还需要有段js渲染的时间。
Vue.js 2.0开始支持服务端渲染,从而让开发者在使用上有了更多的选择。

2.v-if vs v-show
当v-if和v-show的条件发生变化时,v-if引起了dom操作级别的变化,而v-show仅发生了样式的变化,从切换的角度考虑v-show消耗的性能要比v-if小。
除此之外,v-if切换时,Vue.js会有一个局部编译/卸载的过程,因为v-if中的模板也可能包括数据绑定或子组件。v-if会确保条件块在切换当中适当地销毁与中间内部的事件监听器和子组件。而且v-if是惰性的,如果在初始化条件为假时,v-if本身什么都不会做,而v-show则仍会进行正常的操作,然后吧css样式设置为display:none。
总的来说,v-if有更高的切换消耗,而v-show有更高的初始渲染消耗,我们需要根据实际的使用场景来选择合适的指令。

3.列表渲染v-for指令
Vue.js对data中数组的原生方法进行了封装,所以在改变数时能触发视图更新,但以下两种情况是无法触发视图更新的:
① 通过索引直接修改数组元素,例如 vm.items[0] = { title: 'title-changed' };

对于这种情况,Vue.js提供了$set方法,在修改数据的同时进行视图更新,可以写成:vm.items.$set(0, {title: 'title-changed'}) 或者 vm.$set('items[0]', {title: 'title-changed'}),两种方式皆可以达到效果。

② 无法直接修改“修改数组”的长度,例如 vm.items.length = 0;

在列表渲染的时候,有个性能方面的小技巧,如果数组中有唯一标识id,通过track-by给数组设定唯一标识,这样Vue.js在渲染过程中会尽量复用原有对象的作用域及DOM元素。

<li v-for="item in items" track-by="id"></li>

track-by已经替换为key,它的工作方式与其他属性一样,没有v-bind或者:前缀,它会被作为一个字符串处理。多数情况下,需要使用具有完整表达式的动态绑定来替换静态的key。现在应写为:

<li v-for="item in items" v-bind:key="item.id"></li>

五、事件绑定与监听

1.在直接绑定methods函数和内联JavaScript语句时,都有可能需要获取原生DOM事件对象,以下两种方式都可以获取:

<div id="app">
    <button v-on:click="showEvent">showEvent</button>
    <button v-on:click="showEvent($event)">showEvent</button>
    <!--下面这样写获取不到event showEvent方法中输出undefined-->
    <button v-on:click="showEvent()">showEvent</button>
</div>
<script>
    var vm = new Vue({
        el: '#app',         
        methods: {
            showEvent(event) {
                console.log(event);
            }
        }
    });
</script>

同一个元素上也可以通过v-on绑定多个相同事件函数,执行顺序为顺序执行,例如:

<div v-on:click="sayFrom('first')" v-on:click="sayFrom('second')">say</div>    // 执行参数为first的

2.修饰符
Vue.js为指令v-on提供了多个修饰符,方便出来一些DOM时间的细节,并且修饰符可以串联使用。主要修饰符如下:
.stop 等同于调用event.stopPropagation()
.prevent 等同于调用event.preventDefault()
.capture 使用capture模式添加事件监听器
.self 只当事件是从监听元素本身触发时才触发回调
使用方式如下:

<a v-on:click.stop="doThis">say</a>
<!--阻止表单默认提交事件-->
<form v-on:submit.prevent="onSubmit"></form>
<!--阻止默认提交事件且阻止冒泡-->
<form v-on:submit.stop.prevent="onSubmit"></form>
<!--也可以只有修饰符,并不绑定事件-->
<form v-on:submit.stop.prevent></form>

尝试运行以下这个例子,更好地理解修饰符的作用。

<div id="app">
    <!--只点击红色div,弹出'click from inner',点击蓝色div,弹出'click from inner'和'click from self'-->
    <div v-on:click="saySelf('click from inner')" v-on:click.self="saySelf('click from self')" style="width:600px;border:1px solid blue;">
        <div style="width:100px;border:1px solid red;">div</div>
    </div>
    <!--点击button 触发button以及父元素div的saySelf事件 弹出'just button click'和'div'-->
    <div v-on:click="saySelf('div')">
        <button v-on:click="saySelf('button click')">button</button>
    </div>
    <!--点击button 触发button的saySelf事件 弹出'just button click'-->
    <div v-on:click="saySelf('div')">
        <button v-on:click.stop="saySelf('just button click')">button</button>
    </div>
</div>
<script>
    var vm = new Vue({
        el: '#app',       
        methods: {
            saySelf(msg) {
                alert(msg);
            }
        }
    });
</script>

除了事件修饰符之外,v-on还提供了按键修饰符,方便我们监听键盘事件中的按键。例如:

<div id="app">
    <!--监听input的输入,当输入回车时触发saySelf函数(回车的keyCode值为13),用于处理常见的用户输入完直接按回车键提交-->
    <input type="text" v-on:keyup.13="saySelf('回车键')">
</div>
<script>
    var vm = new Vue({
        el: '#app',
        methods: {
            saySelf(msg) {
                alert(msg);
            }
        }
    });
</script>

Vue.js给一些常用的按键名提供了别称,这样就省去了记一些keyCode。全部按键别名为:enter,tab,delete,esc,space,up,down,left,right。例如:

<input type="text" v-on:keyup.enter="saySelf('回车键')">

Vue.js也允许我们自己定义按键别名,例如:

<input type="text" v-on:keyup.f1="saySelf(112)">
Vue.directive('on').keyCodes.f1 = 112;

Vue.js 2.0中可以直接在Vue.config.keyCodes里添加自定义按键别名,无需修改v-on指令,例如:

<input type="text" v-on:keyup.13="saySelf('回车键')">
Vue.config.keyCodes.f1 = 13;

<input type="text" @keyup.media-play-pause="method">
Vue.config.keyCodes = {
  v: 86,
  f1: 112,
  // camelCase 不可用
  mediaPlayPause: 179,
  // 取而代之的是 kebab-case 且用双引号括起来
  "media-play-pause": 179,
  up: [38, 87]
}

第3章 指令

一、内置指令

1.v-bind
拥有三种修饰符,分别为.sync .once .camel。在Vue.js 2.0中修饰符.sync .once均被废弃,规定组件间仅能单向传递,如果子组件需要修改父组件,则必须使用事件机制来进行处理。
.camel 将绑定的特性名字转回驼峰命名。之鞥呢用于普通HTML属性的绑定,通常会用于svg标签下的属性,例如:<svg width='400' height='300' :view-box.camel='viewBox'></svg>,输出结果即为<svg width='400' height='300' :viewBox='viewBox'></svg>

2.v-for
Vue.js 2.0中做了些细微的调整,大致包括以下几个方面:
① 参数顺序变化

当包含参数index或key时,对象参数修改为(item, index)或(value, key),这样与JS Array对象的新方法forEach和map,以及一些对象迭代器(例如lodash)的参数能保持一致。

② v-bind:key

属性track-by被v-bind:key代替,<li v-for="item in items" track-by="id"></li>需要改写为<li v-for="item in items" v-bind:key="item.id"></li>

③ n in 10

v-for="n in 10"中的n由原来的0 ~ 9迭代变成1 ~ 10迭代。

3.v-el 和 v-ref
v-el指令为DOM元素注册了一个索引,使得我们可以直接访问DOM元素。语法上说可以通过所属实例的$els属性调用。
v-ref指令与v-el类型,只不过v-ref作用于子组件上,实例可以通过$refs访问子组件。命名方式也类型,想使用驼峰命名的话用“-”来做连接。
注意:Vue.js 2.0中,v-el和v-ref合并为一个ref属性了,可以在组件实例中通过$refs来调用。这意味着 v-el:my-element 将写成这样:ref="myElement",v-ref:my-component 变成了这样:ref="myComponent"。绑定在一般元素上时,ref 指 DOM 元素,绑定在组件上时,ref 为一组件实例。

<div id="app">
    <div ref="demo">there is a el demo</div>
</div>
<script>
    var vm = new Vue({
        el: '#app',
        methods: {
            saySelf(msg) {
                alert(msg);
            },
        }
    });
    console.log(vm.$refs.demo.innerText);   // there is a el demo
</script>

因为 v-ref 不再是一个指令了而是一个特殊的属性,它也可以被动态定义了。这样在和v-for 结合的时候是很有用的:

<p v-for="item in items" v-bind:ref="'item' + item.id"></p>

以前 v-el/v-ref 和 v-for 一起使用将产生一个 DOM 数组或者组件数组,因为没法给每个元素一个特定名字。现在你还仍然可以这样做,给每个元素一个同样的ref:

<p v-for="item in items" ref="items"></p>

和 1.x 中不同,$refs 不是响应的,因为它们在渲染过程中注册/更新。只有监听变化并重复渲染才能使它们响应。

另一方面,设计$refs主要是提供给 js 程序访问的,并不建议在模板中过度依赖使用它。因为这意味着在实例之外去访问实例状态,违背了 Vue 数据驱动的思想。

4.v-pre
v-pre指令就是跳过编译这个元素和子元素,显示原始的{{}}Mustache标签,用来减少编译时间。例如:

<div id="app">
    <div v-pre>{{ uncompiled }}</div>
</div>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            uncompiled: 'There is an uncompiled element'
        }
    });
</script>
输出结果为:<div>{{ uncompiled }}</div>

5.v-cloak
v-cloak指令相当于在元素上添加了一个[v-cloak]的属性,直到关联的实例结束编译。官方推荐可以和css规则[v-cloak]{display:none}一起使用,可以隐藏未编译的Mustache标签直到实例准备完毕。例如:

<div v-cloak>{{ msg}}</div>

6.v-once
v-once指令是Vue.js 2.0中新增的内置指令,用于标明元素或组件只渲染一次,即使随后发生绑定数据的变化或更新,该元素或组件以及包含的子元素都不会再次被编译和渲染。相当于明确标注了这些元素不需要被更新,所以v-once的作用是最大程度地提升了更新行为中页面的性能,可以略过一些明确不需要变化的步骤。使用方式如下:

<div v-once>{{ uncompiled }}</div>
<my-component v-once :msg="msg"></my-component>

二、自定义指令基础

1、指令的注册
可以通过Vue.directive(id, definition)方法注册一个全局自定义指令,接收参数id和定义对象。id是指令的唯一标识,定义对象则是指令的相关属性及钩子函数。例如:

Vue.directive('global-directive', definition);    // 只注册了这个指令,并没有赋予这个指令任何功能
<div v-global-directive></div>    // 可以在模板中这么使用

除了在全局注册指令外,也可以通过在组件的directives选项注册一个局部的自定义指令。例如:

var comp = Vue.extend({
    directives: {
        'localDirective': {}    // 可以采用驼峰式命名
    }
});
// 该指令就只能在当前组件内通过v-local-directive的方式调用,而无法被其他组件调用。

2.指令的定义对象
注册指令的同时,可以传入definition定义对象,对指令赋予一些特殊功能。一个指令定义对象主提供如下几个钩子函数(均为可选):
bind:只被调用一次,在指令第一个绑定到元素上时调用。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:Vue.js 1.0指令在bind之后以初始值为参数进行第一次调用,之后每次当绑定值发生变化时调用,update接收到的参数为newValue和oldValue。Vue.js 2.0中变化:

指令绑定bind函数执行后不直接调用update函数。 
只要组件发生重绘,无论指令接受的值是否发生变化,均会调用update函数。如果需要过滤不必要的更新,则可以使用binding.value == binding.oldValue来判断。

componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:指令从元素上解绑时调用,只调用一次。

钩子函数参数(即 el、binding、vnode 和 oldVnode)
指令钩子函数会被传入以下参数:
el:指令所绑定的元素,可以用来直接操作 DOM 。
binding:一个对象,包含以下属性:
name:指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
使用了这些属性的自定义钩子样例:

<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
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: '#hook-arguments-example',
  data: {
    message: 'hello!'
  }
})
输出结果:
    name: "demo"
    value: "hello!"
    expression: "message"
    argument: "foo"
    modifiers: {"a":true,"b":true}
    vnode keys: tag, data, children, text, elm, ns, context, functionalContext, key, componentOptions, componentInstance, parent, raw, isStatic, isRootInsert, isComment, isCloned, isOnce

钩子函数实例和参数变化

<div id="demo" v-my-msg:console.log="content"></div>
<script>
    Vue.directive('my-msg', {
        bind: function (el, binding, vnode) {
            console.log('el', el);
            console.log('binding', binding);
            console.log('vnode', vnode);
        }
    });
    var vm = new Vue({
        el: '#demo',
        data: {
            content: 'there is the content'
        }
    });
</script>

函数简写
在很多时候,可能想在bind和update时触发相同行为,不关心其它的钩子。可以直接传入一个函数代替定义对象:

<div id="demo" v-color-swatch="red">ddd</div>
<div v-color-swatch="red">ddd</div>
<script>
    Vue.directive('color-swatch', function (el, binding) {
        el.style.backgroundColor = binding.value;
    });
    var vm = new Vue({
        el: '#demo',
        data: {
            red: 'red'
        }
    });
</script>

对象字面量
如果指令需要多个值,可以传入一个 JavaScript 对象字面量。指令函数能够接受所有合法的 JavaScript 表达式。

<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!"
});

第4章 过滤器

Vue.js 允许自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示:

<!-- 在双花括号中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

可以在一个组件的选项中定义本地的过滤器:

filters: {
  capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}

或者在创建 Vue 实例之前全局定义过滤器:

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

new Vue({
  // ...
})

过滤器函数总接收表达式的值 (之前的操作链的结果) 作为第一个参数。在上述例子中,capitalize 过滤器函数将会收到 message 的值作为第一个参数。

过滤器可以串联:

{{ message | filterA | filterB }}

在这个例子中,filterA 被定义为接收单个参数的过滤器函数,表达式 message 的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数 filterB,将 filterA 的结果传递到 filterB 中。

过滤器是 JavaScript 函数,因此可以接收参数:

{{ message | filterA('arg1', arg2) }}

这里,filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。

日期格式化过滤器:

<div id="app">
    {{date | date('yyyy-MM-dd hh:mm:ss')}}
</div>
<script>
    // 日期格式化
    Vue.filter('date', function (value, format) {
        var o = {
            'M+': value.getMonth() + 1, // 月份
            'd+': value.getDate(),  // 日
            'h+': value.getHours(),  // 小时
            'm+': value.getMinutes(),  // 分
            's+': value.getSeconds(),  // 秒
        };
        if (/(y+)/.test(format)) 
            format = format.replace(RegExp.$1, (value.getFullYear() + '').substr(4 - RegExp.$1.length));
        for (var k in o) {
            if (new RegExp('('+ k + ')').test(format)) 
                format = format.replace(RegExp.$1, (RegExp.$1.length == 1) ? o[k] 
                : ('00' + o[k]).substr(('' + o[k]).length));
        }
        return format;
    });
    var vm = new Vue({
        el: '#app',
        data: {
            date: new Date()
        }
    });
    // 显示结果:2019-04-10 17:19:17
</script>

第5章 css过渡

概述
Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。
包括以下工具:

  • 在 CSS 过渡和动画中自动应用 class
  • 可以配合使用第三方 CSS 动画库,如 Animate.css
  • 过渡钩子函数中使用 JavaScript 直接操作 DOM
  • 可以配合使用第三方 JavaScript 动画库,如 Velocity.js

一、单元素/组件的过渡
Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡

  • 条件渲染 (使用 v-if)
  • 条件展示 (使用 v-show)
  • 动态组件
  • 组件根节点

直接使用transition标签并设定其属性来定义一个过渡效果。例如:

<div id="demo">
  <button v-on:click="show = !show">
    Toggle
  </button>
  <transition name="fade">
    <p v-if="show">hello</p>
  </transition>
</div>
new Vue({
  el: '#demo',
  data: {
    show: true
  }
})
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}

1.过渡的类名

在进入/离开的过渡中,会有 6 个 class 切换。

  1. v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
  2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
  3. v-enter-to: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
  4. v-leave: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
  5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
  6. v-leave-to: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。

图片描述

对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>,则 v- 是这些类名的默认前缀。如果你使用了 <transition name="my-transition">,那么 v-enter 会替换为 my-transition-enter。

常用的过渡都是使用 CSS 过渡。下面是一个简单例子:

<div id="example-1">
  <button @click="show = !show">
    Toggle render
  </button>
  <transition name="slide-fade">
    <p v-if="show">hello</p>
  </transition>
</div>
new Vue({
  el: '#example-1',
  data: {
    show: true
  }
})
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.slide-fade-enter-active {
  transition: all .3s ease;
}
.slide-fade-leave-active {
  transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active for below version 2.1.8 */ {
  transform: translateX(10px);
  opacity: 0;
}

2.CSS 动画
CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除。

<div id="example-2">
  <button @click="show = !show">Toggle show</button>
  <transition name="bounce">
    <p v-if="show">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.</p>
  </transition>
</div>
new Vue({
  el: '#example-2',
  data: {
    show: true
  }
})
.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);
  }
}

3.自定义过渡的类名
可以通过以下特性来自定义过渡类名:

  • enter-class
  • enter-active-class
  • enter-to-class (2.1.8+)
  • leave-class
  • leave-active-class
  • leave-to-class (2.1.8+)

他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用十分有用。示例:

<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">

<div id="example-3">
  <button @click="show = !show">
    Toggle render
  </button>
  <transition
    name="custom-classes-transition"
    enter-active-class="animated tada"
    leave-active-class="animated bounceOutRight"
  >
    <p v-if="show">hello</p>
  </transition>
</div>

new Vue({
  el: '#example-3',
  data: {
    show: true
  }
})

JavaScript 钩子
可以在属性中声明 JavaScript 钩子

<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>
// ...
methods: {
  // --------
  // 进入中
  // --------

  beforeEnter: function (el) {
    // ...
  },
  // 当与 CSS 结合使用时
  // 回调函数 done 是可选的
  enter: function (el, done) {
    // ...
    done()
  },
  afterEnter: function (el) {
    // ...
  },
  enterCancelled: function (el) {
    // ...
  },

  // --------
  // 离开时
  // --------

  beforeLeave: function (el) {
    // ...
  },
  // 当与 CSS 结合使用时
  // 回调函数 done 是可选的
  leave: function (el, done) {
    // ...
    done()
  },
  afterLeave: function (el) {
    // ...
  },
  // leaveCancelled 只用于 v-show 中
  leaveCancelled: function (el) {
    // ...
  }
}

这些钩子函数可以结合 CSS transitions/animations 使用,也可以单独使用。
注意:

  • 当只用 JavaScript 过渡的时候,在 enter 和 leave 中必须使用 done 进行回调。否则,它们将被同步调用,过渡会立即完成。
  • 推荐对于仅使用 JavaScript 过渡的元素添加 v-bind:css="false",Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。

一个使用 Velocity.js 的简单例子:

<!--
Velocity是一款高校的动画引擎,可以单独使用也可以配合jQuery使用。 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画的一个很棒的选择
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>

<div id="example-4">
  <button @click="show = !show">
    Toggle
  </button>
  <transition
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:leave="leave"
    v-bind:css="false"
  >
    <p v-if="show">
      Demo
    </p>
  </transition>
</div>
new Vue({
  el: '#example-4',
  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 })
    }
  }
})

二、初始渲染的过渡
可以通过 appear 特性设置节点在初始渲染的过渡

<transition appear>
  <!-- ... -->
</transition>

这里默认和进入/离开过渡一样,同样也可以自定义 CSS 类名。

<transition
  appear
  appear-class="custom-appear-class"
  appear-to-class="custom-appear-to-class" (2.1.8+)
  appear-active-class="custom-appear-active-class"
>
  <!-- ... -->
</transition>

自定义 JavaScript 钩子:

<transition
  appear
  v-on:before-appear="customBeforeAppearHook"
  v-on:appear="customAppearHook"
  v-on:after-appear="customAfterAppearHook"
  v-on:appear-cancelled="customAppearCancelledHook"
>
  <!-- ... -->
</transition>

appear主要用于元素的首次渲染,如果同时声明了enter和appear的相关钩子函数,元素首次渲染的时候会使用appear系钩子函数,再次渲染的时候才使用enter系钩子函数。

二、多个元素的过渡
我们之后讨论多个组件的过渡,对于原生标签可以使用 v-if/v-else 。最常见的多标签过渡是一个列表和描述这个列表为空消息的元素:

<transition>
  <table v-if="items.length > 0">
    <!-- ... -->
  </table>
  <p v-else>Sorry, no items found.</p>
</transition>

可以这样使用,但是有一点需要注意:当有相同标签名的元素切换时,需要通过 key 特性设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容。即使在技术上没有必要,给在 <transition> 组件中的多个元素设置 key 是一个更好的实践。示例:

<transition>
  <button v-if="isEditing" key="save">
    Save
  </button>
  <button v-else key="edit">
    Edit
  </button>
</transition>

在一些场景中,也可以通过给同一个元素的 key 特性设置不同的状态来代替 v-if 和 v-else,上面的例子可以重写为:

<transition>
  <button v-bind:key="isEditing">
    {{ isEditing ? 'Save' : 'Edit' }}
  </button>
</transition>

使用多个 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'
    }
  }
}

过渡模式mode
同时生效的进入和离开的过渡不能满足所有要求,所以 Vue 提供了过渡模式,控制过渡插入/移除的先后顺序,主要用于元素切换时。可供选择的值有“out-in” “in-out”,如果不设置,则同时调用。

  • in-out:新元素先进行过渡,完成之后当前元素过渡离开。
  • out-in:当前元素先进行过渡,完成之后新元素过渡进入。
<div id="app">
    <button @click="show = !show">
        Toggle
    </button>
    <transition name="fade" mode="out-in">
        <div :key="show">{{show}}</div>
    </transition>
</div>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            show: true
        }
    });
</script>

当show在true和false切换时,mode="out-in"决定先移除<div>true</div>,等过渡结束后,再插入<div>true</div>元素,mode="in-out"则相反。

列表过渡
怎么同时渲染整个列表,比如使用 v-for ?在这种场景中,使用 <transition-group> 组件。这个组件的特点:

  • 不同于<transition>,<transition-group>不是一个虚拟DOM,会真实渲染在DOM树中。默认<span>,可以通过 tag 特性更换为其他元素。
  • 过渡模式不可用,不支持mode参数,因为我们不再相互切换特有的元素。
  • 内部元素总是需要提供唯一的 key 属性值。

列表的进入/离开过渡

<div id="app">
    <button @click="pushFn">push</button>
    <transition-group name="list" appear tag="ul">
        <li v-for="item in items" :key="item.id" class="list-li">{{item.text}}</li>
    </transition-group>
</div>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            items: [
                {id: 1, text: '11'},
                {id: 2, text: '22'},
                {id: 3, text: '33'}
            ]
        },
        methods: {
            pushFn() {
                this.items.push({id: 4, text: '44'});
            }
        }
    });
</script>
<style>
    .list-li{
        width: 100px;
        height: 20px;
        transform: translate(0, 0);
    }
    .list-enter,.list-leave-active{
        opacity: 0;
        transform: translate(-30px, 0);
    }
    .list-enter-active, .list-leave-active{
        transition: all 0.5s ease;
    }
</style>

列表的排序过渡
列表的交错过渡
可复用的过渡
动态过渡

第6章 组件

一、组件注册
Vue.js提供了两种注册方式,分别是全局注册和局部注册。
全局注册需要确保在根实例初始化之前注册,这样才能使组件在任意实例中被使用,示例:

Vue.component('my-component-name', {
    // ... 选项 ...
})
// 这条语句需要写在new Vue({ el: '#app' })之前。
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })

new Vue({ el: '#app' })
<div id="app">
    <component-a></component-a>
    <component-b></component-b>
    <component-c></component-c>
</div>
// 这三个组件在各自内部也都可以相互使用。

如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

局部注册则限定了组件只能在被注册的组件中使用,而无法在其他组件中使用,示例:

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
new Vue({
    el: '#app',
    components: {
        'component-a': ComponentA,
        'component-b': ComponentB
    }
})

对于 components 对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。
注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentA 在 ComponentB 中可用,则你需要这样写:

var ComponentA = { /* ... */ }
var ComponentB = {
    components: {
        'component-a': ComponentA
    },
    // ...
}

或者如果你通过 Babel 和 webpack 使用 ES2015 模块,:

import ComponentA from './ComponentA.vue'
export default {
    components: {
        ComponentA
    },
    // ...
}

注意在 ES2015+ 中,在对象中放一个类似 ComponentA 的变量名其实是 ComponentA: ComponentA 的缩写,即这个变量名同时是:

  • 用在模板中的自定义元素的名称
  • 包含了这个组件选项的变量名

构造器写法:Vue.extend({})

<div id="app">
    <my-component></my-component>
    <parent></parent>
</div>
<script>
    // 全局注册
    var MyComponent = Vue.extend({
        template: '<p>This is a component</p>'
    });
    Vue.component('my-component', MyComponent);
    
    // 局部注册
    var Parent = Vue.extend({
        template: `<div><p>This is a parent component</p>
                    <my-child></my-child>
                    </div>`,
        components: {
            'my-child': {
                template: '<p>This is a child component</p>'
            }
        }
    });
    var vm = new Vue({
        el: '#app',
        components: {
            parent: Parent
        }
    });
</script>

Vue 组件的基本示例:

<div id="app">
    <button-counter></button-counter>
</div>
<script>
    // 组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
    Vue.component('button-counter', {
        // 一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
        data: function () {
            return {
                count: 0
            }
        },
        template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
    });
    var vm = new Vue({
        el: '#app'
    });
</script>

江离白芷
37 声望2 粉丝

下一篇 »
数据存储