国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁(yè) > 營(yíng)銷(xiāo)資訊 > 網(wǎng)站運(yùn)營(yíng) > 為了實(shí)踐微前端,重構(gòu)了自己的導(dǎo)航網(wǎng)站

為了實(shí)踐微前端,重構(gòu)了自己的導(dǎo)航網(wǎng)站

時(shí)間:2023-05-23 08:21:01 | 來(lái)源:網(wǎng)站運(yùn)營(yíng)

時(shí)間:2023-05-23 08:21:01 來(lái)源:網(wǎng)站運(yùn)營(yíng)

為了實(shí)踐微前端,重構(gòu)了自己的導(dǎo)航網(wǎng)站:筆者早期開(kāi)發(fā)了一個(gè)導(dǎo)航網(wǎng)站,一直想要重構(gòu),因?yàn)閼型狭撕脦啄?,終于,在了解到微前端大法后下了決心,因?yàn)楣ぷ魃弦恢睕](méi)有機(jī)會(huì)實(shí)踐,沒(méi)辦法,只能用自己的網(wǎng)站試試,思來(lái)想去,訪問(wèn)量最高的也就是這個(gè)破導(dǎo)航網(wǎng)站了,于是用最快的時(shí)間完成了基本功能的重構(gòu),然后準(zhǔn)備通過(guò)微前端來(lái)擴(kuò)展網(wǎng)站的功能,比如天氣、待辦、筆記、秒表計(jì)時(shí)等等,這些功能屬于附加的功能,可能會(huì)越來(lái)越多,所以不能和導(dǎo)航本身強(qiáng)耦合在一起,需要做到能獨(dú)立開(kāi)發(fā),獨(dú)立上線,所以使用微前端再合適不過(guò)了。

另外,因?yàn)橛行┕δ芸赡芊浅:?jiǎn)單,比如秒表計(jì)時(shí),單獨(dú)創(chuàng)建一個(gè)項(xiàng)目顯得沒(méi)有必要,但是又不想直接寫(xiě)在導(dǎo)航的代碼里,最好是能直接通過(guò)Vue單文件來(lái)開(kāi)發(fā),然后頁(yè)面上動(dòng)態(tài)的進(jìn)行加載渲染,所以會(huì)在微前端方式之外再嘗試一下動(dòng)態(tài)組件。

本文內(nèi)的項(xiàng)目都使用Vue CLI創(chuàng)建,Vue使用的是3.x版本,路由使用的都是hash模式

小程序注冊(cè)

為了顯得高大上一點(diǎn),擴(kuò)展功能我把它稱為小程序,首先要實(shí)現(xiàn)的是一個(gè)小程序的注冊(cè)功能,詳細(xì)來(lái)說(shuō)就是:

1.提供一個(gè)表單,輸入小程序名稱、描述、圖標(biāo)、url、類(lèi)型(微前端方式還需要配置激活規(guī)則,組件方式需要配置樣式文件的url),如下:



2.導(dǎo)航頁(yè)面上顯示注冊(cè)的小程序列表,點(diǎn)擊后渲染對(duì)應(yīng)的小程序:



微前端方式

先來(lái)看看微前端的實(shí)現(xiàn)方式,筆者選擇的是qiankun框架。

主應(yīng)用

主應(yīng)用也就是導(dǎo)航網(wǎng)站,首先安裝qiankun

npm i qiankun -S主應(yīng)用需要做的很簡(jiǎn)單,注冊(cè)微應(yīng)用并啟動(dòng),然后提供一個(gè)容器給微應(yīng)用掛載,最后打開(kāi)指定的url即可。

因?yàn)槲?yīng)用列表都存儲(chǔ)在數(shù)據(jù)庫(kù)里,所以需要先獲取然后進(jìn)行注冊(cè),創(chuàng)建qiankun.js文件:

