Vue核心应用(二)

vue MVVM双向绑定 用户可以更改视图

表单: input select radio checkbox textarea

一.v-model的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- input textarea -->
{{text}}
<textarea v-model="text"></textarea>
<hr/>

<!-- select 单选-->
{{selectValue}}
<select v-model="selectValue">
<!-- 默认值 -->
<option value="" disabled>请选择</option><!--初始选中 请选择-->
<option :value="o.id" v-for="o of selectOptions">{{o.value}}</option>
</select>

<!-- select 多选 值是数组-->
{{selectValue_arr}} <!-- [ 1, 2 ] -->
<select v-model="selectValue_arr" multiple>
<!-- 默认值 -->
<option value="" disabled>请选择</option><!--初始选中 请选择-->
<option :value="o.id" v-for="o of selectOptions">{{o.value}}</option>
</select>
<hr/>

<!-- radio男女 分组 根据v-model取对应的值 -->
{{radioValue}}
<input type="radio" value="男" v-model="radioValue">
<input type="radio" value="女" v-model="radioValue">
<hr/>

<!-- checkbox默认值是不选择 true false -->
{{check}}
爱好:
<input type="checkbox" v-model="check" value="游泳">
<input type="checkbox" v-model="check" value="健身">
<hr/>

<!-- 修饰符 -->
<!-- trim 去前后空格 只能输入number 可以限制只能输入数字 -->
<input type="text" v-model.trim="text">
<input type="text" v-model.number="text">

</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: "#app",
data(){
return {
text: 'jf',
selectValue: "",
selectValue_arr: [], // 多选 初始值是数组类型
selectOptions:[
{value:'香蕉',id:1},
{value:'苹果',id:2}
],
radioValue:'男',
check:[],
}
}
})
</script>
</body>
</html>

二.自定义指令

自定义指令就是操作dom元素, 可以把操作dom元素的功能封装到一起。

  • 全局指令和局部指令
  • 编写一个自定义指令

自动获取焦点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<body>
<div id="app">
<!-- 指令由几部分组成 v-model.a.b.c="abc" 这里abc是变量-->
<!-- 要实现的功能:让输入框自动获取焦点 -->
<!-- 全局指令:所有组件 实例都可以使用 -->

<input type="text" v-focus.a.b.c="xxx">
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
// 官方文档有这个例子 获取焦点
// el 代表当前指令元素,可以用来直接操作dom
// binding 绑定属性 (例如上面的.a.b.c xxx)
// vnode 虚拟节点 context上下文,当前指令所在的上下文

Vue.directive('focus',function(el,binding,vnode) { // 此写法相当于 bind + update
// 此方法 默认只在绑定的时候 才会执行 和 更新(只有依赖的数据发生变化)的时候也会重新执行
console.log(el,binding,vnode);
el.focus();
})

// 等同于上面的 bind + update
Vue.directive('focus',{
bind(el,binding,vnode){
console.log('bind~',el,binding,vnode);
el.focus();
},
update(el,binding,vnode){
console.log('update~',el,binding,vnode);
el.focus();
}
})

// 现在希望的是dom节点插到页面上才获取焦点,而不是已绑定就获取焦点 可以如下:
Vue.directive('focus',{
inserted(el,binding,vnode){ // 指令元素插入到页面时执行
console.log('bind~',el,binding,vnode);
el.focus();
}
}) // 或者可以如下:

Vue.directive('focus',{
bind(el,binding,vnode){
Vue.nextTick(()=>{ //下一个事件环
el.focus();
})
}
})

let vm = new Vue({
el: '#app',
data(){
return {
color: 'red'
}
}
})
</script>
</body>

添加一个背景色

现在要给输入框添加一个背景色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<body>
<div id="app">
<input type="text" v-focus.color="color">
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
// el 代表当前指令元素,可以用来直接操作dom
// binding 绑定属性 (例如上面的.a.b.c xxx)
// vnode 虚拟节点 context上下文,当前指令所在的上下文

Vue.directive('focus',{
inserted(el,binding,vnode){
if(binding.modifiers.color){
el.style.background = binding.value;
}
console.log(el,binding,vnode);
el.focus();
},
unbind(){ // 指令销毁的时候 会触发此函数

}
})
// 顺序 bind => inserted => update => unbind
let vm = new Vue({
el: '#app',
data(){
return {
color: 'red'
}
}
})
</script>
</body>

示例

v-click-outside
日历 弹层 获取焦点的时候弹出来, 点弹框和input之外的区域才消失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
input {
border: 1px solid red
}

.content {
width: 100px;
height: 100px;
background: burlywood;
}
</style>
</head>
<body>
<div id="app">
<div v-click-outside> <!--将 它们包起来,点击这个div之外的地方才隐藏,加一个指令-->
<input type="text"> <!-- 获取焦点的时候显示-->
<div class="content" v-if="isShow">content
<button>点我 </button> <!-- 点击的时候显示-->
</div>
</div>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
// 可以实现为 点击时判断是否存在当前的dom中
let vm = new Vue({
el: '#app',
directives:{ // 局部指令 只对当前实例和它的子组件才生效,可以写多个,所以是复数形式
clickOutside: {
bind(el,binding,vnode){ // 只在绑定的时候执行
// 事件委托
el.fn =(e)=>{
// 根据当前点击的元素,判断是不是在div里面
if(el.contains(e.target)){
// 在div里面 现在要调用 methods中的 focus() | bind() ,如何调用
// vnode.context 代表当前指令所在的上下文,这个上下文可以理解为当前的这个vm
console.log("上下文~", vnode.context);
vnode.context['focus']();
}else{
vnode.context['blur']();
}
}
document.addEventListener('click',el.fn)
},
unbind(el){ // 事件绑定必须要解绑
document.removeEventListener('click',el.fn);
}
}
},
data(){
return {
isShow: false
}
},
methods:{
focus(){
this.isShow = true;
},
blur(){
this.isShow = false;
}
}
})
</script>
</body>
</html>

