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中超復雜自定義組件相關內容

補充說明

最近按照formily的相關API使用 rxJS 自己做了一版,感興趣的可以訪問 https://github.com/leomYili/formilyDemo/tree/master/src/views/rxjs 來對比參考.

結語

針對底層代碼再次做了下閱讀,發現還是有不少東西是很有用的,有些api只有在需要擴展以及需要定制化改造的場景下才會用的到.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM