formily-底層庫原始碼閱讀以及api理解
正文
本篇拋開業務程式碼,從
formily
底層閱讀程式碼,進行分析.但並不是原始碼解析,比較懶,只在閱讀的時候打了註釋,也不想浪費大量的篇幅展示程式碼
Anything comes from Observable Graph
這是官網的描述,介紹了內部使用 Observable Graph
,來記錄任意時刻的全量狀態,也方便回滾.
如何接入第三方元件庫
分為以下幾步:
- 接入 Form/FormItem 元件
- 接入元件庫表單元件
- 實現表單佈局元件
- 實現自增列表元件
如何接入Form/FormItem元件
接入方式目前提供了全域性註冊機制與單例註冊機制,全域性註冊主要使用 registerFormComponent 和 registerFormItemComponent 兩個API來註冊,單例註冊則是直接在 SchemaForm 屬性上傳 formComponent 和 formItemComponent。如果是 SPA 場景,推薦使用單例註冊的方式
import React from 'react' import { SchemaForm, registerFormComponent, registerFormItemComponent } from '@formily/react-schema-renderer' import { Form } from 'antd' export const CompatFormComponent = ({ children, ...props }) => { return <Form {...props}>{children}</Form> //很簡單的使用Form元件,props是SchemaForm元件的props,這裡會直接透傳 } export const CompatFormItemComponent = ({ children, ...props }) => { const messages = [].concat(props.errors || [], props.warnings || []) let status = '' if (props.loading) { status = 'validating' } if (props.invalid) { status = 'error' } if (props.warnings && props.warnings.length) { status = 'warning' } return ( <Form.Item {...props} label={props.schema.title} help={ messages.length ? messages : props.schema && props.schema.description } validateStatus={status} > {children} </Form.Item> ) } /*** 全域性註冊方式 registerFormComponent(CompatFormComponent) registerFormItemComponent(CompatFormItemComponent) ***/ //單例註冊方式 export default () => { return ( <SchemaForm formComponent={CompatFormComponent} formItemComponent={CompatFormItemComponent} /> ) }
可以看到,擴充套件表單整體或區域性的樣式,僅僅只需要通過擴充套件 Form/FormItem 元件就可以輕鬆解決了,這裡需要注意的是,FormItem 元件接收到的 props 有點複雜,不用擔心,後面會列出詳細 props API,現在我們只需要知道大概是如何註冊的就行了.
如何接入表單元件
因為元件庫的所有元件都是原子型元件,同時大部分都相容了 value/onChange 規範,所以我們可以藉助 connect 函式快速接入元件庫的元件,通常,我們接入元件庫元件,大概要做 3 件事情:
- 處理狀態對映,將 formily 內部的 loading/error 狀態對映到該元件屬性上,當然,前提是要求元件必須支援 loading 或 error 這類的樣式
- 處理詳情態樣式,將 formily 內部的 editable 狀態,對映到一個 PreviewText 元件上去,用於更友好更乾淨的展示資料
- 處理元件列舉態,我們想一下,JSON Schema,每一個節點都應該支援 enum 屬性的,如果配了 enum 屬性,我們最好都以 Select 形式來展現,所以我們需要處理一下元件列舉態.
import React from 'react'
import { connect, registerFormField } from '@formily/react-schema-renderer'
import { InputNumber } from 'antd'
const mapTextComponent = (
Target: React.JSXElementConstructor<any>,
props: any = {},
fieldProps: any = {}
): React.JSXElementConstructor<any> => {
const { editable } = fieldProps
if (editable !== undefined) {
if (editable === false) {
return PreviewText
}
}
if (Array.isArray(props.dataSource)) {
return Select
}
return Target
}
const mapStyledProps = (
props: IConnectProps,
fieldProps: MergedFieldComponentProps
) => {
const { loading, errors } = fieldProps
if (loading) {
props.state = props.state || 'loading'
} else if (errors && errors.length) {
props.state = 'error'
}
}
const acceptEnum = (component: React.JSXElementConstructor<any>) => {
return ({ dataSource, ...others }) => {
if (dataSource) {
return React.createElement(Select, { dataSource, ...others })
} else {
return React.createElement(component, others)
}
}
}
registerFormField(
'number',
connect({
getProps: mapStyledProps, //處理狀態對映
getComponent: mapTextComponent //處理詳情態
})(acceptEnum(InputNumber)) //處理列舉態
)
如何處理表單佈局
JSON Schema 描述表單資料結構,其實是天然支援的,但是表單最終還是落在 UI 層面的,可惜在 UI 層面上我們有很多元件其實並不能作為 JSON Schema 的一個具體資料節點,它僅僅只是一個 UI 節點
import React from 'react'
import { SchemaForm, registerVirtualBox } from '@formily/react-schema-renderer'
import { Card } from 'antd'
registerVirtualBox('card', ({ children, ...props }) => {
return <Card {...props.schema.getExtendsComponentProps()}>{children}</Card>
})
export default () => {
return (
<SchemaForm
schema={{
type: 'object',
properties: {
layout_card: {
//layout_card這個屬性名,您改成什麼都不會影響最終提交的資料結構
type: 'object',
'x-component': 'card',
'x-component-props': {
title: 'This is Card'
},
properties: {
array: {
type: 'array',
items: {
type: 'object',
properties: {
input: {
type: 'string'
}
}
}
}
}
}
}
}}
/>
)
}
card 就是一個正常的 Object Schema 節點,只是需要指定一個 x-component 為 card,這樣就能和 registerVirtualBox 註冊的 card 匹配上,就達到了虛擬節點的效果.
這裡需要注意的是 x-component-props 是直接透傳到 registerVirtualBox 的回撥函式引數上的。 這是 JSON Schema 形式的使用.
import React from 'react'
import { SchemaForm, createVirtualBox } from '@formily/react-schema-renderer'
import { Card } from 'antd'
const Card = createVirtualBox('card', ({ children, ...props }) => {
return <Card {...props}>{children}</Card>
})
export default () => {
return (
<SchemaForm>
<Card title="This is Card">
<Field type="array" name="array">
<Field type="object">
<Field type="string" name="input" />
</Field>
</Field>
</Card>
</SchemaForm>
)
}
還有 JSchema 的使用方式:
import React from 'react'
import { SchemaForm, createVirtualBox } from '@formily/react-schema-renderer'
import { Card } from 'antd'
const Card = createVirtualBox('card', ({ children, ...props }) => {
return <Card {...props}>{children}</Card>
})
export default () => {
return (
<SchemaForm>
<Card title="This is Card">
<Field type="array" name="array">
<Field type="object">
<Field type="string" name="input" />
</Field>
</Field>
</Card>
</SchemaForm>
)
}
如何實現超複雜自定義元件
可以定義一下,什麼才是超複雜自定義元件:
-
元件內部存在大量表單元件,同時內部也存在大量聯動關係
-
元件內部存在私有的服務端動態渲染方案
-
元件內部有複雜佈局結構
為什麼我們通過正常的封裝自定義元件的形式不能解決問題呢?其實主要是受限於校驗,沒法整體校驗,所以,我們需要一個能聚合大量欄位處理邏輯的能力.
詳情可以檢視demo中超複雜自定義元件相關內容
結語
針對底層程式碼再次做了下閱讀,發現還是有不少東西是很有用的,有些api只有在需要擴充套件以及需要定製化改造的場景下才會用的到.