LavaC

LavaC

Still finding my answer
github

一個月的Formily使用經驗總結

背景#

因為工作原因近一個月換了完全沒用過的技術棧開發新項目。React、Storybook 這些倒還好,基本一天就上手了,倒是 Formily 這厮陸續摸索了半個多月才算入門,趁一階段工作結束空隙我就把這些經驗總結一下。

因為項目原因我使用 Formily 的方式不一定是最佳實踐甚至不一定是對的,對表單方面的使用也約等於零,主要場景集中於使用@formily/antd-v5和自製組件上。

基礎#

Formily 是阿里開發的一套以 Schema 描述表單的開源框架,不過我們項目中使用它的理由並不是構建表單,而是用於動態構建頁面。

Formily 裡有三個比較重要的概念:Form、Field 和 Schema。

  • Form 是整個表單的核心,其實例可用於獲取和設置表單的校驗、值、字段、字段聯動等內容
  • Field 是 Form 的組成部件,也就是字段,比如表單裡設置了一個叫 name 為keyword的輸入框,那每次修改輸入框的內容時,form.values.keyword就會跟著變動。
  • Schema 是描述 Field 的數據,可以簡單的認為其與 ReactNode 等價,具體一個 Schema 能包含什麼內容可以參考官方文檔
    • 在 Schema 轉組件的過程中 Formily 會默認代理組件的valueonChange,所以要開發 Formily 組件時要考慮到這一點。

從一個簡單的頁面開始#

先從一個簡單的示例開始
Pasted image 20241003170934.png

Pasted image 20241003171026.png

上面的代碼創建一個 ==“帶邊框的 div,裡面裝著一個輸入框和另一個帶了兩個輸入框的容器”== 的場景,從代碼中我們可以得到幾個結論:

  • Schema 的層級結構相當於 HTML 中的層級結構,其中的properties起到了類似children的作用。
  • createForm中可以定義表單的一些初始狀態?
  • x-component代表當前位置需要渲染的元素,可以支持原生的標籤可自定義的組件,其中的自定義組件需要在createSchemaField中進行註冊。
  • x-component-props中定義了組件的 props,x-decorator定義了包裝這個組件的組件。

簡單操作一下表單,可以發現 Schema 和實際的表單值的映射關係。
Pasted image 20241003171319.png
image.png

如果要管理不同字段的值,schema 的type非常關鍵。

  • typevoid的時候,表單會忽略這一層的路徑。
  • typeobject的時候,該字段會成為承接子字段的對象。
  • type為基本類型的時候,該字段代表具體的值。

當時我們本來打算封裝一個組合了 Card + Tabs 功能的組件,但是在 Formily 的機制下,字段本身不能既表示自己的值(Tabs 的 activeKey),也成為包含子字段的對象,所以最後打消了這種做法。

函數處理#

如果堅持使用 JSON Schema,那往 Schema 裡塞函數的做法就顯得不那麼合理了。在 Schema 中,Formily 會把{{}}的字符串處理成函數,所以要轉換一下寫法。

// ...
	input: {
		type: "string",
		"x-component": "Input",
		"x-component-props": {
			onClick: `{{ (event) => { console.log(event) } }}`
		}
	}
// ...

同樣的,對需要傳 ReactNode 的 props 也是如此處理,不過需要使用React.createElement

import { createElement } from "react";
import { Input } from '@formily/antd-v5'
import { SearchOutlined } from '@ant-design/icons';

// ...
const SchemaField = createSchemaField({
	components: {
		Input
	},
	scope: {
		createElement,
		SearchOutlined
	}
})
// ...
input: {
	type: "string",
	"x-component": "Input",
	"x-component-props": {
		suffix: `{{ createElement(SearchOutlined) }}`
	}
}
// ...

字段聯動#

這部分內容官網文檔說得就挺好的,就不贅述了

數據傳遞#

我們當時的頁面有一個聯動邏輯,當在頂部導航欄切換時間後,下面的各個圖表都要同步更新數據,而我們的解決方式如下,不一定是最佳實踐。
image.png

組件開發流程#

Formily 組件和常規的 React 組件差別還是挺大的,如果把@formily/antd-v5作為官方的推薦實踐方式的話,那就不能以之前的思路來開發組件。

以 antd 的 Table 為例,無論是columns還是dataSource都是作為 props 的一部分傳遞給組件的,但在 Formily 中,數據應只由默認配置的props.value來處理,所以開發時應該做好渲染數據與props.value的轉換。對接已有組件時也可以用官方的mapProps方法做映射。

要較真的話數據放在哪都是可以實現功能的,但能簡單的從form.values拿到所有值還是比把數據分散到 field 的componentProps要方便的。

除此之外,善用useFormuseFielduseFieldSchema可以方便獲取父子字段的內容。

一些坑#

ReactNode 轉 Schema#

雖然可以用{{createElement}}的方式傳遞 ReactNode,但對於自定義組件來說還需要提前把組件傳入 scope 中,對於動態的 Schema 來說,這難以做到按需導入。

我當時的做法是組件內部做攔截。用useFieldSchema拿到 Schema,然後判斷對應的屬性是否也是一個 Schema 對象,如果是則返回一個RecursionField來渲染 Schema。

攔截寫完後乍一看還沒什麼毛病,但是在 antd 的 Table sortIcon 時就傻眼了,渲染的圖標在點擊後完全沒有狀態變化。

個人猜測這跟 Formily 的渲染機制有關,RecursionField的渲染結果被緩存了,只是重新運行函數傳遞新的 props 還不足以觸發其重新渲染。

onChange#

還是跟 Table 的過濾有關,原流程中點擊表頭的排序會觸發表格的onChange事件獲得排序信息。

但我在嘗試換了多種寫法後發現仍不能觸發這個onChange,看了源碼後才發現為了防止冒泡官方給onChange覆蓋了個空函數。你們是完全用不到這個功能嗎?

業務層面沒什麼解決方法,這裡最後用了 patch 的方式解決的,頗為麻煩。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。