前端單頁應用微服務化解決方案5 - 路由分發

文章轉發自 alili.tech前端

路由分發式微前端

從應用分發路由到路由分發應用

用這句話來解釋,微前端的路由,再合適不過來.react

路由分發式微前端,即經過路由將不一樣的業務分發到不一樣的、獨立前端應用上。其一般能夠經過 HTTP 服務器的反向代理來實現,又或者是應用框架自帶的路由來解決。 就當前而言,經過路由分發式的微前端架構應該是採用最多、最易採用的 「微前端」 方案。可是這種方式看上去更像是多個前端應用的聚合,即咱們只是將這些不一樣的前端應用拼湊到一塊兒,使他們看起來像是一個完整的總體。可是它們並非,每次用戶從 A 應用到 B 應用的時候,每每須要刷新一下頁面。 -- 引用自phodal 微前端的那些事兒git

模塊加載器那一章的示例代碼,已經很是充分了展現了路由分發應用的步驟.github

在單頁面前端的路由,目前有兩種形式, 一種是全部主流瀏覽器都兼容多hash路由, 基本原理爲url的hash值的改變,觸發了瀏覽器onhashchange事件,來觸發組件的更新redux

還有一種是高級瀏覽器才支持的 History API, 在 window.history.pushState(null, null, "/profile/");的時候觸發組件的更新瀏覽器

// hash 模式,項目路由用的是hash模式會用到該函數
export function hashPrefix(app) {
    return function (location) {
        let isShow = false
        //若是該應用 有多個須要匹配的路勁
        if(isArray(app.path)){
            app.path.forEach(path => {
                if(location.hash.startsWith(`#${path}`)){
                    isShow = true
                }
            });
        }
        // 普通狀況
        else if(location.hash.startsWith(`#${app.path || app.url}`)){
            isShow = true
        }
        return isShow;
    }
}

// pushState 模式
export function pathPrefix(app) {
    return function (location) {
        let isShow = false
        //若是該模塊 有多個須要匹配的路徑
        if(isArray(app.path)){
            app.path.forEach(path => {
                if(location.pathname.indexOf(`${path}`) === 0){
                    isShow = true
                }
            });
        }
        // 普通狀況
        else if(location.pathname.indexOf(`${app.path || app.url}`) === 0){
            isShow = true
        }
        return isShow;
    }
}

// 應用註冊
export async function registerApp(params) {
    // 第三個參數爲,該模塊是否顯示
    singleSpa.registerApplication(params.name,  // 模塊名字
                                  () => SystemJS.import(params.main), // 模塊渲染的入口文件
                                  params.base ? (() => true) : pathPrefix(params) // 模塊顯示的條件
                                  );

}

複製代碼

路由分發應用

當url前綴,與配置中的url前綴保持一致的時候, singleSpa會激活對應的模塊,而後把模塊內容渲染出來.前端框架

應用分發路由

在模塊被激活的時候,模塊會讀取url,再渲染到對的頁面.服務器

這就是微前端路由的路由工做流程架構

微前端路由的挑戰

Hash路由

在目前全部支持spa的前端框架中,都支持了Hash路由. Hash路由都工做大體原理就是: url的Hash值的改變,觸發了瀏覽器onhashchange事件,進而來觸發組件的更新. 全部的前端的框架,都是基於onhashchange來更新咱們的頁面的. 當咱們的架構使用微前端的話,若是選擇hash路由,即可以保證全部的前端技術框架的更新事件都是一致的. 因此使用Hash路由也是最省心的.若是不介意Hash路由中url的 # 字符,在微前端中使用Hash也是推薦的.app

HTML5 History 路由

你們都知道,HTML5中History對象上新增了兩個API (pushState與replaceState). 在這兩個新API的做用下,咱們也是能夠作到頁面無刷新,而且更新頁面的.而且url上不須要出現#號. 保持了最高的美觀度(對於一些人來說). 固然如今幾乎全部的主流SPA技術框架都支持這一特性. 可是問題是,這兩個API在觸發的時候,是沒有一個全局的事件觸發的. 多種技術框架對History路由的實現都不同,就算是技術棧都是 React,他的路由都有好幾個版本.

那咱們如何保證一個項目下,多個技術框架模塊的路由作到協同呢?

只有一個history

前提: 假設咱們全部的項目用的都是React,咱們的路由都在使用着同一個版本.

