LavaC

LavaC

Still finding my answer
github

一ヶ月のFormily使用経験のまとめ

背景#

仕事の関係で、ここ一ヶ月全く使ったことのない技術スタックで新しいプロジェクトを開発しました。React や Storybook はそれほど難しくなく、基本的に一日で使いこなせましたが、Formily は半月以上かかってようやく入門できました。作業の一区切りがついたので、これらの経験をまとめてみました。

プロジェクトの理由から、私が Formily を使用する方法は必ずしもベストプラクティスではなく、正しいとも限りません。フォームに関しての使用はほぼゼロで、主なシーンは@formily/antd-v5と自作コンポーネントの使用に集中しています。

基礎#

Formily はアリババが開発した、Schema でフォームを記述するオープンソースフレームワークですが、私たちのプロジェクトでそれを使用する理由はフォームを構築するためではなく、動的にページを構築するためです。

Formily には 3 つの重要な概念があります:Form、Field、Schema。

  • Form は全体のフォームのコアで、そのインスタンスはフォームの検証、値、フィールド、フィールドの連動などの内容を取得および設定するために使用できます。
  • Field は Form の構成要素であり、フィールドです。例えば、フォームにkeywordという名前の入力ボックスを設定した場合、入力ボックスの内容を変更するたびに、form.values.keywordも変動します。
  • Schema は Field のデータを記述するもので、簡単に言えば ReactNode と同等と考えられます。具体的に Schema がどのような内容を含むことができるかは公式文書を参照してください。
    • Schema をコンポーネントに変換する過程で、Formily はデフォルトでコンポーネントのvalueonChangeを代理しますので、Formily コンポーネントを開発する際にはこの点を考慮する必要があります。

簡単なページから始める#

まずは簡単な例から始めましょう。
Pasted image 20241003170934.png

Pasted image 20241003171026.png

上記のコードは ==「枠付きの div の中に入力ボックスともう一つの 2 つの入力ボックスを持つコンテナが入っている」== というシーンを作成しています。コードからいくつかの結論を得ることができます:

  • Schema の階層構造は HTML の階層構造に相当し、その中のpropertieschildrenのような役割を果たします。
  • 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 を例にとると、columnsdataSourceはコンポーネントに渡される props の一部として扱われますが、Formily ではデータはデフォルト設定のprops.valueによってのみ処理されるべきです。したがって、開発時にはレンダリングデータとprops.valueの変換を適切に行う必要があります。既存のコンポーネントと接続する際には、公式のmapPropsメソッドを使用してマッピングすることもできます。

データをどこに置いても機能を実現することは可能ですが、form.valuesからすべての値を簡単に取得できる方が、データをフィールドの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 の方法で解決しました。非常に面倒でした。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。