可視化搭建 - 容器組件設計
可視化搭建會遇到如下三類容器組件:
-
簡單容器:以
children
容納子組件的容器。 -
卡片容器:以
props.header
加上props.header
等多個插槽容納子組件的容器。 -
Tab 容器:以
props.tabPanel[x]
等動態數量插槽容納子組件的容器。
畫布本身也是一個容器組件,所以可視化搭建離不開容器。
另一方面,我們應該允許給組件 props 傳入 React 組件實例,但組件樹是可序列化的 JSON 結構,因此需要一種定義方式,將某些屬性轉化爲 React 組件實例傳給組件實例。
容器的定義
任何組件都可能是容器組件,只要它將 props.children
或 props.footer
等任何屬性作爲 ReactNode 渲染。因此我們不需要特殊聲明組件是否爲容器,而僅需將某些組件 Key 聲明爲 ReactNode 節點。
Children
children
因爲太常用因此單獨強調出來,可以只在在組件實例定義 children
屬性,它爲是一個數組:
import { ComponentInstance } from "designer";
const componentTree: ComponentInstance = {
componentName: "div",
children: [
{
componentName: "input",
},
],
};
對於這個組件,Designer 會將 children
定義的屬性理解爲組件實例,並真正解析爲 React 實例傳遞給 props.children
,因此組件渲染代碼可以直接使用 children
渲染:
import { ComponentMeta } from "designer";
const divMeta: ComponentMeta = {
componentName: "div",
element: ({ children }) => <div>{children}</div>,
};
這種約定的好處是直觀自然,組件代碼也沒有關心到框架邏輯,自然而然實現了容器功能。
treeLike 結構
只要將任意組件 props 定義爲數組模式,並且包含 componentName
,Designer 就認爲應該解析爲 ReactNode。
如下面的例子,我們定義的 div
組件初始化就會渲染一個 input
組件在 props.header
位置:
import { ComponentMeta } from "designer";
const divMeta: ComponentMeta = {
componentName: "div",
element: ({ header }) => <div>{header}</div>,
defaultProps: {
header: [
{
componentName: "input",
},
],
},
};
也可以在描述組件樹時直接寫在對應 props 位置:
import { ComponentInstance } from "designer";
const componentTree: ComponentInstance = {
componentName: "div",
props: {
header: [
{
componentName: "input",
},
],
},
};
這種約定的好處是直觀的支持了任意 props key 爲組件實例,但依然存在限制,因此 Designer 還需要支持一種用戶 100% 掌控的申明式定義:propTypes
。
PropTypes
在組件元信息 propTypes
屬性定義更細緻的容器插槽位置,比如:
const tabMeta = {
componentName: "tab",
propTypes: {
tabs: [
{
panel: "element",
},
],
},
};
那麼當組件實例如下定義時:
const componentInstance = {
componentName: "tab",
props: {
tabs: [
{
title: "tab1",
panel: {
componentName: "card",
},
},
{
title: "tab2",
panel: {
componentName: "text",
},
},
],
},
};
組件拿到的 props.tabs[0].panel
就是一個可以直接渲染的 React 組件實例,因爲在 propTypes
定義了 tabs[].panel
路徑是一個組件實例。
這樣設計需要考慮組件樹遍歷的問題,因爲組件實例位置定義在組件元信息上,因此僅靠組件樹無法做遍歷(因爲遍歷父節點時,不結合 componentMeta
就無法確認哪些 props 位置是子組件實例),這樣會帶來兩個問題:
-
遍歷組件非常麻煩,極端情況下,如果大量組件是遠程註冊的三方組件,會導致需要一層層串行遠程拉取組件實例,導致遍歷過程變慢。
-
更極端的場景是,當組件版本升級導致
propTypes
變化,一些原本不是組件實例的位置成爲了組件實例,或者反之,此時拉取最新組件元信息讀取的propTypes
可能就是錯的。
因爲以上兩個原因,實現方案應該是將組件元信息定義的 propTypes
拷貝一份到組件實例,這樣就可以僅憑組件樹自身來遍歷組件樹了,而且定義在組件樹上的 propTypes
一定對應當前組件樹的結構。
總結
我們通過 children
與 props 上 treeLike 這兩個約定,實現了業務基本夠用的容器定義能力,僅憑這兩個約定就可以實現幾乎所有容器需要的效果。
propTypes 定義補全了約定拓展性的不足,讓 props 任何位置都可能成爲組件實例,只需要付出額外定義 propTypes 的代價。
閱讀到這,相信你已經理解到,可視化搭建其實不存在容器組件的概念,因爲這個組件之所以是容器,僅僅因爲它的某個 prop 屬性是組件實例,而它恰好將該屬性渲染到某個位置(甚至用 createPortal
掛載到其他 dom 節點),所以它僅僅是一種 prop 屬性的體現,因此對容器組件,我們沒有設計一種新 type
,而是允許任意位置屬性定義爲實例。
下一節我們會介紹爲組件元信息添加取數與篩選聯動的鉤子,讓篩選器 + 查詢場景可以輕鬆被實現。
討論地址是:精讀《容器組件設計》· Issue #468 · dt-fe/weekly
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/i2KOPIhBz8hlVDBiWYXprw