组件定义与使用
Vue.component 原理
- 内部会调用 Vue.extend 返回一个组件的构造函数,通过 new 这个构造函数产生实例,将该实例挂载到 Vue.options.components 上,这也说明所有的全局组件最终都会挂载到这个变量上;
- 在组件中调用全局组件:组件 options 会和全局 options 进行合并拿到全局组件的实例,组件的合并策略是 通过原型链找到全局组件;
创建全局组件
<body>
<div id="app">
<my-button></my-button>
<my-button></my-button>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
<script type="text/javascript">
window.onload = function () {
Vue.component('myButton', {
template: '<div>{{msg}}</div>',
data() {
return {
msg: '你好'
}
}
})
var app = new Vue({
el: '#app',
data: { }
})
}
</script>
创建局部组件
<body>
<div id="app">
<my-button></my-button>
<my-button></my-button>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
<script type="text/javascript">
window.onload = function () {
var app = new Vue({
el: '#app',
data: {
},
components: {
'my-button': {
template: '<div>{{msg}}</div>',
data() {
return {
msg: 'aaa'
}
}
}
}
})
}
</script>
定义组件名的方式
-
使用 kebab-case (短横线分隔命名);
-
使用 PascalCase (首字母大写命名:驼峰);
组件的好处
-
可复用,提高开发效率;
-
方便后期的维护和修改;
-
可以减少渲染;
组件缓存
受 keep-alive 的影响,其内部所有嵌套的组件都具有两个生命周期钩子函数,分别是 activated 和 deactivated,它们分别在组件激活和失活时触发;
第一次 activated 触发是在 mounted 之后;
控制缓存的刷新可以控制 component 的 key 来实现;
非缓存组件
在来回切换组件后,组件状态会丢失,即会重新渲染组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
</head>
<body>
<div id="app">
<button @click="currentComp='component1'">组件一</button>
<button @click="currentComp='component2'">组件二</button>
<br>
<br>
<component :is="currentComp"></component>
</div>
</body>
<script>
let component1 = {
data: function () {
return {
count: 1,
}
},
template: '<button style="background-color: #13E8E9" @click="count++">累加{{count}}</button>',
};
let component2 = {
data: function () {
return {
count: 1,
}
},
template: '<button style="background-color: red" @click="count++">累加{{count}}</button>',
};
new Vue({
el: '#app',
data: {
currentComp: component1,
},
methods: {},
//局部注册组件
components: {
'component1': component1,
'component2': component2,
}
});
</script>
</html>
缓存组件
来回切换不会丢失状态
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
</head>
<body>
<div id="app">
<button @click="currentComp=component1">组件一</button>
<button @click="currentComp=component2">组件二</button>
<br>
<br>
<!--
可以通过 include 和 exclude prop 来定制该行为;
这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是包含这两种类型的一个数组
-->
<!--
我们可以通过传入 max prop 来限制可被缓存的最大组件实例数;
<KeepAlive> 的行为在指定了 max 后类似一个 LRU 缓存:如果缓存的实例数量即将超过指定的那个最大数量,则最久没有被访问的缓存实例将被销毁,以便为新的实例腾出空间
-->
<keep-alive>
<component :is="currentComp"></component>
</keep-alive>
</div>
</body>
<script>
let component1 = {
data: function () {
return {
count: 1,
}
},
template: '<button style="background-color: #13E8E9" @click="count++">累加{{count}}</button>',
};
let component2 = {
data: function () {
return {
count: 1,
}
},
template: '<button style="background-color: red" @click="count++">累加{{count}}</button>',
};
new Vue({
el: '#app',
data: {
currentComp: component1,
},
methods: {},
//局部注册组件
components: {
component1,
component2,
}
});
</script>
</html>
设置白名单 include
<!-- 以英文逗号分隔的字符串 -->
<KeepAlive include="a,b">
<component :is="view" />
</KeepAlive>
<!-- 正则表达式 (需使用 `v-bind`) -->
<KeepAlive :include="/a|b/">
<component :is="view" />
</KeepAlive>
<!-- 数组 (需使用 `v-bind`) -->
<KeepAlive :include="['a', 'b']">
<component :is="view" />
</KeepAlive>
设置黑名单 exclude
<!-- 以英文逗号分隔的字符串 -->
<KeepAlive exclude="a,b">
<component :is="view" />
</KeepAlive>
<!-- 正则表达式 (需使用 `v-bind`) -->
<KeepAlive :exclude="/a|b/">
<component :is="view" />
</KeepAlive>
<!-- 数组 (需使用 `v-bind`) -->
<KeepAlive :exclude="['a', 'b']">
<component :is="view" />
</KeepAlive>
设置最大缓存数 max
通过 max 可以设置最大缓存数,当缓存的实例超过该数时,vue 会移除最久没有使用的组件缓存
<KeepAlive :max="10">
<component :is="view" />
</KeepAlive>
组件扩展
逻辑扩展:mixins、extends、Composition API;
内容扩展:slots;
mixins
mixins:当用得比较多的时候,可能会出现多个 mixins 变量名一样时的冲突,而且溯源麻烦,不好维护;
mixins 的 data、methods 优先级低于组件里 data、methods;
mixins 的生命周期钩子函数优先级高于组件里面的生命周期钩子函数;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>mixins 扩展选项</title>
<script type="text/javascript" src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
</head>
<body>
<h1>mixins 扩展选项</h1>
<hr>
<div id="app">
<p>{{num}}</p>
<p><button @click="add">add</button></p>
</div>
</body>
<script>
const mymixin1 = {
created() {
console.log('我是mixin1中的created')
},
methods: {
// 如果方法名一样,只触发构造器里的方法,扩展不触发,混入选项也一样
add: function () {
this.num++
console.log('我是 mixin1 中的方法 add')
}
}
}
const mymixin2 = {
created() {
console.log('我是 mixin2 中的 created')
}
}
var app = new Vue({
el: '#app',
data: {
num: 1
},
created() {
console.log('我是构造器中的 created')
},
mounted() {
console.log('我是构造器 mounted')
},
updated: function () {
console.log('我是构造器触发的 updated')
},
methods: {
add: function () {
this.num++
console.log('我是原生的方法 add')
}
},
mixins: [mymixin1, mymixin2]
});
</script>
</html>
extends
使用基础 Vue 构造器,创建一个 “子类”,参数是一个包含组件选项的对象;
data 选项是特例,需要注意,在 Vue.extend() 中它必须是函数;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue Extends 扩展选项</title>
<script type="text/javascript" src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
</head>
<body>
<h1>Vue Extends 扩展选项</h1>
<hr>
<div id="app">
<p>{{num}}</p>
<p><button @click="add">add</button></p>
</div>
</body>
<script>
var extendObj = {
updated: function () {
console.log('我是扩展的 Update')
},
mounted() {
console.log('我是扩展的 mounted')
},
methods: {
// 如果方法名一样,只触发构造器里的方法,扩展不触发,混入选项也一样
add: function () {
this.num++
console.log('我是扩展出来的方法 add')
}
}
};
var app = new Vue({
el: '#app',
data: {
num: 1
},
created() {
console.log('我是构造器中的 created')
},
mounted() {
console.log('我是构造器 mounted')
},
updated: function () {
console.log('我是构造器触发的 updated')
},
methods: {
add: function () {
this.num++
console.log('我是原生的方法 add')
}
},
// 扩展不能为数组,混入是数组,所以扩展只能一个
extends: extendObj,
});
</script>
</html>
Composition API
面试题
vue 子组件和父组件创建和挂载顺序?
-
创建过程自上而下,挂载过程自下而上,即:
- parent created;
- child created;
- child mounted;
- parent mounted;
-
之所以会这样是因为 vue 创建过程是一个递归过程,先创建父组件,有子组件就会创建子组件,因此创建时先有父组件再有子组件;子组件首次创建时会添加 mounted 钩子到队列,等到 patch 结束再执行它们,可见子组件的 mounted 钩子是先进入到队列中的,因此等到 patch 结束执行这些钩子也先执行;
怎么缓存当前的组件?缓存后怎么更新?
-
开发中缓存组件使用 keep-alive 组件,keep-alive 是 vue 内置组件,keep-alive 包裹动态组件 component 时,会缓存不活动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,防止重复渲染 DOM;
-
结合属性 include 和 exclude 可以明确指定缓存哪些组件或排除缓存指定组件,vue3 中结合 vue-router 时变化较大,之前是 keep-alive 包裹 router-view,现在需要反过来用 router-view 包裹 keep-alive:
<router-view v-slot="{ Component }"> <keep-alive> <component :is="Component"></component> </keep-alive> </router-view>
-
缓存后如果要获取数据,解决方案可以有以下两种:
JavaScriptJavaScript// 第一种 beforeRouteEnter:在有 vue-router 的项目,每次进入路由的时候,都会执行 beforeRouteEnter; beforeRouteEnter(to, from, next){ next(vm => { console.log(vm) // 每次进入路由执行 vm.getData() // 获取数据 }) }
// 第二种 actived:在 keep-alive 缓存的组件被激活的时候,都会执行 actived 钩子; activated(){ this.getData() // 获取数据 }
-
keep-alive 是一个通用组件,它内部定义了一个 map 缓存创建过的组件实例,它返回的渲染函数内部会查找内嵌的 component 组件对应组件的 vnode,如果该组件在 map 中存在就直接返回它,由于 component 的 is 属性 是个响应式数据,因此只要它变化,keep-alive 的 render 函数就会重新执行;
vue2.x✍️ render 函数的应用
上一篇