// qiankun.jsimport { registerMicroApps, start } from 'qiankun'import api from '@/api';// 注冊(cè)及啟動(dòng)const registerAndStart = (appList) => { // 注冊(cè)微應(yīng)用 registerMicroApps(appList) // 啟動(dòng) qiankun start()}// 判斷是否激活微應(yīng)用const getActiveRule = (hash) => (location) => location.hash.startsWith(hash);// 初始化小程序export const initMicroApp = async () => { try { // 請(qǐng)求小程序列表數(shù)據(jù) let { data } = await api.getAppletList() // 過(guò)濾出微應(yīng)用 let appList = data.data.filter((item) => { return item.type === 'microApp'; }).map((item) => { return { container: '#appletContainer', name: item.name, entry: item.url, activeRule: getActiveRule(item.activeRule) }; }) // 注冊(cè)并啟動(dòng)微應(yīng)用 registerAndStart(appList) } catch (e) { console.log(e); }}一個(gè)微應(yīng)用的數(shù)據(jù)示例如下:

{ container: '#appletContainer', name: '后閣樓', entry: 'http://lxqnsys.com/applets/hougelou/', activeRule: getActiveRule('#/index/applet/hougelou')}可以看到提供給微應(yīng)用掛載的容器為#appletContainer,微應(yīng)用的訪問(wèn)urlhttp://lxqnsys.com/applets/hougelou/,注意最后面的/不可省略,否則微應(yīng)用的資源路徑可能會(huì)出現(xiàn)錯(cuò)誤。

另外解釋一下激活規(guī)則activeRule,導(dǎo)航網(wǎng)站的url為:http://lxqnsys.com/d/#/index,微應(yīng)用的路由規(guī)則為:applet/:appletId,所以一個(gè)微應(yīng)用的激活規(guī)則為頁(yè)面urlhash部分,但是這里activeRule沒(méi)有直接使用字符串的方式:#/index/applet/hougelou,這是因?yàn)楣P者的導(dǎo)航網(wǎng)站并沒(méi)有部署在根路徑,而是在/d目錄下,所以#/index/applet/hougelou這個(gè)規(guī)則是匹配不到http://lxqnsys.com/d/#/index/applet/hougelou這個(gè)url的,需要這樣才行:/d/#/index/applet/hougelou,但是部署的路徑有可能會(huì)變,不方便直接寫(xiě)到微應(yīng)用的activeRule里,所以這里使用函數(shù)的方式,自行判斷是否匹配,也就是根據(jù)頁(yè)面的location.hash是否是以activeRule開(kāi)頭的來(lái)判斷,是的話代表匹配到了。

微應(yīng)用

微應(yīng)用也就是我們的小程序項(xiàng)目,根據(jù)官方文檔的介紹Vue 微應(yīng)用,首先需要在src目錄新增一個(gè)public-path.js

// public-path.jsif (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}然后修改main.js,增加qiankun的生命周期函數(shù):

// main.jsimport './public-path';import { createApp } from 'vue'import App from './App.vue'import router from './router'let app = nullconst render = (props = {}) => { // 微應(yīng)用使用方式時(shí)掛載的元素需要在容器的范圍下查找 const { container } = props; app = createApp(App) app.use(router) app.mount(container ? container.querySelector('#app') : '#app')}// 獨(dú)立運(yùn)行時(shí)直接初始化if (!window.__POWERED_BY_QIANKUN__) { render();}// 三個(gè)生命周期函數(shù)export async function bootstrap() { console.log('[后閣樓] 啟動(dòng)');}export async function mount(props) { console.log('[后閣樓] 掛載'); render(props);}export async function unmount() { console.log('[后閣樓] 卸載'); app.unmount(); app = null;}接下來(lái)修改打包配置vue.config.js

