数据侦听 Vue.$watch
watch
提供了观察和响应实例上数据变动的办法,当有一些数据需要跟随其他数据变化而变化时,如子组件某个数据依赖来自于父组件的prop
计算。很直观的会想到计算这功能和计算属性十分类似。Vue建议用户使用计算属性,除非如下情况:
(1)当要执行的操作是异步操作时,
(2)相应事件是开销较大的操作时。
watch: {
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
}
}
}
当观察的值发生改变时, 观察者会接收到两个参数:(1) 新值,(2)原先的值。 值得注意的是, watch
在组件第一次被挂载时不会触发, 只有值被改变时才触发。
watch: {
}
}
data选项为什么是一个函数?
Vue官网第一课描述的data
选项就是一个对象,为什么在编写组件的时候却要定义成一个函数?
我们知道对象是引用类型,而组件最大的特性就是可复用性,当一个组件被多次复用却指向同一个引用类型数据,组件间将无独立性而言。因此,将data选项
定义成一个函数,是为了利用函数的私有作用域特性实现不同组件间数据私有的效果
计算属性缓存 及 劫持setter
一个需要计算的数据,通常有: (1)计算属性获取,(2)定义一个方法实现。虽然实现结果相同,但前者优势在于计算属性是基于它们的依赖进行缓存的。也就是说:
(1)计算属性依赖不改变,计算就不会触发,改变了才重新触发计算;而调用方法总会再次执行函数
(2)当依赖不是响应式依赖时, 计算属性将永远不会触发计算。如
computed: {
}
}
计算属性默认只有 getter
,常规用法其实是调用了计算属性的getter
方法。若在需要时也可以提供一个setter
,此时, 需要将计算属性定义为一个对象,setter
的含义与原生JS的类似
computed: {
return this.val
},
set (newVal) { // setter
return newVal.split('').reverse().join('')
}
}
}
v-if 惰性、缓存 及 使用 <template>
我们知道,v-if
能决定DOM结构存不存在,而v-show
只是控制了DOM元素的display
属性,当页面切换频率不高时,Vue建议使用v-if
。
所谓的惰性,就是当遇到条件为非真时直接跳过,只有第一次遇到真值才开始渲染条件块。
而缓存,官网给出解释如下:
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。
也就是说,假设页面原本渲染了一个input
标签,而状态改变后也有一个input
标签,Vue检查到新老标签标签名和属性列表都相同。将保留已渲染的标签继续使用。
这种缓存机制是Vue默认的,想修改这种动作,只要给标签加上具有唯一值的key
属性即可,
<input placeholder="Enter your username" key="username-input">
正常情况下,v-if
会被设置在一个标签元素内使用,当遇到前后两个或多个兄弟标签都需要使用相同状态值来判断是否渲染时,可以一个无状态不可见标签<template>
来包裹,Vue在构建DOM时会将其丢弃,并正确的将v-if
作用到相应的标签上。
<template v-if="real">
</template>
v-if 与 v-for 优先级
根据Vue的风格指南,不建议将v-if
和v-for
放在一起使用,我们来探索一下为什么.
它们一起使用的场景无非就有两个
(1)希望通过v-if
控制v-for
代码块是否显示。这种情况下一般v-if
变量是个状态量,与v-for
循环变量无关。
(2)希望通过循环变量中的某个属性的真假值,来控制该项是否应该被循环渲染出来
这两种用法有什么问题?在Vue语法中有个规则:循环体中,v-for属性优先级高于其他属性。也就是说:
场景(1): v-if
的渲染会发生在循环之后,列表优先生成,这就无法提前阻止循环列表的渲染。这与我们初衷想要决定循环块是否渲染产生冲突。解决办法是:使用<template>
标签包裹并在这里设置v-if
控制
场景(2): 如果存在不该被渲染的项,这个项就不应该出现在循环变量中,Vue建议使用计算属性过滤数组。因此也不再需要v-if
v-for 作用于对象
循环不止作用于数组,同样可作用于所有可迭代类型变量中。
在遍历对象时,通常是按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下是一致的。
</div>
v-for渲染后的数组缓存替换规则
Vue 包含一组观察数组的变异方法(mutation method
),它们会触发视图更新。包括: push()、pop()、shift()、unshift()、splice()、sort()、reserve()
等。这些方法都会改变原数组。
同样还包含非变异方法,如filter()、concat()、slice()
。他们不改变原数组,而是返回一个新数组。
如果我们对已渲染过后的数组进行非变异方法操作,直觉上列表会重新渲染,其实不然。
Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的、启发式的方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
exa.items = exa.items.filter(function (item) {
冻结双向数据绑定
如果初始渲染后不想让视图层响应模型层变化, 可以使用v-once
标签属性, 告知被包含在该标签内部的所有数据绑定不要响应视图更新
<span v-once>这个数据不会发生改变: {{ message }}</span>
绑定一段 HTML
Vue在html部分, 无论是利用双大括号{{ }}
还是v-model
绑定的值都会被解释为普通文本。如果需要绑定一段 HTML,可以使用v-html
<p v-html="htmlCode"></p>
修饰符
(1) .prevent / .stop / .passive
如果你遇到过在页面执行一个Click事件,触发了两次函数调用,你则需要检查一下是否由事件冒泡引起的。 在DOM2级, DOM3级事件标准中, 浏览器接受一个点击交互后, 产生事件流会有两个过程,捕获和冒泡。 过程如下:
![8646214-d913dd6896b60f43.png](https://upload-images.jianshu.io/upload_images/8646214-d913dd6896b60f43.png)
为解决该问题,Vue提供了修饰符.prevent
可以告诉v-on
指令对于触发的事件调用event.preventDefault()
来阻止浏览器的默认行为。 .stop
则是调用event.stopPropagation()
来阻止目标元素的冒泡事件
.passive
不能和.prevent
一同使用,它会屏蔽.prevent
的冒泡效果。.passive
主要使用在移动端,它能提高其性能
(2)键盘修饰符 .enter / .tab / .delete ...
Vue提供监听键盘按键键值的办法,方便我们监听键盘事件。一般情况下,直接使用键值修饰,如enter
键的键值为13
,则使用办法为:
<input @click.13="handleClick"></input>
Vue为方便记忆,绑定了常用键名与键值的关系可直接使用键名绑定
<input @click.enter="handleClick"></input>
常用的有:.enter
,.tab
,.delete
,.esc
,.space
,.up
,.down
,.left
,.right
也可以用通过config.keyCodes
对象自定义按键修饰符别名:
// 可以使用 `v-on:keyup.f1`
(3)鼠标修饰符
鼠标修饰符限制处理函数仅响应特定的鼠标按钮,包括.left
,.right
,.middle
。
动态样式绑定 :class
Vue允许动态切换一个样式, 支持两种语法: 对象形式 | 数组形式
- 对象办法:键表示样式类名,值为 Truthy 表示添加该样式
<div class="wrap" :class="{ borderTop: boolean, active: isActive }"></div>
(2)给:class
传递一个数组,表示应用一组样式
<div :class="[ classA, classB ]"></div>
当
v-bind:style
使用需要添加浏览器引擎前缀的 CSS 属性时,如transform
,Vue.js 会自动侦测并添加相应的前缀。
事件绑定传参
如下,前者使用监听事件,而后者是内联处理器
<div id="example">
</div>
表单输入绑定
对于普通元素如<div> {{ message }} </div>
等并没有真正表现出Vue双向数据绑定的魅力,其只展现了从ViewModel层发生变化后反馈到View层的单方面特性。而表单输入的双向数据绑定还增加了用户交互使得View层发生改变并响应到ViewModel层,真正体现了“双向”功能。
v-model
可以在表单元素<input>, <textarea>
及<select>
上创建双向数据绑定。Vue会根据空间类型自动选取正确的方法更新元素。值得注意的是,v-model
会忽略所有表单元素的value, chekcd, selected
特性的初始值而总是将Vue实例的数据data选项
作为数据来源。也就是说,不能通过特性自身赋值绑定到v-model
上,而需要在data
中手动赋初始值
- 对于单行多行输入框,经
v-model
绑定过后的元素中增加内容不会生效,Vue只读绑定中的内容。 - 单个复选框,
v-model
绑定到布尔值;而多个复选框则绑定到同一个数组 - 单选按钮,绑定到同一个字符串,其值是
value
所对应的值 - 下拉选择菜单,单选时绑定到一个值上,多选时绑定到一个数组
# 单选下拉框去掉 multiple属性即可
{{ opt .text }}
</option>
</select>
<span>Array: {{ selected }}</span>
表单绑定修饰符
- .lazy:将
input
触发的更新延迟至change
触发
<input v-model.lazy="msg" >
- .number:将用户输入的内容转化为数字,否则总是返回字符串。设置
type
属性移动端可以调起数字键盘。如果这个值无法被parseFloat()
解析,则会返回原始的值
<input v-model.number="age" type="number">
- .trim:自动过滤用户输入的首尾空白字符
<input v-model.trim="msg">
Vue.$emit参数,及与 v-on 事件命名规范
在刚开始开发时可能会思考为什么prop
没有子向父传递。不幸运的是,prop
的逆向会给数据流向带来巨大的维护和理解困难,这也是为什么Vue封装了$emit
的模式 触发事件来取而代之的原因
this.$emit('method-name', param)
第一个参数是抛出的事件名,对应父级v-on
事件名,第二个参数是要带出的数据,该数据使用$event
捕获
<Children @click="$emit('enlarge-text', 0.1)"> Enlarge text </Children >
<blog @enlarge-text="postFontSize += $event"></blog>
通常父组件中会绑定给一个属性,该属性定义为一个方法且它的第一个参数就是被带出来的数据
enlargeText (num) {
this.postFontSize += num
}
【注意】不同于组件和prop,经$emit
抛出的事件名不会被用作一个JavaScript变量 名或属性名,所以就没有理由使用camelCase(驼峰式)或PascalCase(短线式)。因为HTML大小写不明感因素,v-on事件监听器在DOM模板中实质上会被自动转换为全小写,所以驼峰式命名规则将全部失效无法监听。建议使用短线式或全小写,特别是前者
<my-component v-on:my-event="handleEmit"></my-component> // 禁止使用myEvent
动态组件
比如我们有一个tab栏,其中有三个tab页,点击不同tab页需切换至不同的组件下,此时非常适合使用is
来指定不同的组件达到动态组件效果,如下
Vue.component('tab-home', {
...
new Vue({
el: '#demo',
data: {
currentTab: 'Home',
tabs: ['Home', 'Posts', 'Archive']
},
computed: {
currentTabComponent () {
return 'tab-' + this.currentTab.toLowerCase()
}
}
})
<component :is="currentTabComponent"></component>
Prop传递数据防脏
所有的 prop
都使得其父子组件形成一个单向下行绑定,父级prop
的更新会流动到子组件中,但反过来不行。这种设计办法是为了防止子组件意外改变父组件的状态,从而导致你的应用的数据流向难以理解。另外,如果该数据还被其他子组件使用,也将受影响,产生洪水式灾难。因此不应该在子组件中设计修改prop
数据的操作。
在Javascript中对象和数组都是通过引用传入的,因此对于引用类型的prop来说,在子组件中修改数据本身将直接改变父级的数据。
常见的试图改变prop
的操作有一下两种情形:
- 接收的
prop
作为一个初始值,这个子组件接下来希望将其作为一个本地的prop
数据来使用。这种情况下应该使用子组件中的data
来拷贝一份prop
数据数据
prop: [ 'initialNum' ],
num: this.initialNum
}
}
接收的prop
作为原始的值需要进行格式转换。这种情况下,应该使用计算属性来实现
props: ['size'],
当不需要对prop
做改变只是进行使用时可以不用data
拷贝,但也需要注意使用,曾经遇到将 ==
写成 =
,花了不少时间找bug
。当系统比较庞大时这种问题不好找,所以大家一定要细心实在不行就多做个data
拷贝。
prop自定义检查函数
Vue允许在进行prop传值时对值进行验证,type
可以验证数据类型,default
可以设置当未传入时的默认值。除此之外,还允许开发者们自定义验证函数
function CheckName (firstName, lastName) {
}
验证办法如下
prop: {
Prop与自身属性重名问题
当使用ElementUI
或Bootstrap
这些第三方插件时,往往他们定义有自己的属性,如果开发者们自定义的prop
属性与其发生重名时,Vue在大多数情况下,从外部提供给组件的值会替换掉组件内部设置好的值。
即假设存在传入type="text"
就会替换掉本身type="date"
类型,原来的就会被破坏。庆幸的是, class
和style
会智能一些,即两边的值会合并起来
Prop实现‘双向绑定’效果 .sync修饰符
Vue不是不建议子组件改变父组件属性吗?为什么还要给prop做"双向绑定"?我第一想法就是这样的。
其实他只是作为一种语法糖存在,并且具有可替代办法。他的应用场景就是:prop的值如果需要根据子组件的操作响应修改,则可以使用该语法糖