視覺化搭建 - 元件值與聯動

黃子毅發表於2023-02-27

元件聯動是指幾個元件相互關聯。也就是當一個元件狀態變化時,其他元件可以響應。

元件聯動是多對多關係的,且目的分為一次性與持續性:

  • 多對多關係:即一個元件可以同時被多個元件聯動;多個元件可以同時聯動一個元件。
  • 一次性與持續性:一次性事件可以被覆蓋,持續性事件會同時生效,且要考慮疊加關係。

一定程度上,持續性事件可以覆蓋一次性事件的場景:元件永遠響應最後一個過來的事件即可。

接下來我們引入 元件值值聯動 兩個概念,來實現持續性聯動功能。

元件值

每個元件例項都有一個唯一的元件值。

我們可以透過 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 表示當前選中狀態。只有抽象一個定義與元件元資訊的規則,讓業務自由對接,才可以讓元件值適配任意型別的元件。

值聯動

有了元件值這個概念,就可以以元件例項為粒度,設計元件的關聯關係了。

為了讓元件關聯更加靈活,我們的設計需要滿足以下幾種能力:

  1. 聯動關係支援多對多。
  2. 可以隨著全域性資料狀態變化,或者元件自身 props 變化,隨時改變元件關聯關係。
  3. 一個元件可以定義其他幾個元件的關聯關係,哪怕自己不參與到聯動關係鏈中。
  4. 當元件例項被刪除時,由它定義的聯動關係立刻失效。

估我們採用 componentMeta.valueRelates 宣告式定義值聯動關係:

const table = {
  componentName: "table",
  valueRelates: ({ componentId, selector }) => {
    return [
      {
        sourceComponentId: componentId, // 自己為觸發源
        targetComponentId: selector(({ props }) => props.targetComponentId), // 目標元件 ID 為 props.targetComponentId
      },
    ];
  },
};

這樣設計可以同時滿足以上四個要求,解釋如下:

  1. 可以在任意元件例項定義多個聯動關係,自然可以實現多對多聯動。
  2. valueRelates 引入 selector 可以響應 stateprops 的變化,可以由任意狀態驅動聯動關係更新。
  3. 如果 sourcetarget 都不指向自己,則自己不參與到聯動關係鏈中。
  4. 宣告式定義方式,自然在元件例項被銷燬時失效。

那麼元件如何響應聯動呢?重點就在這裡,元件可以透過 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 變化,也就是元件可以宣告式靈活響應聯動,真正意義上讓聯動可以用在任何場景。

框架沒有對聯動做太多的聯動內建行為,實現的都是靈活規則,雖然業務需要補全不少宣告,但勝在靈活與用法統一。

描述聯動行為

不同的聯動可能做不同的事,比如一個輸入框元件,可能同時有以下兩種作用:

  1. 讓另一個元件查詢條件增加 "where name=" 當前輸入框的值。
  2. 當元件的值為 "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 過濾 relatespayload="filter" 的值,在 runtimeProps 過濾 relatespayload="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 也具有宣告式能力,並將聯動作用透過 selectorrelates 物件傳遞給元件例項使用,讓聯動的消費靈活度大大增加。

視覺化搭建框架設計思路可能都大同小異,但可惜的是,許多搭建框架都對比如聯動、查詢等場景做了定製化約束,使每個框架或多或少存在著私有協議,而我在這個系列想強調的是,可以進一步抽象,讓框架提供業務自由定義協議的能力,而不是提供某個固定的協議。

討論地址是:精讀《元件值與聯動》· Issue #469 · dt-fe/weekly

如果你想參與討論,請 點選這裡,每週都有新的主題,週末或週一釋出。前端精讀 - 幫你篩選靠譜的內容。

關注 前端精讀微信公眾號

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證

相關文章