本文是對(duì)阿里FormRender的分析。
FormRender 1.0 是下一代的 React.js 表單解決方案。項(xiàng)目從內(nèi)核級(jí)別進(jìn)行了重寫,為了能切實(shí)承接日益復(fù)雜的表單場(chǎng)景需求。我們的目標(biāo)是以強(qiáng)大的擴(kuò)展能力對(duì)表單場(chǎng)景 100%的覆蓋支持,同時(shí)保持開發(fā)者能快速上手,并以表單編輯器、插件、自定義組件等一系列周邊產(chǎn)品帶來(lái)極致的開發(fā)體驗(yàn)。
FormRender配套有在線拖拉拽的工具,見fr-generator。
FormRender和fr-generator兩套工具配合起來(lái),基本滿足了項(xiàng)目的初始需求,因需要擴(kuò)展,或許還會(huì)對(duì)源碼進(jìn)行改造,在此先深入研究了下FormRender@1.5.8的源碼,后面再研究fr-generator。
1 使用舉例
1.1 schema
{
"type": "object",
"properties": {
"input_xnNHW9": {
"title": "輸入框",
"type": "string",
"props": {}
},
"textarea_Ski8kS": {
"title": "編輯框",
"type": "string",
"format": "textarea",
"props": {}
}
},
"column": 1, // 整體控制一行的列數(shù),默認(rèn)為1
"labelWidth": 120, // 整體控制標(biāo)簽寬度
"displayType": "row" // 整體控制表單元素與 label 同行 or 分兩行展示, inline 則整個(gè)展示自然順排,有'column'、'row'和'inline'三個(gè)選項(xiàng)
}
1.2 渲染

2 整體架構(gòu)

3 核心

3.1 Core
<Core />組件經(jīng)過(guò)簡(jiǎn)單預(yù)處理后將schema和表單布局等信息傳給<MCore/>,而MCore = React.memo(CoreRender, areEqual),會(huì)根據(jù)表單值、錯(cuò)誤提示和schema等信息是否有變化來(lái)決定是否重新渲染<CoreRender />(在form-render@1.8.5中去掉了MCore這一層):
const areEqual = (prev, current) => {
if (prev.allTouched !== current.allTouched) {
return false;
}
if (prev.displayType !== current.displayType) {
return false;
}
if (prev.column !== current.column) {
return false;
}
if (prev.labelWidth !== current.labelWidth) {
return false;
}
if (
JSON.stringify(prev._value) === JSON.stringify(current._value) &&
JSON.stringify(prev.schema) === JSON.stringify(current.schema) &&
JSON.stringify(prev.errorFields) === JSON.stringify(current.errorFields)
) {
return true;
}
return false;
};
const MCore = React.memo(CoreRender, areEqual);
3.2 CoreRender
<div
style={columnStyle}
className={`${containerClass} ${debugCss ? 'debug' : ''}`}
>
<RenderField {...fieldProps}>{_children}</RenderField>
</div>
3.2.1 _children:根據(jù)子元素的類型是object、數(shù)組或checkbox來(lái)決定調(diào)用的組件
// 計(jì)算 children
let _children = null;
if (hasChildren) {
if (isObj) {
_children = objChildren; // 即RenderObject組件
} else if (isList) {
_children = listChildren; // 即RenderList組件
}
} else if (isCheckBox) {
_children = schema.title;
}
3.2.2 CoreRender會(huì)調(diào)用RenderField組件渲染_children,RenderField的核心邏輯如下圖:

ExtendedWidget中有一個(gè)邏輯是通過(guò)Suspense包裹了組件,這大概是因?yàn)橛幸徊糠纸M件通過(guò)React.lazy做了處理,需要配套使用Suspense。
3.2.3
RenderList當(dāng)組件類型為
list時(shí),會(huì)根據(jù)具體情況分別渲染成不同組件:
switch (renderWidget) {
case 'list0':
case 'cardList':
return <CardList {...displayProps} />;
case 'list1':
case 'simpleList':
return <SimpleList {...displayProps} />;
case 'list2':
case 'tableList':
return <TableList {...displayProps} />;
case 'list3':
case 'drawerList':
return <DrawerList {...displayProps} />;
case 'list4':
case 'virtualList':
return <VirtualList {...displayProps} />;
case 'tabList':
return <TabList {...displayProps} />;
default:
return <CardList {...displayProps} />;
}
3.2.4 RenderObject
const RenderObject = ({
children = [],
dataIndex = [],
displayType,
hideTitle,
}) => {
return (
<>
{children.map((child, i) => {
const FRProps = {
displayType,
id: child,
dataIndex,
hideTitle,
};
return <Core key={i.toString()} {...FRProps} />;
})}
</>
);
};
RenderObject內(nèi)部會(huì)遞歸調(diào)用Core組件,一層層找到子組件配置進(jìn)行渲染。
schema有個(gè)配置類型是object,type是object,title為空,會(huì)被渲染成div,例如根元素;如果type為object,但title不為空會(huì)被渲染成Collpase組件。
object類型往往配置有properties,用來(lái)定義子組件們。