這是第 61 篇不摻水的原創,想獲取更多原創好文,請掃 👆 上方二維碼關注咱們吧~ 本文首發於政採雲前端團隊博客:如何開發跨框架的組件javascript
筆者所在的業務中臺團隊,須要提供業務組件給不一樣的上層業務方使用,但由於一些歷史遺留問題,不一樣業務線使用的框架不統一,包括 jQuery、React 、Vue 。爲了知足不一樣業務方的需求,每每須要根據業務方使用的框架,開發對應框架的組件。css
這樣作就會產生一些痛點:html
每種選型都須要開發一次,費時勞力前端
組件升級,須要業務方同步發版升級,溝通成本高、迭代效率低java
如何設計一個符合上面提到的跨框架、少升級指望的通用方案呢?npm
很容易想到用原生 JS 來實現,避免跨框架的問題。api
用原生 JS 實現,包含頁面裏用到的 UI 組件,不依賴任何框架。跨域
優勢:app
缺點:框架
適用場景:
不須要複雜交互的場景,如前臺吊頂、後臺菜單側邊欄可採用這種方式。
但在實際的業務場景中,業務組件中有比較多複雜的交互場景, 上面的方案不太能知足要求,因此咱們在上面的方案之上進行迭代:
咱們將業務組件拆分爲兩部分:
1、容器組件:
用原生 JS 實現中間層容器組件,解決跨框架的加載問題,容器組件主要負責:
2、業務邏輯組件
根據 iframe 自然的沙箱特性,業務邏輯用 iframe 頁面加載,就保證了業務組件的實現不受框架的限制,能夠完美解決問題。業務邏輯組件主要負責:
缺點:
此方案容器組件做爲中間層,封裝不變的邏輯,將多變的業務邏輯隔離出來,從而保證協做方儘可能少升級或不升級。業務定製性可根據接口配置,返回不一樣的 iframe 地址,加載不一樣的業務邏輯組件,一次開發任意使用。
下面是整個組件的邏輯圖:
使用方經過容器組件初始化參數、並註冊相應的回調:
容器組件
// 獲取主域名
function getTopLevelDomain(host) {
let data = host || window.location.host;
return data.split('.').slice(-2).join('.');
}
// 設置主域名
function setDomainToTopLevelDomain() {
try {
window.document.domain = getTopLevelDomain();
} catch (error) {
console.error("設置domain失敗")
}
}
複製代碼
class Vanilla {
// 獲取配置信息
constructor(config) {
const options = { ...defaultConfig, ...config };
this.options = options;
this.elCls = options.elCls;
}
// 生成容器 div
render() {
const div = document.createElement('div');
this.el = div;
const { width, height } = this.options;
div.className = `${prefixCls}-wrap ${prefixCls}-wrap-loading ${this.elCls || ''}`;
const maskNode = getMaskNode(prefixCls);
const iframeNode = getIframeNode(prefixCls, width, height);
div.innerHTML = maskNode + iframeNode;
document.body.appendChild(div);
this.fn();
}
init() {
// 設置主域名
setDomainToTopLevelDomain();
// 初始化 div
this.render();
// 初始化全局回調函數
this.initCallbacks();
}
...
}
複製代碼
class Vanilla {
...
initCallbacks() {
const self = this;
const options = this.options;
// 初始化全局變量
window[paramsName] = options;
window.onSuccess = function onSuccess(data, res) {
options.onSuccess && options.onSuccess(data, res);
// 延遲1500ms刪除用來顯示成功提示
setTimeout(() => {
self.removeNode();
}, 1500);
self.resetCallbacks && self.resetCallbacks();
};
window.onCancel = function onCancel() {
options.onCancel && options.onCancel();
self.removeNode();
self.resetCallbacks && self.resetCallbacks();
};
window.onError = function onError(data) {
options.onError && options.onError(data);
};
}
}
複製代碼
let timer = function timer() {};
class Vanilla {
...
$mount() {
...
this.fn();
}
fn() {
const {
width,
height,
isAutoSize,
} = this.options;
const el = this.el;
const url = getContentUrl('你的iframe地址');
const iframeWidth = width;
const iframeHeight = height;
const iframeEle = el.querySelector('.J_CreditIframe');
const modalNode = el.querySelector(`.${prefixCls}`);
if (!isAutoSize && (iframeWidth !== width || iframeHeight !== height)) {
this.setNodeSizeAndPostion(modalNode, iframeEle, iframeWidth, iframeHeight);
}
iframeEle.setAttribute('src', url);
// 監聽load後,隱藏loading
addEvent(iframeEle, 'load', () => {
el.className = `${prefixCls}-wrap ${this.elCls || ''}`;
const maxTime = 3000;
const duration = 1000;
let timerCounter = 0;
let w = defaultConfig.width;
let h = defaultConfig.height;
// 自適應寬高
if (isAutoSize) {
timer = setInterval(() => {
...
//
this.setNodeSizeAndPostion(modalNode, iframeEle, scrollWidth, scrollHeight);
}
timerCounter += duration;
if (timerCounter >= maxTime) {
clearInterval(timer);
}
}, duration);
}
});
}
// 設置iframe寬高
setNodeSizeAndPostion(container, iframe, width, height) {
container.style.cssText = `width: ${width}px; height: ${height}px;margin-left: -${width / 2}px;margin-top: -${height / 2}px;`;
iframe.style.cssText = `width: ${width}px; height: ${height}px;`;
}
// 刪除DOM
removeNode() {
timer && clearInterval(timer);
if (this.el) {
document.body.removeChild(this.el);
}
}
...
}
複製代碼
上面咱們完成了整個業務組件的加載過程,下面咱們須要處理的就是業務邏輯組件如何與容器組件之間進行通訊:
一般咱們能夠這樣處理:
// 獲取父頁面屬性
const params = window.parent.paramsName;
// 調用父頁面方法
window.parent.onSuccess && window.parent.onSuccess(data);
複製代碼
但在實際的業務場景中,咱們可能會面臨的問題是業務方的域名與 iframe 加載的組件地址域名不一致,這個時候咱們就必需要解決組件的跨域通訊問題了.
跨域的通訊問題
咱們能夠經過如下三種方式去解決:
postMessage
主域名修改
Nginx 代理
// 靜態頁面地址
location ~ ^/your-project/ {
root /opt/front/your-project/;
try_files $uri $uri/ /index.html = 404;
access_log off;
}
// 反向代理
location ~ ^/api/service/(.*)$ {
proxy_pass http://your-ip;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header requestId $request_id;
proxy_http_version 1.1;
proxy_set_header Connection "";
expires 30d;
access_log off;
}
複製代碼
注意處理非白色背景的圓角部分,容易出現毛邊。處理方法是 iframe 容器不設置背景色,由 iframe 裏面設置圓角
版本控制:小版本保證向前兼容,大版本可經過動態獲取 iframe 地址來實現版本控制
多框架背景下的組件重複開發問題,根源仍是多框架的歷史債務問題。更好的方式則是推進技術棧的統一,從根源上避免出現此種狀況。此場景下更爲完善的解決方案則是微前端,咱們也在向這個方向探索,本文提供的是一種基礎的解決多技術棧業務場景的思路,若是有更好的方案歡迎你們一塊兒討論~
政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 50 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。
若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「 5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com