三.watch和computed

为什么总有面试问他们的区别? 先说watch 和 computed内部调用的都是new Watcher

  • 有的时候可以实现相同的功能
    computed 计算 会根据其他的值来算
    watch 监控 监控某个值的变化
    这两个方法都是基于 vm.$watch

  • 区别:
    watch 每次值变化后 都能执行对应的回调
    computed 计算属性 多次取值是有缓存效果的,如果依赖的值变化 才会重新执行
    computed 内部用 defineProperty (get / set)

wacth(监控)

官方解释: 一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<body>
<div id="app">

</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data:{
name: 'jf',
// name: {name:'jf2'},
},
watch:{ // 当前watch这样的写法与下面initWatch() 这样的写法是一样的功能 内部就是遍历 调用 $watch()
// *********************************************************************
name(newValue){
console.log('newValue~', newValue);
}// 浏览器执行 打开console.log 输入 vm.name= '123',改变name的值,可以看到打印line16将新值打出
// 也可以按下面的写法
// *********************************************************************
name:{
handler(newValue){
console.log('newValue~', newValue);
},
immediate: true,// 立即执行handler // 浏览器打开页面 刷新可以看到 console.log中已经打印出了name 的值 jf
deep: true, // 深度监控 浏览器执行,如果不加 改变name 的值line22不能打印,加了,当 vm.name2.name='123'时,可以打印出新值
lazy: true, // 就是computed的实现
}
},// watch 当一个值变化了 可以做一件事(例如调用ajax)
})

</script>
</body>

当数据变化后会执行对应的处理函数 handler

watch功能的内部实现如下: 源码 node_modules/vue/src/core/

1
2
3
4
5
6
7
8
9
10
11
12
function initWatch(watch){
for(let key in watch){
vm.$watch(key,watch[key]);
}
}
initWatch(
{
name(newValue){
console.log('打印新值:',newValue)
}
}
)

computed

计算属性最大的特点 可以缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<body>
<div id="app">

</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data:{
name: 'jf',
},
// computed initComputed原理解释
computed:{
// computed 内部具有缓存 如果依赖的属性没有变化 不会重新执行对应函数
fullname(){ // new Watcher vm.$watch
return this.name;
}
}
})
// computed功能的内部实现如下:
function initComputed(key,handler) {
Object.defineProperty(vm,key,{
get(){
handler();
}
})
}
initComputed('fullname',()=>{
console.log('刷新')
return vm.name + 'jw';
})

// 计算属性最大的特点 可以缓存
vm.fullname; // 刷新浏览器执行
vm.fullname; // 刷新浏览器执行


// 可以看到输出了2次 做缓存 如下:
let dirty = true; // 内部有脏值检测系统
function initComputed(key,handler) {
// 源码中dirty 是通过watcher中来实现自动更新dirty的值
let watcher = new Watcher(vm,handler,function(){},{lazy:true});
Object.defineProperty(vm, key, {
get() {
if (watcher.dirty) { // 取值后会将dirty 变成false
value = handler();
watcher.dirty = false;
}
return value
}
});
}
initComputed('fullname',()=>{
console.log('刷新')
return vm.name + 'jw';
})
// 计算属性最大的特点 可以缓存
vm.fullname
vm.fullname // 运行可以看到 只刷新了一次
vm.name = 'jw'; // 因为line56 fullname返回的值里面依赖这个name的值,所以当name值改变时,如下,内部自动加上下面这句话 如下,将dirty=true即可
dirty = true;
console.log(vm.fullname);

</script>
</body>

computed 示例

(1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<body>
<div id="app">
{{getFullname}}
{{a}}
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: "#app",
data(){
return{
firstName: 'jiafei',
lastName: 'H',
a: 1,
}
},
computed:{
getFullname(){ // 默认这个是get方法 这样写只有get方法
console.log('更新~');
return this.firstName + this.lastName;
}
}// 可以看到 浏览器打开 console.log vm.a=...改变a的值getFullname()里面没有执行,只有改变与之相关的firstName、lastName时才会触发更新
})
</script>
</body>
(2) 全选、非全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<body>
<div id="app">
全选:<input type="checkbox" v-model="checkAll">
<input type="checkbox" v-for="(check,index) in checks" v-model="check.value">
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: "#app",
data(){
return{
checks:[{value: true},{value:false},{value: false}]
}
},
computed:{ // 内部是Object.defineProperty
checkAll:{
get(){ // 如果一个为false就为false
return this.checks.every(check=>check.value);
},
set(newValue){ // 用set 比较少
this.checks.forEach(check => {
check.value = newValue;
});
}
}
}
})
</script>
</body>