本文是對阿里FormRender的分析。
FormRender 1.0 是下一代的 React.js 表單解決方案。項目從內(nèi)核級別進行了重寫徘跪,為了能切實承接日益復雜的表單場景需求司光。我們的目標是以強大的擴展能力對表單場景 100%的覆蓋支持茅诱,同時保持開發(fā)者能快速上手邑闺,并以表單編輯器季俩、插件栅组、自定義組件等一系列周邊產(chǎn)品帶來極致的開發(fā)體驗雀瓢。
FormRender配套有在線拖拉拽的工具,見fr-generator玉掸。
FormRender和fr-generator兩套工具配合起來刃麸,基本滿足了項目的初始需求,因需要擴展排截,或許還會對源碼進行改造嫌蚤,在此先深入研究了下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ù)脱吱,默認為1
"labelWidth": 120, // 整體控制標簽寬度
"displayType": "row" // 整體控制表單元素與 label 同行 or 分兩行展示, inline 則整個展示自然順排,有'column'认罩、'row'和'inline'三個選項
}
1.2 渲染
2 整體架構(gòu)
3 核心
3.1 Core
<Core />
組件經(jīng)過簡單預處理后將schema和表單布局等信息傳給<MCore/>
箱蝠,而MCore = React.memo(CoreRender, areEqual),會根據(jù)表單值、錯誤提示和schema等信息是否有變化來決定是否重新渲染<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來決定調(diào)用的組件
// 計算 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
會調(diào)用RenderField組件渲染_children牙瓢,RenderField的核心邏輯如下圖:
ExtendedWidget中有一個邏輯是通過Suspense包裹了組件,這大概是因為有一部分組件通過React.lazy做了處理间校,需要配套使用Suspense矾克。
3.2.3
RenderList
當組件類型為
list
時,會根據(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)部會遞歸調(diào)用Core
組件憔足,一層層找到子組件配置進行渲染胁附。
schema
有個配置類型是object
,type
是object
滓彰,title
為空控妻,會被渲染成div
,例如根元素揭绑;如果type
為object
弓候,但title
不為空會被渲染成Collpase
組件。
object
類型往往配置有properties他匪,用來定義子組件們菇存。