module.exports = { // ... configureWebpack: { devServer: { // 主應(yīng)用需要請(qǐng)求微應(yīng)用的資源,所以需要允許跨域訪問(wèn) headers: { 'Access-Control-Allow-Origin': '*' } }, output: { // 打包為umd格式 library: `hougelou`, libraryTarget: 'umd' } }}最后,還需要修改一下路由配置,有兩種方式:

1.設(shè)置base

import { createRouter, createWebHashHistory } from 'vue-router';let routes = routes = [ { path: '/', name: 'List', component: List }, { path: '/detail/:id', name: 'Detail', component: Detail },]const router = createRouter({ history: createWebHashHistory(window.__POWERED_BY_QIANKUN__ ? '/d/#/index/applet/hougelou/' : '/'), routes})export default router這種方式的缺點(diǎn)也是把主應(yīng)用的部署路徑寫(xiě)死在base里,不是很優(yōu)雅。

2.使用子路由

import { createRouter, createWebHashHistory } from 'vue-router';import List from '@/pages/List';import Detail from '@/pages/Detail';import Home from '@/pages/Home';let routes = []if (window.__POWERED_BY_QIANKUN__) { routes = [{ path: '/index/applet/hougelou/', name: 'Home', component: Home, children: [ { path: '', name: 'List', component: List }, { path: 'detail/:id', name: 'Detail', component: Detail }, ], }]} else { routes = [ { path: '/', name: 'List', component: List }, { path: '/detail/:id', name: 'Detail', component: Detail }, ]}const router = createRouter({ history: createWebHashHistory(), routes})export default router在微前端環(huán)境下把路由都作為/index/applet/hougelou/的子路由。

效果如下:



優(yōu)化

1.返回按鈕

如上面的效果所示,微應(yīng)用內(nèi)部頁(yè)面跳轉(zhuǎn)后,如果要回到上一個(gè)頁(yè)面只能通過(guò)瀏覽器的返回按鈕,顯然不是很方便,可以在標(biāo)題欄上添加一個(gè)返回按鈕:

<div class="backBtn" v-if="isMicroApp" @click="back"> <span class="iconfont icon-fanhui"></span></div>const back = () => { router.go(-1);};這樣當(dāng)小程序?yàn)槲?yīng)用時(shí)會(huì)顯示一個(gè)返回按鈕,但是有一個(gè)問(wèn)題,當(dāng)在微應(yīng)用的首頁(yè)時(shí)顯然是不需要這個(gè)返回按鈕的,我們可以通過(guò)判斷當(dāng)前的路由和微應(yīng)用的activeRule是否一致,一樣的話就代表是在微應(yīng)用首頁(yè),那么就不顯示返回按鈕:

<div class="backBtn" v-if="isMicroApp && isInHome" @click="back"> <span class="iconfont icon-fanhui"></span></div>router.afterEach(() => { if (!isMicroApp.value) { return; } let reg = new RegExp("^#" + route.fullPath + "?$"); isInHome.value = reg.test(payload.value.activeRule);});

2.微應(yīng)用頁(yè)面切換時(shí)滾動(dòng)位置恢復(fù)

如上面的動(dòng)圖所示,當(dāng)從列表頁(yè)進(jìn)入到詳情頁(yè)再返回列表時(shí),列表回到了頂部,這樣的體驗(yàn)是很糟糕的,我們需要記住滾動(dòng)的位置并恢復(fù)。

可以通過(guò)把url和滾動(dòng)位置關(guān)聯(lián)并記錄起來(lái),在router.beforeEach時(shí)獲取當(dāng)前的滾動(dòng)位置,然后和當(dāng)前的url關(guān)聯(lián)起來(lái)并存儲(chǔ),當(dāng)router.afterEach時(shí)根據(jù)當(dāng)前url獲取存儲(chǔ)的數(shù)據(jù)并恢復(fù)滾動(dòng)位置:

const scrollTopCache = {};let scrollTop = 0;// 監(jiān)聽(tīng)容器滾動(dòng)位置appletContainer.value.addEventListener("scroll", () => { scrollTop = appletContainer.value.scrollTop;});router.beforeEach(() => { // 緩存滾動(dòng)位置 scrollTopCache[route.fullPath] = scrollTop;});router.afterEach(() => { if (!isMicroApp.value) { return; } // ... // 恢復(fù)滾動(dòng)位置 appletContainer.value.scrollTop = scrollTopCache[route.fullPath];});

3.初始url為小程序url的問(wèn)題

正常在關(guān)閉小程序時(shí)會(huì)把頁(yè)面的路由恢復(fù)至頁(yè)面原本的路由,但是比如我在打開(kāi)小程序的情況下直接刷新頁(yè)面,那么因?yàn)?code>url滿足小程序的激活規(guī)則,所以qiankun會(huì)去加載對(duì)應(yīng)的微應(yīng)用,然而可能這時(shí)頁(yè)面上連微應(yīng)用的容器都沒(méi)有,所以會(huì)報(bào)錯(cuò),解決這個(gè)問(wèn)題可以在頁(yè)面加載后判斷初始路由是否是小程序的路由,是的話就恢復(fù)一下,然后再去注冊(cè)微應(yīng)用:

