元件聯動是指幾個元件相互關聯。也就是當一個元件狀態變化時,其他元件可以響應。
元件聯動是多對多關係的,且目的分為一次性與持續性:
- 多對多關係:即一個元件可以同時被多個元件聯動;多個元件可以同時聯動一個元件。
- 一次性與持續性:一次性事件可以被覆蓋,持續性事件會同時生效,且要考慮疊加關係。
一定程度上,持續性事件可以覆蓋一次性事件的場景:元件永遠響應最後一個過來的事件即可。
接下來我們引入 元件值 與 值聯動 兩個概念,來實現持續性聯動功能。
元件值
每個元件例項都有一個唯一的元件值。
我們可以透過 getValue(componentId)
與 setValue(componentId, value)
訪問或更新元件值:
const table = {
componentName: "table",
runtimeProps: ({ componentId, setValue }) => ({
// 給元件注入 onChange 函式,在其觸發時更新當前元件例項的元件值
onChange: (value) => setValue(componentId, value),
}),
};
也可以透過 componentMeta.value
宣告元件值,比如下面的例子,讓元件值與 props.value
同步:
const table = {
componentName: "table",
// 宣告 value 的值為元件 props.value 的返回值,並隨著元件 props.value 的更新而更新
value: ({ selector }) => selector(({ props }) => props.value),
};
以上兩種方式任選一種使用即可。
為什麼一個元件例項只有一個元件值?
一個元件可能同時擁有多個狀態,比如該元件內部有一個輸入框,還有一個按鈕,可能輸入框的值,與按鈕的點選狀態都會對其他元件產生聯動效果。但這並不意味著一個元件例項需要多個元件值,我們可以將元件值定義為物件,併合理規劃不同的 key 描述不同維度的值:
// 元件值結構
{
// 元件內輸入框的值
text: '123',
// 元件內按鈕被按下的次數
buttonClickTimes: false
}
為什麼不用 props.value
代替元件值?
理論上可以,但這樣限定了元件對 props 的定義。也許有的元件用 props.value
描述輸入框的值,但也有比如 Check 元件,用 props.checked
表示當前選中狀態。只有抽象一個定義與元件元資訊的規則,讓業務自由對接,才可以讓元件值適配任意型別的元件。
值聯動
有了元件值這個概念,就可以以元件例項為粒度,設計元件的關聯關係了。
為了讓元件關聯更加靈活,我們的設計需要滿足以下幾種能力:
- 聯動關係支援多對多。
- 可以隨著全域性資料狀態變化,或者元件自身 props 變化,隨時改變元件關聯關係。
- 一個元件可以定義其他幾個元件的關聯關係,哪怕自己不參與到聯動關係鏈中。
- 當元件例項被刪除時,由它定義的聯動關係立刻失效。
估我們採用 componentMeta.valueRelates
宣告式定義值聯動關係:
const table = {
componentName: "table",
valueRelates: ({ componentId, selector }) => {
return [
{
sourceComponentId: componentId, // 自己為觸發源
targetComponentId: selector(({ props }) => props.targetComponentId), // 目標元件 ID 為 props.targetComponentId
},
];
},
};
這樣設計可以同時滿足以上四個要求,解釋如下:
- 可以在任意元件例項定義多個聯動關係,自然可以實現多對多聯動。
valueRelates
引入selector
可以響應state
或props
的變化,可以由任意狀態驅動聯動關係更新。- 如果
source
與target
都不指向自己,則自己不參與到聯動關係鏈中。 - 宣告式定義方式,自然在元件例項被銷燬時失效。
那麼元件如何響應聯動呢?重點就在這裡,元件可以透過 selector(({ relates }) =>)
的 relates
拿到自己當前的聯動狀態,比如:
const table = {
componentName: "table",
runtimeProps: ({ selector }) => {
// relates 結構如下,對於每一個作用於自己的元件例項 ID 與最新 value 值都可以拿到
// [{
// sourceComponentId: 'abc',
// value: '123'
// }]
const relates = selector(({ relates }) => relates);
return {
status: relates.length > 0 ? "linked" : "free",
};
},
};
如果我們在 runtimeProps
裡使用 selector
監聽 relates
,就可以在聯動狀態變化時,驅動元件渲染,並傳入聯動相關狀態;如果在 fetcher
裡使用 selector
監聽 relates
,就可以在聯動狀態變化時,驅動元件觸發查詢,等等。
以後我們擴充越來越多的元件元資訊回撥函式,支援了 selector
之後,都可以宣告式的響應 relates
變化,也就是元件可以宣告式靈活響應聯動,真正意義上讓聯動可以用在任何場景。
框架沒有對聯動做太多的聯動內建行為,實現的都是靈活規則,雖然業務需要補全不少宣告,但勝在靈活與用法統一。
描述聯動行為
不同的聯動可能做不同的事,比如一個輸入框元件,可能同時有以下兩種作用:
- 讓另一個元件查詢條件增加 "where name=" 當前輸入框的值。
- 當元件的值為 "delete" 時,讓畫布另一個元件隱藏。
為了區分聯動的功能,可以在 valueRelates
增加 payload
引數,描述該聯動的目的:
const table = {
componentName: "table",
valueRelates: ({ componentId, selector }) => {
return [
{
sourceComponentId: componentId,
targetComponentId: selector(({ props }) => props.targetComponentId),
// 作用為目標元件的查詢篩選條件
payload: "filter",
},
{
sourceComponentId: componentId,
targetComponentId: selector(({ props }) => props.targetComponentId),
// 作用為目標元件是否隱藏
payload: "hide",
},
];
},
};
然後目標元件就可以根據實際情況,在 fetcher
過濾 relates
中 payload="filter"
的值,在 runtimeProps
過濾 relates
中 payload="hide"
的值。
用持續聯動實現一次性聯動
每一次元件更新 value 值後,都會重新整理對目標元件 relates
的位置,具體來說,會將其置頂,所以目標元件可以根據 relates
先來後到順序判斷,比如在聯動效果衝突時,讓排在前面的優先生效。
比如:
const table = {
componentName: "table",
runtimeProps: ({ selector }) => {
// 找到最初生效的,payload 為 color 的聯動,覆蓋 props.color
const relateColor = selector(({ relates }) =>
relates.find((each) => each.payload === "color")
);
return {
color: relateColor,
};
},
};
當另一個元件觸發 value 變化時,它會排在目標元件 relates
最前面,這樣的話,如果目標元件按照如上方式編寫響應程式碼,就總會響應最後一次生效的聯動。
總結
這一節介紹瞭如何設定聯動,並引出了元件值概念。
在框架層定義抽象的元件值概念,並透過宣告式或呼叫式對接到 state
狀態或元件 props
,這種抽象理念會貫穿整個框架的設計過程。相似的 valueRelates
也具有宣告式能力,並將聯動作用透過 selector
的 relates
物件傳遞給元件例項使用,讓聯動的消費靈活度大大增加。
視覺化搭建框架設計思路可能都大同小異,但可惜的是,許多搭建框架都對比如聯動、查詢等場景做了定製化約束,使每個框架或多或少存在著私有協議,而我在這個系列想強調的是,可以進一步抽象,讓框架提供業務自由定義協議的能力,而不是提供某個固定的協議。
討論地址是:精讀《元件值與聯動》· Issue #469 · dt-fe/weekly
如果你想參與討論,請 點選這裡,每週都有新的主題,週末或週一釋出。前端精讀 - 幫你篩選靠譜的內容。
關注 前端精讀微信公眾號
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證)