# Vue高级特性
# 一、scoped实现原理
在vue文件中的style标签上,有一个特殊的属性:scoped。当一个style标签拥有scoped属性时,它的CSS样式就只能作用于当前的组件,也就是说,该样式只能适用于当前组件元素。通过该属性,可以使得组件之间的样式不互相污染。如果一个项目中的所有style标签全部加上了scoped,相当于实现了样式的模块化。
vue中的scoped属性的效果主要通过PostCSS转译实现,如下是转译前的vue代码:
<template>
<div class="example">hi</div>
</template>
<style scoped>
.example {
color: red;
}
</style>
2
3
4
5
6
7
8
转义后:
<template>
<div class="example" data-v-5558831a>hi</div>
</template>
<style>
.example[data-v-5558831a] {
color: red;
}
</style>
2
3
4
5
6
7
8
即:PostCSS给一个组件中的所有dom添加了一个独一无二的动态属性,然后,给CSS选择器额外添加一个对应的属性选择器来选择该组件中dom,这种做法使得样式只作用于含有该属性的dom——组件内部dom。
# 二、自定义v-model
在input元素中使用v-model实现双向数据绑定,其实就是在输入的时候触发元素的input事件,通过这个语法糖,也能够实现父子组件数据的双向绑定。
组件上的 v-model 默认使用名为 value 的 prop 和名为 input 的事件。
组件的input事件通过组件内部输入框的input事件提交$emit,手动触发。
所以为了保持组件内input的value和input跟组件的value和input一致,组件内的<input>
必须:
- value 使用作为prop的value
- input 绑定到组件的input事件
父组件
<template>
<div id="app">
<Child v-model="number"></Child>
<p>{{number}}</p>
</div>
</template>
<script>
import Child from "./components/Child.vue";
export default {
name: "App",
data() {
return {
number: 0
};
},
components: {
Child
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
子组件,Child.vue
<template>
<div>
<button @click="$emit('input',value+1)">点击</button>
</div>
</template>
<script>
export default {
name: "Child",
props: ["value"]
};
</script>
2
3
4
5
6
7
8
9
10
11
12
通过v-bind把父组件的数据绑定到了子组件的props属性中,而在props上默认用value取值,然后通过$emit触发事件input,因为v-model绑定的事件是input,故在子组件上触发了父组件的input事件,通过触发事件来进行传值,实现了父子组件数据的双向绑定。
# 使用model属性
在子组件里定义model属性,设置prop和event。
父组件
<template>
<div id="app">
<Child v-model="number"></Child>
<p>{{number}}</p>
</div>
</template>
<script>
import Child from "./components/Child.vue";
export default {
name: "App",
data() {
return {
number: 1
};
},
components: {
Child
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
子组件
<template>
<input type="text" :value="value" @input="$emit('change',$event.target.value)" />
</template>
<script>
export default {
name: "Child",
model: {
prop: "value", // 定义父组件传过来的prop名称
event: "change" // 定义子组件向父组件派发的事件名称
},
props: {
value: String // 和上面model里的prop名称一致
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 三、Vue自定义过滤器、自定义指令
# 自定义过滤器
在插值表达式中,如果需要对变量的格式进行处理,可以使用过滤器,过滤器的使用方法如下:
{ { ctime|dateFormat } }
,dateFormat即为过滤器函数,它的第一个参数为ctime的值,传入的是ctime的值,返回一个新的值。过滤器可定义为全局过滤器和局部过滤器,全局过滤器为所有vue实例所共享,局部过滤器为当前实例所私用。
Vue.filter('dataFormat',function(dateStr,pattern=""){
var dt=new Date(dateStr);
var y=dt.getFullYear();
var m=dt.getMonth()+1;
var d=dt.getDate();
// return y+'-'+m+'-'+d;
if (pattern.toLowerCase() === 'yyyy-mm-dd'){
return `${y}-${m}-${d}`;
}else{
var hh=dt.getHours();
var mm=dt.getMinutes();
var ss=dt.getSeconds();
return `${y}-${m}-${d}-${hh}:${mm}:${ss}`;
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 自定义指令
使用Vue.directive()
定义全局指令。
参数1:指令的名称,注意,在定义的时候,指令的名称前面不需要加 v- 前缀,但是调用的时候必须加v-前缀。
参数2:是一个对象,这个对象身上有一些指令相关的函数,这些函数可以在特定的阶段执行相关的函数操作。
Vue.directive('focus', {
bind: function (el) {
// 每当指令绑定到元素上的时候,会立即执行这个 bind 函数,只执行一次
// 注意: 在每个 函数中,第一个参数,永远是 el ,表示 被绑定了指令的那个元素,这个 el 参数,是一个原生的JS对象
// 和样式相关的操作,一般都放在bind函数
},
inserted: function (el) {
// inserted 表示元素 插入到DOM中的时候,会执行 inserted 函数【触发1次】
el.focus()
// 和JS行为有关的操作,最好在 inserted 中去执行,放置 JS行为不生效
},
updated: function (el) {
// 当VNode更新的时候,会执行 updated, 可能会触发多次
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 四、$nextTick
- vue是异步渲染
- data改变之后,dom不会立即更新
- $nextTick会在DOM渲染之后被触发,以获取最新DOM节点
# 五、slot插槽
slot(插槽),简言之就是在组件添加一个占位的空间,在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动替换组件模板中。
# 默认插槽
父组件
<template>
<div class="first-page">
<div class="first-content">
我来自父组件
<child>
<span style="color: red;">我将替换子组件插槽</span>
<!-- 单个插槽,替换子组件匿名插槽 -->
</child>
</div>
</div>
</template>
<script type="text/javascript">
import child from "./components/Child.vue";
export default {
name: "parent",
components: {
// 用来声明页面组件
child
}
};
</script>
<style media="screen">
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
子组件
<template>
<div class="sidebar">
<h1>默认插槽</h1>
<div class="slot">
<slot></slot>
</div>
</div>
</template>
<script>
export default { };
</script>
2
3
4
5
6
7
8
9
10
11
12
# 具名插槽
父组件,通过设置slot属性,即slot="name_slot"
的方式指定要替换哪个插槽。在2.6版本后,使用v-slot:name_slot
指令的方式指定要替换哪个插槽。
# 2.6版本以下
父组件
<template>
<div class="first-page">
<div class="first-content">
我来自父组件
<child>
<span slot="name_slot">我将替换子组件slot name="name_slot"的插槽</span>
</child>
</div>
</div>
</template>
<script type="text/javascript">
import child from "./components/Child.vue";
export default {
name: "parent",
components: {
// 用来声明页面组件
child
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 2.6版本
父组件,具名插槽可以缩写,例如 v-slot:name_slot 可以被重写为 #name_slot
<template>
<div class="first-page">
<div class="first-content">
我来自父组件
<child>
<template v-slot:name_slot>
<span>我将替换子组件slot name="name_slot"的插槽</span>
</template>
</child>
</div>
</div>
</template>
<script type="text/javascript">
import child from "./components/Child.vue";
export default {
name: "parent",
components: {
// 用来声明页面组件
child
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
子组件,通过slot标签设置name属性来给自己取名字<slot name="name_slot"></slot>
。
<template>
<div class="sidebar">
<h2>具名插槽</h2>
<div class="nameSlot">
<slot name="name_slot"></slot>
</div>
</div>
</template>
<script>
export default { };
</script>
2
3
4
5
6
7
8
9
10
11
12
# 作用域插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的。
子组件
<template>
<span>
<!-- 为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 <slot> 元素
的一个 attribute 绑定上去,绑定在 <slot> 元素上的 attribute 被称为插槽 prop -->
<slot :user="user">{{ user.lastName }}</slot>
</span>
</template>
<script>
export default {
data() {
return {
user: {
lastName: "zls",
firstName: "baa"
}
};
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
父组件
<template>
<div class="first-page">
<!-- 使用 v-slot 来定义包含所有插槽 prop 的对象命名为 slotProps,
但你也可以使用任意你喜欢的名字。default表示默认插槽-->
<child>
<template v-slot:default="slotProps">{{ slotProps.user.firstName }}</template>
<!-- 解构插槽的写法 -->
<template v-slot:default="{user}">{{ user.firstName }}</template>
</child>
</div>
</template>
<script type="text/javascript">
import child from "./components/Child.vue";
export default {
name: "parent",
components: {
// 用来声明页面组件
child
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:
<child v-slot="{user}">
{{ user.firstName }}
</child>
2
3
# 六、动态组件、异步组件
# 动态组件
使用is来切换不同的组件,适合需要根据数据动态渲染的场景。
<component v-bind:is="currentTabComponent"></component>
。
currentTabComponent是data里的数据可以动态改变从而切换不同的组件,显示不同的组价内容。
<template>
<div class="first-page">
<component :is="comName"></component>
</div>
</template>
<script>
import Child from "./components/Child";
export default {
data() {
return {
comName: "Child"
};
},
components: {
Child
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 异步组件
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。
- import 函数
- 按需加载,异步加载大组件
<template>
<div class="first-page">
<Child v-if="isShow"></Child>
<button @click="isShow=true">点击加载子组件</button>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false
};
},
components: {
Child: () => import("./components/Child")
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 七、keep-alive缓存组件
使用keep-alive缓存组件的时候不会被销毁。
- 缓存组件
- 频繁切换,不需要重复渲染
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
2
3
# 八、mixin使用
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
// 定义一个混入对象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
export default {
mixins:[myMixin],
data() {
return {
isShow: false
};
},
components: {
Child: () => import("./components/Child")
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mixin的问题:
- 变量来源不明确,不利于阅读
- 多mixin可能会造成命名冲突
- mixin和组件可能出现多对多的关系,复杂度较高
← Vue源码解读(三) Vue3教程 →