視覺化搭建會遇到如下三類容器元件:
- 簡單容器:以
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
如果你想參與討論,請 點選這裡,每週都有新的主題,週末或週一釋出。前端精讀 - 幫你篩選靠譜的內容。
關注 前端精讀微信公眾號
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證)