其它
$nextTick
Vue一个重要的概念:异步更新队列。
Vue在观察到数据变化时并不是直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。在缓冲时会去除重复数据,从而避免不必要的计算和DOM操作。然后,在下一个事件循环tick中,Vue刷新队列并执行实际(已去重的)工作。所以如果你用一个for循环来动态改变数据100次,其实它只会应用最后一次改变,如果没有这种机制,DOM就要重绘100次,这固然是一个很大的开销。
Vue会根据当前浏览器环境优先使用原生的Promise.then和MutationObserver,如果都不支持,就会采用setTimeout 代替。
$nextTick就是用来知道什么时候DOM更新完成的:
<body>
<div id="app">
<div id="div" v-if="showDiv">这是一段文本</div>
<button @click="getText">获取 div内容</button>
</div>
<script src="../lib/vue.2.6.11.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {
showDiv: false
},
methods: {
getText: function () {
this.showDiv = true;
//Vue在观察到数据变化时并不是直接更新DOM
//let text1 = document.getElementById('div').innerHTML;
//console.log(text1);
this.$nextTick(function () {
let text2 = document.getElementById('div').innerHTML;
console.log(text2);
});
}
}
});
</script>
</body>
X-Templates
果你没有使用webpack、gulp等工具,试想一下你的组件template的内容很冗长、复杂,如果都在JavaScript里拼接字符串,效率是很低的,因为不能像写HTML 那样舒服。Vue提供了另一种定义模板的方式,在<script>
标签里使用text/x-template类型,并且指定一个id,将这个id赋给template。
<body>
<div id="app">
<my-component></my-component>
<script type="text/x-template" id="my-component">
<div>这是组件的内容</div>
<div>appMsg={{appMsg}}</div>
<div>cptMsg={{cptMsg}}</div>
</script>
</div>
<script src="../lib/vue.2.6.11.js"></script>
<script>
Vue.component('my-component', {
template: '#my-component',
data: function () {
return {
cptMsg: "子组件的内容"
}
}
});
var app = new Vue({
el: '#app',
data: {
appMsg: "父组件的内容"
}
});
</script>
</body>
但是,这种方式,x-template
中无法识别父组件和子组件中的变量
手动挂载实例
我们现在所创建的实例都是通过new Vue()的形式创建出来的。在一些非常特殊的情况下,我们需要动态地去创建Vue实例,Vue提供了Vue.extend和$mount 两个方法来手动挂载一个实例。
Vue.extend是基础Vue构造器,创建一个“子类”,参数是一个包含组件选项的对象。
如果Vue实例在实例化时没有收到el选项,它就处于“未挂载”状态,没有关联的DOM元素。可以使用$mount()手动地挂载一个未挂载的实例。这个方法返回实例自身,因而可以链式调用其他实例方法。
动挂载实例(组件)是一种比较极端的高级用法,在业务中几乎用不到,只在开发一些复杂的独立组件时可能会使用,所以只做了解就好。
示例:数组输入框
数字输入框只能输入数字,而且有两个快捷按钮,可以直接减 1或加1。除此之外,还可以设置初始值、最大值、最小值,在数值改变时,触发一个自定义事件来通知父组件。
- index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 示例:数字输入框</title>
</head>
<body>
<div id="app">
<input-number v-model="value" :max="10" :min="0" @on-change="onValueChange"></input-number>
</div>
<script src="../../lib/vue.2.6.11.js"></script>
<script src="input-number.js"></script>
<script src="index.js"></script>
</body>
</html>
- index.js
var app = new Vue({
el: '#app',
data: {
value: 0
},
methods: {
onValueChange: function () {
console.log('onValueChange->', this.value);
}
}
});
- input-number.js
Vue.component('input-number', {
template: '
<div class="input-number">
<input
type="text"
:value="currentValue"
@change="handleChange">
<button
@click="handleDown"
:disabled="currentValue <= min">-</button>
<button
@click="handleUp"
:disabled="currentValue >= max">+</button>
</div>',
props: {
max: {
type: Number,
default: Infinity
},
min: {
type: Number,
default: -Infinity
},
value: {
type: Number,
default: 0
}
},
data: function () {
return {
currentValue: this.value
}
},
methods: {
handleDown: function () {
if (this.currentValue <= this.min) return;
this.currentValue -= 1;
},
handleUp: function () {
if (this.currentValue >= this.max) return;
this.currentValue += 1;
},
updateValue: function (val) {
if (val > this.max) val = this.max;
if (val < this.min) val = this.min;
this.currentValue = val;
},
handleChange: function (event) {
var val = event.target.value.trim();
var max = this.max;
var min = this.min;
if (isValueNumber(val)) {
val = Number(val);
this.currentValue = val;
if (val > max) {
this.currentValue = max;
} else if (val < min) {
this.currentValue = min;
}
} else {
event.target.value = this.currentValue;
}
}
},
watch: {
currentValue: function (val) {
this.$emit('input', val);
this.$emit('on-change', val);
},
value: function (val) {
this.updateValue(val);
}
},
mounted: function () {
this.updateValue(this.value);
}
});
watch
watch选项用来监听某个prop或data的改变,当它们发生变化时,就会触发watch配置的函数
在本例中,我们要监听两个量:value和currentValue。监听value是要知晓从父组件修改了value,监听currentValue是为了当currentValue 改变时,更新value。
watch监听的数据的回调函数有2个参数可用,第一个是新的值,第二个是旧的值,这里没有太复杂的逻辑,就只用了第一个参数。
input事件与v-model
监听currentValue的回调里,this.$emit('input', val)是在使用v-model时改变value的;this.$emit('on-change', val)是触发自定义事件on-change
组件的为什么不用v-model进行双向绑定
这里绑定的currentValue也是单向数据流,并没有用v-model,所以在输入时,currentValue的值并没有实时改变。如果输入的不是数字(比如英文和汉字等),就将输入的内容重置为之前的currentValue。如果输入的是符合要求的数字,就把输入的值赋给currentValue。
示例:Tab组件
- index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 示例:Tab组件</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="app" v-cloak>
<tabs v-model="activeKey">
<pane label="标签一" name="1">
标签一的内容
</pane>
<pane label="标签二" name="2">
标签二的内容
</pane>
<pane label="标签三" name="3">
标签三的内容
</pane>
</tabs>
</div>
<script src="../../lib/vue.2.6.11.js"></script>
<script src="pane.js"></script>
<script src="tab.js"></script>
<script src="index.js"></script>
</body>
</html>
- index.js
var app = new Vue({
el: '#app',
data: {
activeKey: '1'
}
});
- tabs.js
Vue.component('tabs', {
template: '
<div class="tabs">
<div class="tabs-bar">
<div class="tabs-bar">
<div :class="tabCls(item)" v-for="(item, index) in navList" @click="handleChange(index)">
{{ item.label }}
</div>
</div>
</div>
<div class="tabs-content">
<slot></slot>
</div>
</div>',
props: {
value: {
type: [String, Number]
}
},
data: function () {
return {
// 用于渲染 tabs的标题
navList: [],
// 因为不能修改 value,所以复制一份自己维护
currentValue: this.value,
}
},
methods: {
getTabs() {
// 通过遍历子组件,得到所有的pane组件
return this.$children.filter(function (item) {
return item.$options.name === 'pane';
});
},
updateNav() {
this.navList = [];
// 设置对this的引用,在function 回调里,this指向的并不是Vue实例
var _this = this;
this.getTabs().forEach(function (pane, index) {
_this.navList.push({
label: pane.label,
name: pane.name || index
});
// 如果没有给pane 设置 name,默认设置它的索引
if (!pane.name) pane.name = index;
// 设置当前选中的tab的索引,在后面介绍
if (index === 0) {
if (!_this.currentValue) {
_this.currentValue = pane.name || index;
}
}
});
this.updateStatus();
},
updateStatus() {
var tabs = this.getTabs();
var _this = this;
// 显示当前选中的tab对应的pane组件,隐藏没有选中的
tabs.forEach(function (tab) {
return tab.show = tab.name === _this.currentValue;
})
},
tabCls: function (item) {
return [
'tabs-tab',
{
//给当前选中的tab 加一个class
'tabs-tab-active': item.name === this.currentValue
}
]
},
// 点击 tab 标题时触发
handleChange: function (index) {
var nav = this.navList[index];
var name = nav.name;
// 改变当前选中的tab ,并触发下面的watch
this.currentValue = name;
// 更新value
this.$emit('input', name);
// 触发一个自定义事件,供父级使用
this.$emit('on-click', name);
}
},
watch: {
value: function (val) {
this.currentValue = val;
},
currentValue: function () {
//在当前选中的tab 发生变化时,更新pane的显示状态
this.updateStatus();
}
}
})
- pane.js
Vue.component('pane', {
name: 'pane',
template: '
<div class="pane" v-show="show">
<slot></slot>
</div>',
props: {
name: {
type: String
},
label: {
type: String,
default: ''
}
},
data: function () {
return {
show: true
}
}
, methods: {
updateNav() {
this.$parent.updateNav();
}
},
watch: {
label() {
this.updateNav();
}
},
mounted() {
this.updateNav();
}
});
- style.css
[v-cloak] {
display: none;
}
.tabs{
font-size: 14px;
color: #657180;
}
.tabs-bar:after{
content: '';
display: block;
100%;
height: 1px;
background: #d7dde4;
margin-top: -1px;
}
.tabs-tab{
display: inline-block;
padding: 4px 16px;
margin-right: 6px;
background: #fff;
border: 1px solid #d7dde4;
cursor: pointer;
position: relative;
}
.tabs-tab-active{
color: #3399ff;
border-top: 1px solid #3399ff;
border-bottom: 1px solid #fff;
}
.tabs-tab-active:before{
content: '';
display: block;
height: 1px;
background: #3399ff;
position: absolute;
top: 0;
left: 0;
right: 0;
}
.tabs-content{
padding: 8px 0;
}