思路: 咱們是能夠這樣作的,在咱們的base前端模塊(由於他老是第一個加載,也是永遠都不會被銷燬的模塊)中的Store.js, 實例化一個React router的核心庫history,經過消息總線,把這個實例傳入到全部的模塊中. 在每一個模塊的路由初始化的時候,是能夠自定義本身的history的.把模塊的history從新指定到傳入的history. 這樣就能夠作到,全部模塊的路由之間的協同了. 由於當頁面切換的時候,history觸發更新頁面的事件,當全部模塊的history都是一個的時候,全部的模塊都會更新到正確的頁面. 這樣就保證了全部模塊與路由都協同.

若是你看不懂我在講什麼,直接貼代碼吧:

//Base前端模塊的 Store.js
import { createStore, combineReducers } from 'redux'

// react router 的核心庫 history
import createHistory from 'history/createBrowserHistory'

const history = createHistory()

// 傳出去
export const storeInstance = createStore(combineReducers({ namespace: () => 'base' ,history }))

複製代碼
// 應用註冊
export async function registerApp(params) {
    ...

    // history 直接引入進來,用systemjs直接導入實例
    try {
        storeModule = params.store ? await SystemJS.import(params.store) : { storeInstance: null };
    } catch (e) {
        ...
    }
    ...

    // 跟派發器一塊兒放進 customProps 中
    customProps = { store: storeModule, globalEventDistributor: ... };


    // 在註冊的時候傳入 customProps
    singleSpa.registerApplication(params.name, 
                                () => SystemJS.import(params.main), 
                                params.base ? (() => true) : pathPrefix(params), 
                                customProps // 應用註冊的時候,history會包含在 customProps 中,直接注入到模塊中
                                );
}
複製代碼
// React main.js
import React from 'react'
import ReactDOM from 'react-dom'
import singleSpaReact from 'single-spa-react'
import RootComponent from './root.component'

const reactLifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: (spa) => {
    // 在這裏,把history傳入到組件
    return <RootComponent history={spa.customProps.history}/> }, domElementGetter: () => document.getElementById('root') }) ... 複製代碼
// RootComponent
import React from 'react'
import { Provider } from 'react-redux' 
export default class RootComponent extends React.Component {
    render() {
        return <Provider store={this.state.store}> // 在這裏從新指定Router的history <Router history={this.props.history}> <Switch> ... </Switch> </Router> </Provider>
    }
}

複製代碼

以上就是讓全部模塊的路由協同,保證只有一個history的用法

多技術棧模塊路由協同

問題: 用上面的方式是可行的,可是遺憾的是,他的應用場景比較小,只能在單一技術棧,單一路由版本的狀況下使用. 微前端最大的優點之一就是自由選擇技術棧. 在一個項目中,使用多個適合不一樣模塊的技術棧.

思路: 咱們實際上是能夠經過每個模塊對外輸出一個路由跳轉到接口,基於消息總線的派發,讓每個模塊渲染到正確的頁面. 好比 模塊A要跳轉到 /a/b/c ,模塊a先更新到/a/b/c路由的頁面,而後經過消息總線,告訴全部模塊,如今要跳轉到 /a/b/c了. 而後其餘模塊,有/a/b/c這個路由都,就直接跳轉,沒有的就什麼都不作.

咱們能夠這樣作:

// Store.js
import { createStore, combineReducers } from 'redux'
import createHistory from 'history/createBrowserHistory'
const history = createHistory()

// 對外輸出一個to的接口,當一個模塊須要跳轉界面的時候,會向全部的模塊調用這個接口,
// 而後對應的模塊會直接渲染到正確的頁面
function to(state, action) {
  if (action.type !== 'to' ) return { ...state, path: action.path }
  history.replace(action.path)
  return { ...state, path: action.path }
}

export const storeInstance = createStore(combineReducers({ namespace: () => 'base', to }))

export { history }

複製代碼

這是路由跟消息總線的一種完美結合的使用方式,消息總線的潛力還有不少,後續會慢慢說明. 未完待續 ...

相關文章

前端單頁應用微服務化解決方案1 - 思考

前端單頁應用微服務化解決方案2 - Single-SPA

前端單頁應用微服務化解決方案3 - 模塊加載器

前端單頁應用微服務化解決方案4 - 消息總線

前端單頁應用微服務化解決方案5 - 路由分發

前端單頁應用微服務化解決方案6 - 構建與部署

前端單頁應用微服務化解決方案7 - 靜態數據共享

前端單頁應用微服務化解決方案8 - 二次構建

Demo

前端微服務化 Micro Frontend Demo

微前端模塊加載器

微前端Base App示例源碼

微前端子項目示例源碼

相關文章
相關標籤/搜索