if (///index//applet///.test(route.fullPath)) { router.replace("/index");}initMicroApp();

Vue組件方式

接下來(lái)看看使用Vue組件的方式,筆者的想法是直接使用Vue單文件來(lái)開(kāi)發(fā),開(kāi)發(fā)完成后打包成一個(gè)js文件,然后在導(dǎo)航網(wǎng)站上請(qǐng)求該js文件,并把它作為動(dòng)態(tài)組件渲染出來(lái)。

簡(jiǎn)單起見(jiàn)我們直接在導(dǎo)航項(xiàng)目下新建一個(gè)文件夾作為小程序的目錄,這樣可以直接使用項(xiàng)目的打包工具,新增一個(gè)stopwatch測(cè)試組件,目前目錄結(jié)構(gòu)如下:



組件App.vue內(nèi)容如下:

<template> <div class="countContainer"> <div class="count">{{ count }}</div> <button @click="start">開(kāi)始</button> </div></template><script setup>import { ref } from "vue";const count = ref(0);const start = () => { setInterval(() => { count.value++; }, 1000);};</script><style lang="less" scoped>.countContainer { text-align: center; .count { color: red; }}</style>index.js用來(lái)導(dǎo)出組件:

import App from './App.vue';export default App// 配置數(shù)據(jù)const config = { width: 450}export { config}為了個(gè)性化,還支持導(dǎo)出它的配置數(shù)據(jù)。

接下來(lái)需要對(duì)組件進(jìn)行打包,我們直接使用vue-cli,vue-cli支持指定不同的構(gòu)建目標(biāo),默認(rèn)為應(yīng)用模式,我們平常項(xiàng)目打包運(yùn)行的npm run build,其實(shí)運(yùn)行的就是vue-cli-service build命令,可以通過(guò)選項(xiàng)來(lái)修改打包行為:

vue-cli-service build --target lib --dest dist_applets/stopwatch --name stopwatch --entry src/applets/stopwatch/index.js上面這個(gè)配置就可以打包我們的stopwatch組件,選項(xiàng)含義如下:

--target app | lib | wc | wc-async (默認(rèn)為app應(yīng)用模式,我們使用lib作為庫(kù)打包模式)--dest 指定輸出目錄 (默認(rèn)輸出到dist目錄,我們改成dist_applets目錄下)--name 庫(kù)或 Web Components 模式下的名字 (默認(rèn)值:package.json 中的 "name" 字段或入口文件名,我們改成組件名稱)--entry 指定打包的入口,可以是.js或.vue文件(也就是組件的index.js路徑)更詳細(xì)的信息可以移步官方文檔:構(gòu)建目標(biāo)、CLI 服務(wù)。

但是我們的組件是不定的,數(shù)量可能會(huì)越來(lái)越多,所以直接在命令行輸入命令打包會(huì)非常的麻煩,我們可以通過(guò)腳本來(lái)完成,在/applets/目錄下新增build.js

// build.jsconst { exec } = require('child_process');const path = require('path')const fs = require('fs')// 獲取組件列表const getComps = () => { let res = [] let files = fs.readdirSync(__dirname) files.forEach((filename) => { // 是否是目錄 let dir = path.join(__dirname, filename) let isDir = fs.statSync(dir).isDirectory // 入口文件是否存在 let entryFile = path.join(dir, 'index.js') let entryExist = fs.existsSync(entryFile) if (isDir && entryExist) { res.push(filename) } }) return res}let compList = getComps()// 創(chuàng)建打包任務(wù)let taskList = compList.map((comp) => { return new Promise((resolve, reject) => { exec(`vue-cli-service build --target lib --dest dist_applets/${comp} --name ${comp} --entry src/applets/${comp}/index.js`, (error, stdout, stderr) => { if (error) { reject(error) } else { resolve() } }) });})Promise.all(taskList) .then(() => { console.log('打包成功'); }) .catch((e) => { console.error('打包失敗'); console.error(e); })然后去package.json新增如下命令:

{ "scripts": { "buildApplets": "node ./src/applets/build.js" }}運(yùn)行命令npm run buildApplets,可以看到打包結(jié)果如下:



我們使用其中css文件和umd類(lèi)型的js文件,打開(kāi).umd.js文件看看:



factory函數(shù)執(zhí)行返回的結(jié)果就是組件index.js里面導(dǎo)出的數(shù)據(jù),另外可以看到引入vue的代碼,這表明Vue是沒(méi)有包含在打包后的文件里的,這是vue-cli刻意為之的,這在通過(guò)構(gòu)建工具使用打包后的庫(kù)來(lái)說(shuō)是很方便的,但是我們是需要直接在頁(yè)面運(yùn)行的時(shí)候動(dòng)態(tài)的引入組件,不經(jīng)過(guò)打包工具的處理,所以exports、module、define、require等對(duì)象或方法都是沒(méi)有的,沒(méi)有沒(méi)關(guān)系,我們可以手動(dòng)注入,我們使用第二個(gè)else if,也就是我們需要手動(dòng)來(lái)提供exports對(duì)象和require函數(shù)。

當(dāng)我們點(diǎn)擊Vue組件類(lèi)型的小程序時(shí)我們使用axios來(lái)請(qǐng)求組件的js文件,獲取到的是js字符串,然后使用new Function來(lái)執(zhí)行js,注入我們提供的exports對(duì)象和require函數(shù),然后就可以通過(guò)exports對(duì)象獲取到組件導(dǎo)出的數(shù)據(jù),最后再使用動(dòng)態(tài)組件渲染出組件即可,同時(shí)如果存在樣式文件的話也要?jiǎng)討B(tài)加載樣式文件。

