- Vue.js: Up and Running,中文版是Vue.js快跑:构建触手可及的高性能Web应用
- Vue.js 2 Web Development Projects,中文版是Vue.js项目实战
- 1 v-if与v-show
- 2 向Vue实例添加反应式属性
- 3 重新审视Vue实例的内容
- 4 Vue操作数组
- 5 双向绑定的补充
- 6 computed属性的set
- 7 watch监听对象属性和获取新旧值
- 7.1 监听对象的属性
- 7.2 监听时获取变化前后的值
- 7.3 深度监听
- 8 filter补遗
- 9 ref引用
- 10 处理事件
- 11 过渡与动画
v-if与v-show
v-show
需要先把模板处理完毕,而v-if
不需要。看下边这个例子:
<div id="app"> <div v-show="user"> <p>User name: {{ user.name }}</p> </div> </div> <script> new Vue({ el: '#app', data: { user: undefined } }); </script>这里会报错,因为Vue会尝试去获取
user.name
。如果将v-show
改成v-if
,内部的元素根本不会去被解释和渲染,所以不会报错。
采用这个方法,可以在父子组件通信的时候先进行父组件传来的数据的判断。
v-show
的好处是开销小。v-if
变动的时候,其内部组件会被重新计算和渲染,而v-show
不会,如果需要展示经常变化的数据,v-show
更合适一些。。
向Vue实例添加反应式属性
在之前QQ群讨论里,有朋友就说面试时候被问到如何给Vue实例添加属性,不用说,肯定是问如何添加反应式的属性。 Vue是在初始化实例的时候对data内的对象添加监听方法,为了要给Vue添加反应式的新属性有如下几种方法: 主要有三种方法:- 给要添加的属性提前赋值为undefined,然后再赋值,当然这种其实不叫做添加了,因为已经让Vue初始化了
- 使用
Object.assign()
,创建一个新对象覆盖原来data的对象。一会看下边例子。 - 使用
Vue.set(object,name, value)
,其中object
是data
中的对象,name
是属性名,value
设置的属性的值。在组件里想要使用这个方法,则可以通过this.$set
来使用这个方法。
<div id="app" class="container"> <template v-if="formData.user"> <div>这是新添加的user属性: {{formData.user}}</div> </template> <div>这是原来的属性: {{formData.username}}</div> </div> <script> var app= new Vue({ el: '#app', data: { formData: { username: 'jenny', } }, }); </script>启动这个实例后,在控制台里输入app.formData = Object.assign({},app.formData,{user:'cony'}),页面就能渲染出来新添加的属性。 再来看第三种方法的实例:
<div id="app" class="container"> <template v-if="formData.user"> <div>这是新添加的user属性: {{formData.user}}</div> </template> <div>这是原来的属性: {{formData.username}}</div> <button @click="addProperty">添加user=cony属性</button> </div> <script> var app= new Vue({ el: '#app', data: { formData: { username: 'jenny', } }, methods:{ addProperty:function () { this.$set(this.formData,'user','cony') } } }); </script>点击这个按键,就会看到新添加的属性被显示了出来。
重新审视Vue实例的内容
在之前学的时候,总以为数据放data,需要计算的放在computed里边,事件处理放在methods中。这也是被Vue.js实战这本书按照实例直接形成的想法。 实际上Vue实例就将其看成一个对象,其中的内容可以将其分为properties
和methods
两大类。
data
就是纯粹的保存数据,而methods
实际上就是这个Vue实例的方法。而computed
介于属性和方法之间,采用函数进行计算,采用属性进行访问。
另外就是this
的使用:
- 在模板渲染的双花括号里,不要使用
this
,Vue直接会去找data
,computed
和methods
对应的名称 - 在
methods
里访问属性和方法,需要加上this
,这个this
表示方法所属的Vue实例对象,可以用其调用其他属性和方法(以及computed
属性)。 - 在
computed
里访问属性和方法,也需要加上this
- 在
watch
里访问属性和方法,也需要加上this
- 在
filter
中无法通过this访问Vue实例的内容
Vue操作数组
Vue里不能够直接通过索引操作数组。看下边这个例子:<div id="app" class="container" v-cloak=""> <p>{{games}}</p> </div> <script> var app= new Vue({ el: '#app', data: { games: ['Baldur\'s gate 3','Baldur\'s gate 2','Neverwinter Nights','TotalWar: ThreeKingdoms', 'Planescape: Torment'] }, methods:{ } }); </script>如果在控制台中输入app.games[2]='Neverwinter Nights 2',会发现数组没有任何变化,这是因为Vue在代理数组的时候,没有针对索引进行监听,而是针对数组的原生方法进行监听。 想要更改数组的内容,有如下几种办法:
- Vue内置了对于
.splice
等数组常用内部方法的监听,所以可以用这些方法来更改。 - 用上边提到的
Vue.set
直接更新整个数组
双向绑定的补充
双向绑定已经了解了其实是一个语法糖,会绑定value
属性并且通过事件进行监听。
双向绑定之后,HTML元素的value
,checked
和selected
属性会失效,这个要注意。
由于绑定可以用来监听value属性,所以对于radio,checkbox,select都可以用来监听。只需要给要监听的一组内容绑定同一个属性即可。而且还可以反向传递value来控制选中哪个内容。
computed属性的set
computed
设置的东西,也像一个属性一样,可以实例内访问。data
,computed
和methods
的区别如下:
名称 | 可读 | 可写 | 是否能接受参数 | 动态计算 | 缓存 |
---|---|---|---|---|---|
data | Yes | Yes | No | No | 不会被计算 |
methods | Yes | No | Yes | Yes | No,每次调用都重新计算 |
computed | Yes | Yes | No | Yes | Yes |
computed
属性也可以set
,要通过设置具体的get
和set
方法才行:
<div id="app"> <p>{{numbers}}</p> <p>Sum of numbers: {{ numberTotal }}</p> </div> <script> var app = new Vue({ el: '#app', data: { numbers: [5, 8, 3] }, computed: { numberTotal: { get() { return this.numbers.reduce((sum, val) => sum + val); }, set(newValue) { const oldValue = this.numberTotal; const difference = newValue - oldValue; this.numbers.push(difference) } } } }); </script>这里的
get
就是访问属性的时候得到的结果。set
方法则是传入一个新要设置的值作为参数,然后在set
方法中进行更改。
这样就极大的提高了computed
属性的灵活性。
watch监听对象属性和获取新旧值
watch
的内容比较多,要分成几个部分总结。
监听对象的属性
watch
能做的事情,如果只是纯粹更改数值,最好还是使用methods
和computed
,否则逻辑会乱。watchers
的真正意义在于异步进行一些操作,比如模拟一个异步:
<div id="app"> <input type="text" v-model="inputValue"> <p>Five seconds ago, the input said "{{ oldInputValue }}".</p> </div> <script> new Vue({ el: '#app', data: { inputValue: '', oldInputValue: '' }, watch: { inputValue() { const newValue = this.inputValue; setTimeout(() => { this.oldInputValue = newValue; }, 5000); } } }); </script>注意
watch
是在数字变动之后才执行的。这里就监听了count
属性,然后每次有变动的时候,获取新的属性,过五秒在设置到oldInputValue
上。
普通情况下,如果watch一个对象,只有整个对象被替换成其他对象,才会触发watch。修改对象的属性,不会触发watch,这个时候需要特殊的写法:
<div id="app"> <p>outer对象的value是{{outer.value}}</p> </div> <script src="https://unpkg.com/vue"></script> <script> var app = new Vue({ el: '#app', data: { outer: { value: 1, } }, watch: { 'outer.value'() { console.log('outer.value watcher 被触发了') }, outer() { console.log('outer watcher整个对象被替换了') } } }); </script>如果在控制台输入app.outer.value =3,会发现只触发了
'outer.value'
的监听,如果输入app.outer={},就可以看到两个监听器都被触发了。
监听时获取变化前后的值
watch
还一个重要的内容是可以获取变化前后的值,这两个值是作为watch方法的两个参数存在:
<div id="app"> <p>outer对象的value是{{outer.value}}</p> </div> <script src="https://unpkg.com/vue"></script> <script> var app = new Vue({ el: '#app', data: { outer: { value: 1, } }, watch: { 'outer.value'(newval, oldval) { console.log('outer.value watcher 被触发了'); console.log('新的值是:' + newval); console.log('老的值是:' + oldval); }, outer() { console.log('outer watcher整个对象被替换了') } } }); </script>可以方便的通过参数来获取要设置的值和原来的值,这就可以进行一些特殊的操作。
深度监听
如果要监听一个对象所有的属性变化,就需要将watch拆开来写:<div id="app"> <p>outer对象的value是{{outer.value}}</p> </div> <script> var app = new Vue({ el: '#app', data: { outer: { value: 1, inner:{ value:2 } } }, watch: { outer: { handler(newval, oldval) { console.log(newval, oldval); }, deep: true } } }); </script>这里实际上是递归增加了所有监听器,所以不能直接显示前后的值,监听某个具体属性是可以的。
watch
的其他一些高级用法可以看这里。
过滤器filter补遗
filter本质上就是接受一个参数的函数。一般来说可以有两种方法编写过滤器:- 在Vue实例的
filters
属性之内编写 Vue.filter(filtername,function)
来注册
ref引用
已经知道了<canvas ref="myCanvas"></canvas>
这样一个元素可以用this.$refs.myCanvas
来获取对应的vnode节点对象。
这里还需要知道的是this.$refs
只是对当前组件内的元素进行查找,不会查到其他组件去,所以可以很方便的使用,省去了使用queryselector
等方式定位元素。
处理事件
之前的内容主要都是数据展示,还不涉及到交互。如果要使用交互,就要涉及到处理事件了。 处理事件的时候,可以直接写在绑定内部,是因为已经知道绑定内部的东西都按照JS语句解析。 但实际上,还是采用处理函数比较方便,因为处理函数的第一个参数会被传入事件对象。修饰符
prevent
,阻止默认事件,常用于a标签,submit类型的按钮之类stop
,阻止冒泡,很常用once
,只有第一次才生效capture
,这是一个新了解的,即元素自身触发的事件先在此处理,然后才交由内部元素进行处理。像反向冒泡。self
,event.target是当前元素,即事件不是从内部冒泡上来的。
详细的生命周期
一个Vue实例的生命周期对应八个钩子,其实是四个大阶段+四个before,具体如下:new Vue()
执行,新实例初始化beforeCreate
调用,反应式功能初始化created
调用,模板被初始化,会根据组件的template和render函数等初始化模板,模板还未加载到DOM上beforeMount
调用,根据模板生成了HTML元素,依然没有挂载到DOM上mounted
在生成好HTML元素之后调用,Vue 2.0中不能保证这个钩子调用之后Vue实例立刻被挂载到DOM上,必须在mounted
中调用Vue.nextTick()
(组件中使用this.$nextTick()
)才能确保,这个还有一个回调函数可以执行自己的代码。详细看下边例子。此钩子执行完毕之后,用户可以看到DOM元素,但数据还没更新上去。beforeUpdate
在数据还没有更新到DOM上之前调用,去计算新数据和准备更新updated
在用数据更新DOM完成之后调用,这个钩子会反复调用,如果有多个地方需要更新数据beforeDestroy
调用,在需要从DOM中去除组件的时候调用destroyed
调用,从DOM中去除组件之后调用
Vue.nextTick()
的例子是:
<script> new Vue({ el: '#app', mounted() { // Element might not have been added to the DOM yet this.$nextTick(() => { // Element has definitely been added to the DOM now }); } }); </script>
过渡与动画
Vue提供了很多过渡效果,之前一直没有看过,现在要稍微入入门看看。CSS过渡效果
Vue提供了一个组件<transition>
,这个组件会给其中的所有元素添加一个v-if指令和一个css类,这样就可以通过CSS类给元素添加挂载到DOM和从DOM移除时候的动画效果。
下边是一个初始的例子:
<div id="app"> <button @click="divVisible = !divVisible">Toggle visibility</button> <div v-if="divVisible">This content is sometimes hidden</div> </div> <script src="https://unpkg.com/vue"></script> <script> new Vue({ el: '#app', data: { divVisible: true } }); </script>这个例子很简单,就是点击按钮来切换下边的元素是否显示,由于我们使用了
v-if
,所以这个元素每次被移入和移出DOM树。
先来通过<transition>
添加一个简单的效果:
<transition name="fade"> <div v-if="divVisible">This content is sometimes hidden</div> </transition>单独加上这个也没效果,必须配合样式:
.fade-enter-active, .fade-leave-active { transition: opacity .5s; } .fade-enter, .fade-leave-to { opacity: 0; }这个生效的原理是:Vue会使用
<transition name="fade">
中的name
属性的值,加上一些后缀拼合成在不同阶段应用的CSS类名,这些类及对应作用是:
{name}-enter
,元素添加进DOM的时候会被加上这个类,在1帧之后就会取消这个类。用来设置元素进入DOM之后初始并且想要逐渐消失的样式,例子里设置了完全透明,在元素进入的时候,一开始就是完全透明的。{name}-enter-active
,这个样式在整个加入过程中都生效,和{name}-enter
同时添加到元素上,在动画完成后移除。这个类里最好设置CSS的transition属性,包括动画时间,属性和过渡曲线。{name}-enter-to
在{name}-enter
被移除的时候添加到元素上,一般用于设置逐渐出现在元素上的样式。{name}-leave
,在触发要移除DOM的第一帧添加上,然后下一帧就会移除{name}-leave-active
,在移除的全过程生效。{name}-leave-to
,很像enter-to,当{name}-leave
被移除的时候添加到元素上。
{name}-enter
、{name}-enter-active
、{name}-leave-active
和{name}-leave-to
四个类。
JS过渡效果
JS过渡就是<transition>
在不同阶段提供的钩子,就可以使用JS库来写动画。
这里的钩子,是指特殊的事件,在<transition>
元素上监听这些事件然后写处理函数就可以了。
钩子和CSS动画的阶段本质上是一致的,列表如下:
before-enter
,在进入动画开始之前执行,用于设置初始样式enter
,进入动画开始的时候执行,可以在其中使用done回调来指示动画结束afterEnter
,进入动画结束的时候调用enterCancelled
,进入动画被取消的时候调用beforeLeave
,移出动画开始之前调用,用于设置移出的初始样式leave
,移出动画开始的时候执行afterLeave
,和上边的阶段类似leaveCancelled
和上边的阶段类似
<div id="app"> <button @click="divVisible = !divVisible">Toggle visibility</button> <transition v-on:before-enter="handleBeforeEnter" v-on:enter="handleEnter" v-on:leave="handleLeave"> <div v-if="divVisible">The content</div> </transition> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script> <script src="https://unpkg.com/vue"></script> <script> new Vue({ el: '#app', data: { divVisible: false }, methods: { handleBeforeEnter(el) { el.style.opacity = 0; }, handleEnter(el, done) { TweenLite.to(el, 1, { opacity: 1, onComplete: done }); }, handleLeave(el, done) { TweenLite.to(el, 1, { opacity: 0, onComplete: done }); } } }); </script>果然还是老外写的书好,昨天看了一会,就发现还有如此多的细节没有掌握。 现在对于一个Vue实例的作用也比较清楚了。
data
用来定义原始的数据,computed
用于基于原始数据生成可以反复使用的结果,methods
就是这个实例的方法,可以包括各种方法,无论是普通功能还是事件调用。当然不同的方法有不同的参数,尤其是处理事件的方法。watch
则用于对数据的异步处理。
前边几个属性都和Vue实例有着紧密的联系,而filter是比较独立的功能函数,无法通过this访问实例。
这样基本上一个实例的内部就理清楚了。把实例进化到组件的话,还要再添加一个props
作为父组件传入的基础数据,和data
一样共同构成组件的基础数据。