动态异步组件
异步组件
异步组件会被分割成代码块文件,并按需从服务器上下载组件xx.js
。
<!-- Vue2 -->
AsyncComp: () => import('./xxx'); /* Vue3 */ import { defineAsyncComponent } from 'vue'; const AsyncComp = defineAsyncComponent(() => import('./xxx'));
动态组件
在交互中,组件的渲染是不确定的,根据用户的操作决定渲染哪个组件。
<component :is="currentComponent"></component>
<template>
<div class="login-tab">
<div class="login-nav">
<div v-for="tab in tabs" :key="tab" :class="['nav-item', { active: activeTab === tab }]" @click="switchTab(tab)">{{ tab }}</div>
</div>
<div class="login-component">
<keep-alive>
<component :is="activeComponent"></component>
</keep-alive>
</div>
</div>
</template>
<script>
import AccountLogin from './AccountLogin';
export default {
name: 'MainLogin',
components: {
AccountLogin,
QrcodeLogin: () => import('./QrcodeLogin'),
MobileLogin: () => import('./MobileLogin'),
},
data() {
return {
activeTab: 'Account',
tabs: ['Account', 'Qrcode', 'Mobile'],
};
},
methods: {
switchTab(tab) {
this.activeTab = tab;
},
},
computed: {
activeComponent() {
return this.activeTab.toLowerCase() + '-login';
},
},
};
</script>
keep-alive
被keep-alive
包裹的组件在切换时会缓存组件,保持组件状态,避免频繁渲染带来的性能问题。
- vDOM:虚拟 DOM
- vNode:虚拟节点
- rDOM:真实 DOM
- rNode:真实节点
keep-alive 的原理
用户操作视图导致组件显示变化,被keep-alive
包裹的组件会缓存其vNode
。有缓存的vNode
时,直接用它更新DOM
,无需经过unmounted
,再次激活时不触发mounted
,而是调用activated
,失活时调用deactivated
。
keep-alive 属性
keep-alive
正常匹配组件的name
属性,若无则匹配局部注册的组件名称。
exclude
排除特定组件不被缓存。
<keep-alive exclude="List,Intro">
<component :is="currentComponent"></component>
</keep-alive>
include
仅允许特定组件被缓存。
<keep-alive include="List,Intro">
<component :is="currentComponent"></component>
</keep-alive>
max
限制最多缓存的组件数量。
<keep-alive max="10">
<component :is="currentComponent"></component>
</keep-alive>
正则匹配
使用正则表达式匹配需要缓存的组件。
<keep-alive :include="/^List|Intro$/">
<component :is="currentComponent"></component>
</keep-alive>
常见问题
异步组件无法使用include
属性,虽然被缓存但无法正确显示。对于异步组件,建议使用exclude
属性。
<keep-alive exclude="AsyncComponent">
<component :is="currentComponent"></component>
</keep-alive>
keep-alive 实现
import Comp1 from './components/Comp1';
import Comp2 from './components/Comp2';
import Comp3 from './components/Comp3';
const buttons = document.getElementById('buttons');
const wrapper = document.getElementById('wrapper');
const App = {
components: {
Comp1,
Comp2,
Comp3,
},
cache: {},
init() {
this.bindEvents();
},
mounted(callback) {
callback && callback();
},
activated(callback) {
callback && callback();
},
bindEvents() {
buttons.addEventListener('click', this.handleButtonClick.bind(this), false);
},
handleButtonClick(event) {
const target = event.target;
const tag = target.tagName.toLowerCase();
if (tag === 'span') {
const key = target.dataset.key;
let vNode = this.cache[key];
if (!vNode) {
vNode = this.createVNode(this.components[key]);
if (this.isKeepAlive(wrapper)) {
this.cache[key] = vNode;
}
}
const realNode = this.createRealNode(vNode);
wrapper.innerHTML = '';
wrapper.appendChild(realNode);
if (this.isKeepAlive(wrapper)) {
this.activated(() => {
console.log(`${key} activated`);
});
} else {
this.mounted(() => {
console.log(`${key} mounted`);
});
}
}
},
createVNode(component) {
const { template, name } = component;
const tag = template.match(/<(.+?)>/)[1];
const content = template.match(/>(.+?)</)[1];
return {
tag,
children: content,
mark: name,
};
},
createRealNode(vNode) {
const node = document.createElement(vNode.tag);
node.innerText = vNode.children;
return node;
},
isKeepAlive(wrapper) {
const parentTag = wrapper.parentNode.tagName.toLowerCase();
return parentTag === 'keep-alive';
},
};
App.init();