<template> <component v-if="comp" :is="comp"></component></template>import * as Vue from 'vue';const comp = ref(null);const load = async () => { try { // 加載樣式文件 if (payload.value.styleUrl) { loadStyle(payload.value.styleUrl) } // 請(qǐng)求組件js資源 let { data } = await axios.get(payload.value.url); // 執(zhí)行組件js let run = new Function('exports', 'require', `return ${data}`) // 手動(dòng)提供exports對(duì)象和require函數(shù) const exports = {} const require = () => { return Vue; } // 執(zhí)行函數(shù) run(exports, require) // 獲取組件選項(xiàng)對(duì)象,扔給動(dòng)態(tài)組件進(jìn)行渲染 comp.value = exports.stopwatch.default } catch (error) { console.error(error); }};執(zhí)行完組件的js后我們注入的exports對(duì)象如下:



所以通過(guò)exports.stopwatch.default就能獲取到組件的選項(xiàng)對(duì)象傳遞給動(dòng)態(tài)組件進(jìn)行渲染,效果如下:



大功告成,最后我們?cè)偕晕⑿薷囊幌拢驗(yàn)橥ㄟ^(guò)exports.stopwatch.default獲取組件導(dǎo)出內(nèi)容我們還需要知道組件的打包名稱stopwatch,這顯然有點(diǎn)麻煩,我們可以改成一個(gè)固定的名稱,比如就叫comp,修改打包命令:

// build.js// ...exec(`vue-cli-service build --target lib --dest dist_applets/${comp} --name comp --entry src/applets/${comp}/index.js`, (error, stdout, stderr) => { if (error) { reject(error) } else { resolve() }})// ...--name參數(shù)由之前的${name}改成寫(xiě)死comp即可,打包結(jié)果如下:



exports對(duì)象結(jié)構(gòu)變成如下:



然后我們就可以通過(guò)comp名稱來(lái)應(yīng)對(duì)任何組件了comp.value = exports.comp.default

當(dāng)然,小程序關(guān)閉的時(shí)候不要忘記刪除添加的樣式節(jié)點(diǎn)。

總結(jié)

本文簡(jiǎn)單了嘗試兩種網(wǎng)站功能的擴(kuò)展方式,各位如果有更好的方式的話可以評(píng)論留言分享,線上效果演示地址http://lxqnsys.com/d/。

關(guān)鍵詞:導(dǎo)航,實(shí)踐

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點(diǎn)擊下載Chrome瀏覽器
關(guān)閉