微前端的介绍,大家已经看过很多了,这里就不多做介绍了,直接上手demo,快速入门,连老奶奶都学得会咯!(文末有项目代码)~
初始化项目
- 初始化主应用 初始化项目(安装的时候勾选上vue router、vuex)
vue create main-app
安装UI框架element-ui、微前端框架qiankun
npm i element-ui qiankun --save
- 初始化子应用 初始化项目(安装的时候勾选上vue router、vuex)
vue create child-app
安装UI框架element-ui
npm i element-ui --save
基础版
编写主应用代码
- /src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'vue', // 子应用名称
entry: '//localhost:10000', // 子应用入口
container: '#vue', // 子应用在主应用中显示的位置
activeRule: '/vue', // 子应用激活的路由规则
}
]);
start();
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
代码详解:
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
引入element-ui
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'vue', // 子应用名称
entry: '//localhost:10000', // 子应用入口
container: '#vue', // 子应用在主应用中显示的位置
activeRule: '/vue', // 子应用激活的路由规则
}
]);
start();
微应用有两种加载方式,一种是基于路由的加载方式,另外一种是基于手动的加载方式,这里我们使用第一种。
使用registerMicroApps进行配置。registerMicroApps(apps, lifeCycles?)接收两个参数,第一个必填项,填入微应用的注册信息。第二个为生命周期钩子,选填,可填入的项目有beforeLoad、beforeMount、afterMount、beforeUnmount、afterUnmount,这里为了降低demo的理解难度,先不填入了,只使用第一个参数。
使用start开启qiankun
- /src/App.vue
<template>
<div>
<el-menu :router="true" mode="horizontal" default-active="/">
<el-menu-item index="/">Home</el-menu-item>
<el-menu-item index="/about">About</el-menu-item>
<el-menu-item index="/vue">Vue</el-menu-item>
</el-menu>
<router-view></router-view>
<div id="vue"></div>
</div>
</template>
代码详解:
<el-menu :router="true" mode="horizontal" default-active="/">
<el-menu-item index="/">Home</el-menu-item>
<el-menu-item index="/about">About</el-menu-item>
<el-menu-item index="/vue">Vue</el-menu-item>
</el-menu>
element-ui提供的菜单栏
<router-view></router-view>
作为主应用本身的路由组件容器
<div id="vue"></div>
作为子应用的容器,跟main.js里面registerMicroApps注册的container要一致。
- /src/views/Home.vue
<template>
<div class="home">
<h1>主应用 Home</h1>
</div>
</template>
- /src/views/About.vue
<template>
<div class="about">
<h1>主应用 About</h1>
</div>
</template>
编写子应用代码
- /src/main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
Vue.config.productionTip = false
let instance;
function render() {
// 加载vue实例
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
}
// 独立运行微应用
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 被主应用使用时
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
export async function bootstrap() {}
export async function mount(props) {
render(props)
}
export async function unmount(props) {
instance.$destroy()
instance = null
}
代码详解:
let instance;
function render() {
// 加载vue实例
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
}
这里要使用一个render函数将原有的实例Vue的过程包裹起来是有两个原因,一个是我们需要在主应用需要渲染子应用的时候再去渲染子应用,另外是我们也需要支持可以独立运行子应用,因此需要让渲染这个过程变得具备可控性,需要渲染的时候再去调用render函数进行渲染
// 独立运行微应用
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
可以window.__POWERED_BY_QIANKUN__通过这个全局变量来区分当前是否运行在qiankun的主应用的上下文中,如果该变量不存在,则证明当前子应用应当独立运行,所以调用render函数进行渲染
// 被主应用使用时
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
我们的主应用使用的端口是8080,而我们的子应用使用的是10000,如果不设置这个,我们对子应用路由的请求会以http://localhost:8080开头,而不是以正确的http://localhost:10000开头。
runtime publicPath 主要解决的是微应用动态载入的 脚本、样式、图片 等地址不正确的问题。
export async function bootstrap() {}
export async function mount() {
render()
}
export async function unmount() {
instance.$destroy()
}
根据规定,微应用需要在自己的入口js文件导出bootstrap、mount、unmount三个生命周期钩子,以供主应用在适当的时机调用。
bootstrap只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用mount钩子,不会再重复触发bootstrap。通常我们可以在这里做一些全局变量的初始化,比如不会在unmount阶段被销毁的应用级别的缓存等。- 应用每次进入都会调用
mount方法,通常我们在这里触发应用的渲染方法 - 应用每次
切出/卸载会调用的方法,通常在这里我们会卸载微应用的应用实例 因此,我们在mount里面进行子应用的渲染,在unmount里面进行子应用的卸载。
- /src/App.vue
<template>
<div class="child-app">
<el-menu :router="true" default-active="/" class="child-menu">
<el-menu-item index="/">Home</el-menu-item>
<el-menu-item index="about">About</el-menu-item>
</el-menu>
<router-view />
</div>
</template>
<style scoped>
.child-app {
width: 400px;
display: flex;
border: 1px solid black;
margin-top: 20px;
padding: 10px;
}
.child-menu {
margin-right: 20px;
}
</style>
- /src/view/Home.vue
<template>
<div class="home">
<h2>子应用 Home</h2>
</div>
</template>
- /src/views/About.vue
<template>
<div class="about">
<h2>子应用 About</h2>
</div>
</template>
- /src/router/index.js
//...
const router = new VueRouter({
mode: 'history',
base: window.__POWERED_BY_QIANKUN__ ? '/vue' : process.env.BASE_URL,
routes
})
//...
作为子应用运行时,子应用的路由生效规则应该和跟主应用main.js里面registerMicroApps注册的activeRule要一致,所以设置base为'/vue'
- /vue.config.js
module.exports = {
devServer: {
port: 10000,
headers: {
'Access-Control-Allow-Origin': '*' // 主应用获取子应用时跨域响应头
}
},
configureWebpack: {
output: {
library: `vue`,
libraryTarget: 'umd',
}
}
}
由于是8080端口到10000端口的访问,我们需要配置跨域允许,另外,为了让主应用能正确识别微应用暴露出来的一些信息,还需要多加些webpack的打包配置。
开跑
至此,主应用和子应用准备完毕,开跑!
- 测试子应用
打开http://localhost:10000
如果你也能看到如下的效果,证明子应用可以独立运行了
- 测试主应用
打开http://localhost:8080
如果你也能看到如下的效果,证明主应用、子应用都完美运行了
通信版
从上面的效果来看,我们已经实现微前端的基础使用了,成功的将子应用嵌入到主应用中去,但是,我们在项目中,经常会涉及到一些需要主应用和子应用通信的场景,由于两个都是vue项目,所以我们使用vuex来实现通信。
编写主应用
- /src/main.js
// ...
registerMicroApps([
{
name: 'vue', // app name registered
entry: '//localhost:10000',
container: '#vue',
activeRule: '/vue',
// 多加了下面这段
props: {
initState: store.state
}
}
]);
// ...
把主应用的state,使用props,传递给子应用,子应用可在导出的mount函数里面接收
- /src/store/index.js
// ...
mutations: {
addNum(state, n) {
state.num += n
}
}
// ...
- /src/App.vue
<template>
<div>
<el-menu :router="true" mode="horizontal" default-active="/">
<el-menu-item index="/">Home</el-menu-item>
<el-menu-item index="/about">About</el-menu-item>
<el-menu-item index="/vue">Vue</el-menu-item>
</el-menu>
<h1>主应用的state.num:{{$store.state.num}}</h1>
<button @click="addNum(10)">主应用增加10</button>
<router-view></router-view>
<div id="vue"></div>
</div>
</template>
<script>
export default {
methods: {
addNum(n) {
this.$store.commit('addNum', 10)
}
}
}
</script>
编写子应用
- /src/main.js
// ...
function render(props = {}) {
if (!store.hasModule('global')) {
// 当独立运行微应用时,state需要初始化赋值,否则会报错
const initState = props.initState || {
num: 0
}
// 将父应用的数据存储到子应用中,命名空间固定为global
const globalModule = {
namespaced: true,
state: initState,
mutations: {
addNum(state, n) {
state.num += n
}
},
};
// 注册一个动态模块
store.registerModule('global', globalModule);
}
// 加载vue实例
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
}
// ...
export async function mount(props) {
render(props)
}
// ...
mount接收主应用传递过来的数据,将其传递给render函数render函数先判断子应用store里面有没有注册过global模块,如果没有的话,使用props接收主应用传递过来的state,并且将其作为global模块的state数据来源,动态注册。之后,我们使用global模块,实际上就是使用父传递过来的数据,由此实现数据通信。
- /src/views/Home.vue
<template>
<div class="home">
<h2>子应用 Home</h2>
<h2>主应用的state.num:{{$store.state.global.num}}</h2>
<button @click="addNum(10)">主应用增加20</button>
</div>
</template>
<script>
export default {
methods: {
addNum() {
this.$store.commit('global/addNum', 20)
}
}
}
</script>
开跑
至此,主应用和子应用准备完毕,开跑!
- 测试子应用
打开http://localhost:10000
可以看到,此时子应用中的按钮也是可以正常点击的,这是因为在render函数中,我们给了其初始值的缘故
const initState = props.initState || {
num: 0
}
- 测试主应用
打开http://localhost:8080
可以看到,无论是点击主应用里面的按钮,还是子应用里面的按钮,数据都能做到同步响应式的更新,这证明我们的数据通信已经成功实现!
项目代码
github.com/xiezijie439… 如果对你有帮助的话,麻烦给我一颗小星星~