微前端实现原理学习总结

247 阅读3分钟

import { registerMicroApps, start } from 'qiankun';

以上是我们主应用引入的乾坤2个api,registerMicroApps 注册子应用,start 启动主应用基座 我们重点看下乾坤在这两个api中主要做了哪些工作 先列出重要的几个步骤:

1、监视路由变化

2、匹配子应用

3、加载子应用

4、渲染子应用

然后再详细讲一下这几个步骤的基本实现原理,用到了哪些api

  1、监视路由变化(hash路由&history路由)

  1. hash路由监视方法为window.onhashchagnge window.addEventListener(‘hashchange’,function(e){ console.log(‘hashchange’,e) })

  2. history路由监视方法就相比而言比较复杂分为A、B、C两个部分,

    A部分是我们点击浏览器前进和后退按钮触发的路由变化;

    B部分就是我们向 history 中塞入一条历史记录例如 :this.$router.push(/orderList?orderNo=76876);

    C部分replaceState,替换history栈中最后一个记录例如:this.$router.push('/index')

 A、history.go、history.back、history.forward 使用popstate事件:window.onpopstate

window.addEventListener('popstate',()=>{
    console.log('popstate');nextRoute=window.location.pathname
    }
)

   B和C、pushState、replaceState  需要通过函数重写的方式进行劫持

const rawPushState=window.history.pushState
window.history.pushState=(...args)=>{
    rawPushState.apply(window.history,args)
    nextRoute=window.location.pathname
    }

const rawReplaceState=window.history.replaceState
    window.history.replaceState=(...args)=>{
    rawReplaceState.apply(window.history,args)
    nextRoute=window.location.pathname
    }

2、匹配子应用

首先在步骤1中监视路由变化的时候我们记录了一个nextRoute(window.location.pathname),也就是要跳转的路由。

其次,在调用registerMicroApps的时候我们传入了一个注册子应用的数组apps 。

const apps=[
  { 
    name: 'app1', 
    entry: 'http://localhost:1111', 
    container: '#appContainer', 
    activeRule: '/app1', 
    props: { data : { store, router } }
  },
  { 
    name: 'app2',
    entry: 'http://localhost:2222', 
    container: '#appContainer', 
    activeRule: '/app2',
    props: { data : store }
  },
]
registerMicroApps(apps);

我们根据nextRoute去 apps 中匹配activeRule即可得到路由变化要匹配的子应用,代码例子:

const app = apps.find(item => getNextRoute().startsWith(item.activeRule))
if (!app) return;

3、加载子应用

加载子应用我们需要那些条件?

1)容器——加载到哪个节点

在步骤2中我们匹配到了子应用app,而app中定义的container:‘#appContaine’就是我们的节点容器如下:

const container = document.querySelector(app.container)

2)要加载的html

我们首先创建一个div,然后使用fetch或者axios也可以,去请求子应用(app.entry)的html放到div中。就得到了要加载的html。

const template = document.createElement('div')

const url=app.entry

const html = fetch(url).then(res=>res.text())

template.innerHTML = html

container.appendChild(template)

  3)加载 script标 签

首先获取所有的script标签的代码放到一个数组 [代码,代码]

因为querySelectorAll得到的是nodeList的伪数组,所以我们用es6的Array.from()转一下。

const scripts=Array.from(template.querySelectorAll('script')).map(script => {
    const src = script.getAttribute('src')
    if (!src) {
        return Promise.resolve(script.innerHTML)
    } else {
        return fetch(src.startsWith('http') ? src :`${url}${src}`).then(res=>res.text())
        }
    })

这样scripts就是我们要获取的子应用的script标签集合了,然后加载使用eval()函数来加载执行字符串的script。

scripts.forEach(code => {
    eval(code)
})

4、渲染子应应用

1)怎么拿到子应用的生命周期钩子(bootstrap、mount、unmount)

子应用打包成了一个umd格式的库

Umd是什么格式?兼容不同的模块规范(commonjs,AMD)

最后导出的就是window[子应用]=factory()

所以我们使用全局变量window[app1]就能拿到子应用的导出结果

然后我们使app.bootstrap=window[app1].bootstrap

执行即可app.bootstrap && app.bootstrap()

 

以上就是本次微前端学习的一些原理主线总结。