书本信息:
<< Vue.js 实战 >> 作者: 梁灏
学习笔记 部分内容摘自书 非原创 侵删
第二章 数据绑定
2.1.4 过滤器
推荐用 computed 来实现
在 {{}} 插值尾部添加一个管道运行算符 |
对数据进行过滤
<div id="app">
{{ data | formatDate }
</div>
<script>
var padDate = function(value) {
return value < 10 ? '0' + value : value;
};
var app = new Vue({
el: '#app',
data: {
date: new Date()
},
filters: {
formatDate: function(value) {
var hours = padDate(date.getHours());
return hours;
}
},
mounted () {
var _this = this;
this.timer = setInterval(function() {
_this.date = new Date();
}, 1000);
},
beforeDestroy () {
if (this.timer) {
clearInterval(this.timer);
}
}
})
</script>
- 串联
{{ message | filterA | filterB }}
- 接收参数
{{ message | filterA('arg1', 'arg2') }}
第三章 计算属性
3.2 计算属性用法
每一个计算属性都包含一个 gettter
和一个 setter
, 在我们需要时, 可以提供一个 setter
函数, 当手动修改计算属性的值就行修改一个普通数据那样时, 就会触发 setter
函数, 执行一些自定义的函数
<div id="app">
姓名: {{ fullName }}
</div>
<script>
var app = new Vue({
el: '#app',
data: {
firstName: 'Jack',
lastName: 'Green'
},
computed: {
fullName: {
// getter
get () {
return this.firstName + ' ' + this.lastName;
},
// setter
set (newValue) {
var names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[names.length - 1];
}
}
}
})
</script>
当执行 app.fullName = 'John Doe';
时, setter
就会被调用, 数据 firstName
和 'lastName' 都会相对更新, 视图也会更新.
第五章 内置指令
5.1.2 v-once
v-once
不需要表达式, 定义它的组件或组件只渲染一次, 包括元素或组件的所有子节点
首次渲染后, 不再岁数据的变化重新渲染, 将被视为静态内容
<span v-once>{{ message }}</span>
5.2.1 key 属性 元素复用
Vue 在渲染元素时出于效率考虑, 会尽可能的复用已有元素而非重新渲染
如果你不希望这样, 可以使用 key
属性, 可以让你自己决定是否要复用元素, key
的值必须唯一
<div id="app">
<template v-if="type === 'name">
<label>用户名: </label>
<input placeholder="输入用户名" key="name-input">
</template>
<template v-else>
<label>邮箱: </label>
<input placeholder="输入邮箱" key="mail-input">
</template>
<button @click="handleToggleClick">切换输入类型</button>
</div>
5.3 列表渲染 v-for
- 数组
<li v-for="(book, index) in books">{{ index }} - {{ book.name }}</li>
- 对象
<li v-for="(value, key, index) in user">{{ index }} - {{ key }}: {{ value }}</li>
- 整数
<span v-for="n in 10">{{ n }} </span>
结果是 1 2 ...... 10
5.3.2 数组更新
Vue 的核心是数据与视图的双向绑定, 当我们修改数组时, Vue 会检测到数据变化, 所以用 v-for 渲染的也会立即更新. Vue 包含了一组观察数组变异的方法, 使用它们改变数组也会触发视图更新.
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
使用以上方法会改变这些方法调用的原始数组, 有些方法不会改变原始数组
- filter()
- concat()
- slice()
它们返回的是一个新数组, 在使用这些非变异方法时, 可以用新数组来替换原始数组
app.books = app.books.filter(function (item) {
return item.name.match(/JavaScript/);
// 对 books 数组做 filter 返回带有 JavaScript 的项
})
Vue 在检测到数组变化时, 并不是直接重新渲染整个列表, 而是最大化地复用 DOM 元素, 替换的数组中, 含有相同元素的项不会被重新渲染, 因此可以大胆地用新数组来替换旧数组, 不用担心性能问题.
需要注意的是, 以下变动的数组中, Vue 是不能检测到的, 也不会触发视图更新
- 通过索引直接设置项, 比如 `app.books[3] = {...}
- 修改数组长度, 比如 `app.books.length = 1
解决第一个问题可以用两种方法
- 用
set
方法
Vue.set(app.books, 3, {
name: 'changedName'
});
// webpack app.&set
this.&set(app.books, 3, {
name: 'changedName'
})
- 用 splice
app.books.splice(3, 1, {
name: 'changedName'
})
第二个问题也可以直接用 splice
来解决
app.books.splice(1);
5.4.2 修饰符
在@绑定的事件后加小圆点 ".", 再更一个后缀来使用修饰符
- .stop
- .prevent
- .capture
- .self
- .once
具体用法
// 阻止单击事件冒泡
<a @click.stop="handle"></a>
// 提交事件不再重载页面
<form @submit.prevent="handle"></form>
// 修饰符可以串联
<a @click.stop.prevent="handle"></a>
// 只有修饰符
<form @submit.prevent></form>
// 添加事件侦听器时使用事件捕获模式
<div @click.capture="handle"></div>
// 只当事件在该元素本身(而不是子元素)触发时触发回调
<div @click.self="handle"></div>
// 只触发一次, 组件同样适用
<div @click.once="handle"></div>
在表单元素上监听键盘事件时, 还可以使用按键修饰符, 比如按下某个键时猜调用方法
// 只有在 keyCode 是13时代用 submit()
<input @keyup.13="submit">
也可以自己配置具体按键
Vue.config.keyCodes.f1 = 112;
// 全局定义后, 就可以使用 @keyup.f1
除了具体的某个 keyCode, 还提供了一些快捷名称
- .enter
- .tab
- .delete (删除和退格)
- .esc
- .space
- .up
- .down
- .left
- .right
这些按键修饰符也可以组合使用, 或者和鼠标一起配合使用
- .ctrl
- .alt
- .shift
- .meta ( Mac 下是 Command, Windows 下是窗口键)
第六章 表单与 v-model
6.3 修饰符
.lazy: 懒加载
在输入框中, v-model 默认实在 input 事件中同步输入框的数据 (除了中文输入法输入的时候), 使用修饰符 .lazy 会转变为在 change 事件中同步
<input v-model.lazy="message">
.number: 将输入的 String 转换为 Number
使用
.number
可以将输入转换为 Number 类型, 否则虽然你输入的是数字, 但是它的类型其实是 String, 在输入输入框中比较有用
<input type="number" v-model.number="message">
.trim: 自动过滤输入的首尾空格
<input type="text" v-model.trim="message">
第七章 组件详解
7.1.2 is 属性挂载组件
Vue 组件的模板在某些情况下会受到 HTML 的限制, 比如 <table> 内规定只允许是 <tr>, <td>, <th> 等这些表格元素, 所以直接在 <table> 内使用组件是无效的, 这种情况下, 可以使用特殊的 is
属性来挂载组件
常见的限制元素还有 <ul>
, <ol>
, <select>
<div id="app">
<table>
<tbody is="my-component"></tbody>
</table>
</div>
7.2.3 数据验证 props
在组件中, 使用选项 props 来声明需要从父级接收的数据, props 的值可以是两种, 一种是字符串数组, 一种是对象
当 prop 需要验证时, 就需要对象写法
一般当你的组件需要提供给别人使用时, 推荐都进行数据验证, 比如某个数据必须是数字类, 如果是 string 就会在控制台弹出警告
props: {
// 必须是数字类型
propA: Number,
// 必须是字符串或者数字
propB: [String, Number],
// 布尔值, 如果没有定义, 默认为 true
propC: {
type: Boolean,
default: true
},
// 数字, 而且是必传
propD: {
type: Number,
required: true
},
// 如果是数组或者对象, 默认值必须是一个函数来返回
propE: {
type: Array,
default: function () {
return [];
}
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10;
}
}
}
验证的 type 类型可以是:
- String
- Number
- Boolean
- Object
- Array
- Function
type 也可以是一个自定义构造器, 使用 instanceof
检测
当 prop 验证失败时, 在开发版本下会在控制台抛出一条警告
7.3.1 自定义事件 v-on .native 监听原生事件, 监听的是该组件的根元素
当给一个 Vue 组件绑定事件的时候需要加上 .native
否则 Vue 会认为这个是一个自定义事件 v-on
来监听子组件的 $emit()
<my-component v-on:click,native="handleClick"></my-component>
7..3.2 自定义组件使用 v-model
<my-component v-model="total"></my-component>
...
methods: {
handleClick () {
this.counter++;
this.$emit('input', this.counter);
}
}
组件 $emit()
的事件名是特殊的 input
, 在使用组件的父级, 并没有在 <my-component> 上使用 @input="handler", 而是直接使用了 v-model 绑定的一个数据 total, 也可以用自定义事件实现
<my-component @input="handleGetTotal"></my-component>
...
methods: {
handleGetTotal (total) {
this.total = total;
}
}
v-model 还可以用来创建自定义的表单输入组件, 进行数据双向绑定
<my-component v-model="total"></my-component>
<button @click="handleReduce">-1</button>
...
<template>
<input :value="value" @input="updateValue">
</template>
...
methods: {
updateValue (event) {
this.$emit('input', event.target.value);
}
}
实现这样一个具有双向绑定的 v-model 组件需要满足下面两个要求
- 接收一个 value 属性
- 在有新的 vaue 时触发 input 事件
7.3.3 非父子组件通信
组件通信几种方式
- $emit() $on() 只能父子组件通信, 子组件 props 接收父组件数据
- bus 空 Vue 实例
- vuex
- 父链和子组件索引
父链
在子组件中, 使用
this.$parent
可以直接访问该组件的父实例或组件, 父组件也可以通过this.$children
访问他所有的子组件, 而且可以递归向上或者向下无限访问, 一直到根实例或最内层的组件
子组件索引
当子组件较多时, 通过
this.$children
来一一遍历出我们需要的一个组件实例是比较困难的, 尤其是当组件动态渲染时, 他们的序列不是固定的. Vue 可以使用特殊的属性ref
来为子组件指定一个索引名称
<div id="app">
<button @click="handleRef">通过ref获取子组件实例</button>
<compoent-a ref="comA"></compoent-a>
</div>
...
methods: {
handleRef () {
var msg = this.$refs.comA.message;
console.log(msg);
}
}
在父组件模板中, 子组件标签上使用 ref
指定一个名称, 并在父组件内通过 this.$refs
来访问指定名称的子组件
注意: $refs
只在组件渲染完成后才填充, 并且他是非响应式的, 它仅仅作为一个直接访问子组件的应急方案, 应当避免在模板或计算属性中使用
7.4.4 作用域插槽
作用域插槽是一种特殊的 slot, 使用一个可以复用的模板替换已渲染元素
// 列表组件, 允许组件自定义应该如何渲染列表每一项
<div id="app">
<my-list :books="books">
// 作用域插槽也可以是具名的 slot
<template slot="book" scope="props">
<li>{{ props.bookName }}</li>
</template>
</my-list>
</div>
<script>
Vue.component('my-list', {
props: {
books: {
type: Array,
default () {
reutrn [];
}
}
},
template: '\
<ul>\
<slot name="book"\
v-for="book in books"\
:book-name="book.name"\
//这里也可以写默认 slot 内容 \
</slot>\
</ul>'
});
var app = new Vue({
el: '#app',
data: {
books: [
{ name: 'abc1'},
{ name: 'abc2'},
{ name: 'abc3'}
]
}
})
</script>
父组件当中 scope="props"
, 这里的 props 只是一个临时变量, 就像 v-for="item in items" 里面的 item 一样. template 可以通过临时变量 props 访问来自子组件插槽的数据
在这个例子当中, 子组件 my-list 接收父级的 prop 数组 books, 并且将它在 name 为 book 的 slot 上使用 v-for, 同时暴露一个变量 bookName
这个作用域插槽的使用场景就是即可以复用子组件的slot, 又可以使 slot 内容不一致. 如果上例还在其他组件内使用, <li> 的内容渲染权是由使用者掌握的, 而数据却可以通过临时变量 (比如 props) 从子组件内获取
7.4.5 访问 slot
this.$slots.name
this.$slots.default
//包括了所有没有被包含在具名 slot 中的节点
7.5.3 动态组件
Vue 提供了一个特殊元素 <component> 来动态地挂载不同的组件, 使用 is 特性来选择要挂载的组件
<div id="aap">
<component :is="currentView"></component>
<button @click="handleChangeView('B')">点击切换到组件B</button>
</div>
<script>
...
components: {
comA: {...},
comB: {...}
},
date: {
currentView: 'comA'
},
methods: {
handleChangeView (component) {
this.currentView = 'com' + component;
}
}
</script>
7.6.1 $nextTick
Vue 中异步更新队列
Vue在观察到数据变化时并不是直接更新 DOM, 而是开启一个队列, 并缓冲在同一事件循环中发生的所有数据改变. 在缓冲时会去除重复数据, 从而避免不必要的计算和 DOM 操作.
然后, 在下一个事件循环 tick 中, Vue 刷新队列并执行实际 (已去重) 工作. 所以如果你用一个 for 循环来动态改变数据100次, 其实它只会应用最后一次改变, 如果没有这种机制, DOM 就要重绘100次, 这显然是一个很大的开销.
Vue 会根据当前浏览器环境优先使用原生的 Promise.then 和 MutationObserver, 如果都不支持, 就会采用 setTimeout 代替.
知道了 Vue 异步更新 DOM 的原理, 上面示例的报错也就不难理解了. 事实上, 在执行 this.showDiv=true; 时, div 仍然还是没有被创建出来, 直到下一个 Vue 事件循环时, 才开始创建. $nextTick 就是用来知道什么时候 DOM 更新完成的
<div id="app">
<div id="div" v-if="showDiv">这是一段文本</div>
<button @click="getText">获取 div 内容</button>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
showDiv: false
},
methods: {
getText () {
this.showDiv = true;
this.$nextTick(function () {
var text = document.getElementById('div').innerHTML;
console.log(text);
});
}
}
})
</script>
这时候点击按钮, 控制台会打印出 div 的内容 "这是一段文本"
理论上, 我们应该不用主动操作DOM, 因为 Vue 的核心思想就是数据驱动 DOM, 但在很多业务里, 我们避免不了会使用一些第三方库, 比如 popper.js, swiper 等, 这些基于原生 js 的库都有创建和更新及销毁的哇证生命周期, 与 Vue 配合使用时, 就要利用好 $nextTick
vue nextTick深入理解-vue性能优化、DOM更新时机、事件循环机制
7.6.3 手动挂载实例
动态地创建 Vue 实例, Vue 提供了 Vue.extend 和 $mount 两个方法来手动挂载一个实例.
Vue.extend 是基础 Vue 构造器, 创建一个 子类 , 参数是一个包含组件选项的对象.
如果 Vue 实例在实例化时没有收到 el
选项, 它就处在 '未挂载' 状态, 没有关联的 DOM 元素. 可以使用 $mount()
手动地挂载一个未挂载的实例. 这个方法返回实例自身, 因而可以链式调用其他实例方法.
<div id="mount-div">
</div>
<script>
var MyComponent = Vue.extend({
template: '<div>Hello: {{ name }}</div>',
data () {
return {
name: 'abc'
}
}
});
new MyComponent().$mount('#mount-div');
</script>
除了这种写法外, 以下两种写法也是可以的
new MyComponent().$mount('#mount-div');
// 同上
new MyComponent({
el: '#mount-div'
});
// 或者, 在文档之外渲染并且随后挂载
var component = new MyComponent().$mount();
document.getElementById('mount-div').appendChild(component.$el);
手动挂载实例 (组件) 是一种比较极端的高级用法, 在业务中几乎用不到, 只在开发一些复杂的独立组件时可能会使用, 这边只做了解.
第八章 自定义指令
8.1 基本用法
exp. 注册一个 v-focus 的指令, 用于在 <input>, <textarea> 元素初始化时自动获得焦点
- 全局注册
Vue.directive('focus', {
//指令选项
});
- 局部注册
var app = new Vue({
el: '#app',
directives: {
focus: {
//指令选项
}
}
})
下面具体介绍自定义指令的各个选项
自定义指令的选项是由几个钩子函数组成的, 每个都是可选的
- bind:
只调用一次, 指令第一次绑定到元素时调用, 用这个钩子函数可以定义一个在绑定时执行一次的初始化动作- inserted:
被绑定元素插入父节点时调用 (父节点存在即可调用, 不必存在于 document 中)- update:
被绑定元素所在的模板更新时调用, 而不论绑定值是否变化. 通过比较更新前后的绑定值, 可以忽略不必要的模板更新- componentUpdated:
被绑定元素所在模板完成一次更新周期时调用- unbind:
只调用一次, 指令与元素解绑时调用
可以根据需求在不同的钩子函数内完成逻辑代码, 例如上面的 v-focus, 我们希望在元素插入父节点时就调用, 可以用 inserted
<div id="app">
<input type="text" v-focus>
</div>
<script>
Vue.directive('focus', {
inserted (el) {
// 聚焦元素
el.focus();
}
});
var app = new Vue({
el: '#app'
})
</script>
浏览器中打开这个页面, input 输入框就自动获得了焦点, 成为可输入状态
每个钩子函数都有几个参数可用, 比如上面我们用到了 el
- 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 钩子中可用
下面是结合了以上参数的一个具体实例
<div id="app">
<div v-test:msg.a.b="message"></div>
</div>
<script>
Vue.directive('test', {
bind (el, binding, vnode) {
var keys = [];
for (var i in vnode) {
keys.push(i);
}
el.innerHTML =
'name: ' + binding.name + '<br>' +
'value: ' + binding.value + '<br>' +
'expression: ' + binding.expression + '<br>' +
'argument: ' + binding.arg + '<br>' +
'modifiers: ' + JSON.stringify(binding.modifiers) + '<br>' +
'vnode keys: ' + keys.join(', ')
}
});
var app = new Vue({
el: '#app',
data: {
message: 'some text'
}
})
</script>
执行后, <div> 的内容会使用 innerHTML 重置
vnode keys:
tag, data, children, text, elm, ns, context, functionalContext, key, componentOptions,
componentInstance, parent, raw, isStatic, isRootInsert, isComment,isCloned, isOnce
在大多数场景下, 我们会在 bind 钩子里绑定一些事件, 比如在 document 上用 addEventListener 绑定, 在 unbind 上用 removeEventListener 解绑, 比较典型的示例就是让这个元素随着鼠标拖拽
如果需要多个值, 自定义指令也可以传入一个 js 对象字面量, 只要是合法类型的 js 表达式都是可以的
<div id="app">
<div v-test="{msg: 'hello', name: 'abc'}"></div>
</div>
<script>
Vue.directive('test', {
bind (el, binding, vnode) {
console.log(binding.value.msg);
console.log(binding.value.name);
}
});
var app = new Vue({
el: '#app'
})
</script>