T-CREATOR

SolidJS フォーム設計の最適解:コントロール vs アンコントロールドの棲み分け

SolidJS フォーム設計の最適解:コントロール vs アンコントロールドの棲み分け

SolidJS でフォームを実装する際、コントロールドコンポーネントとアンコントロールドコンポーネントのどちらを選ぶべきか、迷われた経験はありませんか。React の世界では長年議論されてきたこのテーマですが、SolidJS においても同様に重要な設計判断となります。

本記事では、SolidJS におけるフォーム設計の最適解を探るべく、コントロールドとアンコントロールドの特性を深掘りし、それぞれの適切な使い分けについて詳しく解説していきます。実務で直面する具体的なシナリオを交えながら、あなたのプロジェクトに最適なフォーム設計を見つけていきましょう。

背景

SolidJS のリアクティビティとフォーム

SolidJS は、きめ細かなリアクティビティを持つ JavaScript フレームワークです。仮想 DOM を使わず、シグナルベースのリアクティブシステムによって、効率的な更新を実現しています。このリアクティビティの仕組みは、フォーム設計においても重要な役割を果たすのです。

フォームは、ユーザーとアプリケーションの最も重要な接点の一つですよね。入力された値をどのように管理し、バリデーションをどのタイミングで行い、送信時にどうデータを収集するか。これらの判断が、アプリケーション全体のパフォーマンスとユーザー体験を左右します。

コントロールドとアンコントロールドの概念

フォームコンポーネントには、大きく分けて 2 つのアプローチが存在します。

コントロールドコンポーネントは、入力値を JavaScript の状態として管理します。SolidJS では、シグナルやストアを使って入力値を保持し、常に最新の状態を把握できるようにするのです。ユーザーが文字を入力するたびに、状態が更新され、その変更が即座に UI に反映されます。

一方、アンコントロールドコンポーネントは、DOM 自体に入力値を委ねます。必要なときだけ DOM 参照を使って値を取得するアプローチです。React のuseRefに相当する、SolidJS のrefを活用します。

以下の図で、2 つのアプローチの基本的な違いを確認しましょう。

mermaidflowchart TB
    user["ユーザー入力"]

    subgraph controlled["コントロールド"]
        input1["input要素"]
        signal["シグナル(状態)"]
        input1 -->|onChange| signal
        signal -->|value| input1
    end

    subgraph uncontrolled["アンコントロールド"]
        input2["input要素(DOM)"]
        ref["ref参照"]
        input2 -.->|必要時のみ| ref
    end

    user --> controlled
    user --> uncontrolled

この図から分かるように、コントロールドでは双方向のデータフローが発生し、アンコントロールドでは必要時のみの参照という違いがあります。

SolidJS の特性がもたらす影響

SolidJS のリアクティビティは、非常に効率的です。シグナルの変更時、依存する部分だけが再計算されるため、無駄な再レンダリングが発生しません。

これは、コントロールドフォームを実装する際の大きな利点となるでしょう。React では、状態更新のたびにコンポーネント全体が再レンダリングされる可能性がありますが、SolidJS ではシグナルに依存する箇所だけが更新されます。つまり、パフォーマンスの懸念が軽減されるのです。

課題

フォーム設計における判断の難しさ

フォーム実装において、開発者は以下のような課題に直面します。

パフォーマンスとのトレードオフが最初の課題です。入力のたびに状態を更新すると、処理コストが発生しますよね。特に大規模なフォームや、複雑なバリデーションロジックを持つ場合、このコストは無視できません。しかし、リアルタイムなフィードバックを提供するには、状態管理が必要になります。

開発体験と保守性も重要な観点でしょう。コントロールドコンポーネントは、状態を一元管理できるため、デバッグやテストが容易です。一方、アンコントロールドは記述がシンプルですが、値の取得タイミングが限定されるため、複雑なロジックには向きません。

ユーザー体験の要件も考慮すべきポイントです。リアルタイムバリデーション、入力値の即時変換(例:全角 → 半角)、条件付きフィールドの表示など、インタラクティブな機能を実装するには、状態管理が不可欠となります。

以下の図で、フォーム設計における主な課題を整理します。

mermaidflowchart LR
    form["フォーム設計"]

    form --> perf["パフォーマンス<br/>コスト"]
    form --> dx["開発体験<br/>保守性"]
    form --> ux["ユーザー体験<br/>要件"]

    perf --> decision["設計判断"]
    dx --> decision
    ux --> decision

    decision --> controlled_choice["コントロールド<br/>選択"]
    decision --> uncontrolled_choice["アンコントロールド<br/>選択"]

コントロールドの課題

コントロールドコンポーネントでは、すべての入力フィールドに対してシグナルを用意する必要があります。フォームが大規模になると、状態管理のコードが増え、初期化処理も複雑化するでしょう。

また、バリデーションロジックをどこに配置するかも悩ましい問題です。入力時、フォーカス喪失時、送信時など、複数のタイミングでバリデーションを行う場合、処理の重複や整合性の問題が発生しやすくなります。

アンコントロールドの課題

アンコントロールドコンポーネントは、シンプルな実装が魅力ですが、リアルタイム性が求められる機能との相性が良くありません。

例えば、入力値に応じて他のフィールドを動的に変更したい場合、DOM 参照だけでは実現が困難です。また、入力値のフォーマット変換やマスク処理など、ユーザーの入力を即座に加工する必要がある場合も、コントロールドアプローチが必要となるでしょう。

選択基準の不明確さ

最大の課題は、どちらを選ぶべきかの明確な基準がないことかもしれません。プロジェクトの要件、チームの方針、パフォーマンス要求など、様々な要因を総合的に判断する必要があります。

間違った選択をすると、後からの変更コストが大きくなってしまいますよね。そのため、事前に適切な設計判断を下すことが重要なのです。

解決策

コントロールドコンポーネントの実装パターン

コントロールドコンポーネントは、SolidJS のシグナルを活用して入力値を管理します。基本的なパターンを見ていきましょう。

シグナルによる状態管理

最もシンプルなコントロールドフォームの実装です。各入力フィールドにシグナルを割り当てます。

typescriptimport { createSignal } from 'solid-js';

function ControlledForm() {
  // 各フィールドのシグナルを定義
  const [name, setName] = createSignal('');
  const [email, setEmail] = createSignal('');

  return (
    <form>
      <input
        type='text'
        value={name()}
        onInput={(e) => setName(e.currentTarget.value)}
      />
    </form>
  );
}

上記のコードでは、value属性にシグナルの値を設定し、onInputイベントで状態を更新しています。この双方向バインディングにより、常に最新の入力値を把握できるのです。

ストアによる複雑な状態管理

フォームが大規模になると、複数のシグナルを個別に管理するのは煩雑になりますよね。そこで、SolidJS のストアを活用すると効果的です。

typescriptimport { createStore } from 'solid-js/store';

function ControlledFormWithStore() {
  // フォーム全体をストアで管理
  const [formData, setFormData] = createStore({
    name: '',
    email: '',
    age: 0,
    agreement: false,
  });

  return <form>{/* フィールド定義 */}</form>;
}

ストアを使うことで、関連するデータをオブジェクトとしてまとめて管理できます。後述する実装例で詳しく見ていきましょう。

リアルタイムバリデーション

コントロールドの最大の利点は、入力値の変化を即座に検知できることです。これを活用してリアルタイムバリデーションを実装します。

typescriptimport { createSignal, createMemo } from 'solid-js';

function EmailField() {
  const [email, setEmail] = createSignal('');

  // メールアドレスの妥当性を自動計算
  const emailError = createMemo(() => {
    const value = email();
    if (!value) return '';
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
      ? ''
      : 'メールアドレスの形式が正しくありません';
  });

  return (
    <div>
      <input
        type='email'
        value={email()}
        onInput={(e) => setEmail(e.currentTarget.value)}
      />
      {emailError() && (
        <span class='error'>{emailError()}</span>
      )}
    </div>
  );
}

createMemoを使うことで、入力値が変化したときだけバリデーションが実行されます。効率的かつリアクティブな実装ですね。

アンコントロールドコンポーネントの実装パターン

アンコントロールドコンポーネントは、DOM に値の管理を委ねるシンプルなアプローチです。

ref による DOM 参照

SolidJS では、refを使って DOM 要素への参照を取得できます。

typescriptfunction UncontrolledForm() {
  let nameRef: HTMLInputElement | undefined;
  let emailRef: HTMLInputElement | undefined;

  const handleSubmit = (e: Event) => {
    e.preventDefault();

    // 送信時にDOMから値を取得
    const formData = {
      name: nameRef?.value || '',
      email: emailRef?.value || '',
    };

    console.log(formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type='text' ref={nameRef} />
      <input type='email' ref={emailRef} />
      <button type='submit'>送信</button>
    </form>
  );
}

このコードでは、フォーム送信時のみ値を取得しています。シグナルを使わないため、記述がシンプルですよね。

FormData API の活用

より簡潔な実装として、FormData API を活用する方法もあります。

typescriptfunction UncontrolledFormWithFormData() {
  const handleSubmit = (e: Event) => {
    e.preventDefault();
    const form = e.currentTarget as HTMLFormElement;

    // FormData APIで一括取得
    const formData = new FormData(form);
    const data = Object.fromEntries(formData);

    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type='text' name='name' />
      <input type='email' name='email' />
      <button type='submit'>送信</button>
    </form>
  );
}

name属性を設定しておくだけで、FormData API が自動的に値を収集してくれます。非常に便利ですね。

ハイブリッドアプローチ

実際のプロジェクトでは、両方のアプローチを組み合わせることが最適解となる場合が多いでしょう。

重要なフィールドや、リアルタイムなフィードバックが必要なフィールドはコントロールドに、シンプルな入力フィールドはアンコントロールドに。このように使い分けることで、パフォーマンスと開発効率のバランスを取れます。

以下の図で、ハイブリッドアプローチの概念を示します。

mermaidflowchart TD
    form_fields["フォームフィールド"]

    form_fields --> critical["重要度・要件の判定"]

    critical -->|リアルタイム要| controlled_group["コントロールド<br/>グループ"]
    critical -->|シンプル| uncontrolled_group["アンコントロールド<br/>グループ"]

    controlled_group --> validation["リアルタイム<br/>バリデーション"]
    controlled_group --> conditional["条件付き<br/>表示制御"]
    controlled_group --> format["入力値<br/>フォーマット"]

    uncontrolled_group --> simple_input["シンプルな<br/>テキスト入力"]
    uncontrolled_group --> optional_field["任意項目"]

    validation --> submit["フォーム送信"]
    conditional --> submit
    format --> submit
    simple_input --> submit
    optional_field --> submit

この図から分かるように、フィールドの特性に応じて適切なアプローチを選択し、最終的に統合して送信処理を行います。

使い分けの判断基準

どちらのアプローチを選ぶべきか、具体的な判断基準を表にまとめました。

#要件コントロールドアンコントロールド
1リアルタイムバリデーション★★★☆☆☆
2入力値の即時変換★★★☆☆☆
3条件付きフィールド表示★★★☆☆☆
4複数フィールド間の連携★★★★☆☆
5シンプルな送信のみ★☆☆★★★
6パフォーマンス重視(大量入力)★☆☆★★★
7実装のシンプルさ★☆☆★★★
8テスト容易性★★★★★☆
9デバッグ容易性★★★★☆☆

この表を参考に、プロジェクトの要件に合わせて選択していきましょう。

具体例

ここからは、実際のユースケースを通して、具体的な実装方法を見ていきます。

ケース 1:会員登録フォーム(コントロールド)

会員登録フォームでは、リアルタイムバリデーションとパスワード確認が必要です。コントロールドアプローチが最適でしょう。

ストアの定義とバリデーション

まず、フォームデータとエラー状態をストアで管理します。

typescriptimport { createStore } from 'solid-js/store';
import { createMemo } from 'solid-js';

type RegisterFormData = {
  username: string;
  email: string;
  password: string;
  passwordConfirm: string;
};

type FormErrors = {
  username: string;
  email: string;
  password: string;
  passwordConfirm: string;
};

function RegisterForm() {
  // フォームデータのストア
  const [formData, setFormData] =
    createStore<RegisterFormData>({
      username: '',
      email: '',
      password: '',
      passwordConfirm: '',
    });

  // エラー状態のストア
  const [errors, setErrors] = createStore<FormErrors>({
    username: '',
    email: '',
    password: '',
    passwordConfirm: '',
  });

  // 続く...
}

ストアを使うことで、フォームデータとエラーを構造的に管理できますね。

バリデーション関数の実装

各フィールドのバリデーションロジックを関数として定義します。

typescript// ユーザー名のバリデーション
const validateUsername = (value: string): string => {
  if (!value) return 'ユーザー名を入力してください';
  if (value.length < 3)
    return 'ユーザー名は3文字以上で入力してください';
  if (!/^[a-zA-Z0-9_]+$/.test(value)) {
    return 'ユーザー名は半角英数字とアンダースコアのみ使用できます';
  }
  return '';
};

// メールアドレスのバリデーション
const validateEmail = (value: string): string => {
  if (!value) return 'メールアドレスを入力してください';
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(value)) {
    return 'メールアドレスの形式が正しくありません';
  }
  return '';
};

// パスワードのバリデーション
const validatePassword = (value: string): string => {
  if (!value) return 'パスワードを入力してください';
  if (value.length < 8)
    return 'パスワードは8文字以上で入力してください';
  if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
    return 'パスワードは大文字、小文字、数字を含む必要があります';
  }
  return '';
};

関数として分離することで、テストやメンテナンスが容易になります。

入力ハンドラとリアルタイムバリデーション

入力時にバリデーションを実行するハンドラを実装しましょう。

typescript// 汎用的な入力ハンドラ
const handleInput = (
  field: keyof RegisterFormData,
  value: string,
  validator: (value: string) => string
) => {
  // フォームデータを更新
  setFormData(field, value);

  // バリデーションを実行してエラーを更新
  const error = validator(value);
  setErrors(field, error);
};

// パスワード確認の特別な処理
const handlePasswordConfirmInput = (value: string) => {
  setFormData('passwordConfirm', value);

  // パスワードと一致するか確認
  const error =
    value !== formData.password
      ? 'パスワードが一致しません'
      : '';
  setErrors('passwordConfirm', error);
};

このハンドラにより、入力と同時にバリデーションが実行され、即座にフィードバックが提供されます。

フォーム全体の妥当性チェック

送信前に、フォーム全体が妥当かどうかをチェックする機能も追加しましょう。

typescript// フォームが妥当かどうかを判定
const isFormValid = createMemo(() => {
  return (
    !errors.username &&
    !errors.email &&
    !errors.password &&
    !errors.passwordConfirm &&
    formData.username !== '' &&
    formData.email !== '' &&
    formData.password !== '' &&
    formData.passwordConfirm !== ''
  );
});

// 送信処理
const handleSubmit = async (e: Event) => {
  e.preventDefault();

  if (!isFormValid()) {
    alert('入力内容を確認してください');
    return;
  }

  try {
    // API呼び出しなどの処理
    console.log('登録データ:', formData);
    // await registerUser(formData);
  } catch (error) {
    console.error('登録エラー:', error);
  }
};

createMemoを使うことで、フォームの状態が変わったときだけ妥当性が再計算されます。効率的ですね。

JSX の実装

最後に、UI を構築します。

typescriptreturn (
  <form onSubmit={handleSubmit}>
    <div>
      <label>ユーザー名</label>
      <input
        type='text'
        value={formData.username}
        onInput={(e) =>
          handleInput(
            'username',
            e.currentTarget.value,
            validateUsername
          )
        }
      />
      {errors.username && (
        <span class='error'>{errors.username}</span>
      )}
    </div>

    <div>
      <label>メールアドレス</label>
      <input
        type='email'
        value={formData.email}
        onInput={(e) =>
          handleInput(
            'email',
            e.currentTarget.value,
            validateEmail
          )
        }
      />
      {errors.email && (
        <span class='error'>{errors.email}</span>
      )}
    </div>

    {/* パスワードフィールドとパスワード確認フィールドも同様 */}

    <button type='submit' disabled={!isFormValid()}>
      登録する
    </button>
  </form>
);

このように、コントロールドアプローチでは、リアルタイムなフィードバックと厳密な制御が可能になります。

ケース 2:お問い合わせフォーム(アンコントロールド)

シンプルなお問い合わせフォームでは、アンコントロールドアプローチが適しているでしょう。送信時のみバリデーションを行う実装です。

FormData を活用したシンプルな実装

まず、基本的な構造を見ていきます。

typescriptimport { createSignal } from 'solid-js';

function ContactForm() {
  const [isSubmitting, setIsSubmitting] =
    createSignal(false);
  const [submitError, setSubmitError] = createSignal('');
  const [submitSuccess, setSubmitSuccess] =
    createSignal(false);

  return <form>{/* フィールド定義 */}</form>;
}

状態として持つのは、送信中フラグとエラーメッセージ、成功フラグのみです。シンプルですね。

送信時のバリデーション

送信ボタンがクリックされたときに、初めてバリデーションを実行します。

typescriptconst handleSubmit = async (e: Event) => {
  e.preventDefault();
  setSubmitError('');
  setSubmitSuccess(false);

  const form = e.currentTarget as HTMLFormElement;
  const formData = new FormData(form);

  // フォームデータから値を取得
  const name = formData.get('name') as string;
  const email = formData.get('email') as string;
  const message = formData.get('message') as string;

  // バリデーション
  if (!name || !email || !message) {
    setSubmitError('すべての項目を入力してください');
    return;
  }

  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
    setSubmitError(
      'メールアドレスの形式が正しくありません'
    );
    return;
  }

  // 続く...
};

送信時にまとめてチェックすることで、実装がシンプルになっています。

API 送信処理

バリデーションが通過したら、API に送信します。

typescript// バリデーション通過後の処理
try {
  setIsSubmitting(true);

  // APIへ送信
  const response = await fetch('/api/contact', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name, email, message }),
  });

  if (!response.ok) {
    throw new Error('送信に失敗しました');
  }

  setSubmitSuccess(true);
  form.reset(); // フォームをリセット
} catch (error) {
  setSubmitError(
    'エラーが発生しました。もう一度お試しください'
  );
} finally {
  setIsSubmitting(false);
}

エラーハンドリングも含めて、送信処理を実装しています。

JSX の実装

フォームの UI を構築しましょう。

typescriptreturn (
  <form onSubmit={handleSubmit}>
    <div>
      <label for='name'>お名前</label>
      <input type='text' id='name' name='name' required />
    </div>

    <div>
      <label for='email'>メールアドレス</label>
      <input
        type='email'
        id='email'
        name='email'
        required
      />
    </div>

    <div>
      <label for='message'>お問い合わせ内容</label>
      <textarea
        id='message'
        name='message'
        rows={5}
        required
      />
    </div>

    {submitError() && (
      <div class='error'>{submitError()}</div>
    )}

    {submitSuccess() && (
      <div class='success'>送信が完了しました</div>
    )}

    <button type='submit' disabled={isSubmitting()}>
      {isSubmitting() ? '送信中...' : '送信する'}
    </button>
  </form>
);

name属性を設定しておくだけで、FormData API が自動的に値を収集してくれます。非常に簡潔な実装になりましたね。

ケース 3:動的フォーム(ハイブリッド)

最後に、ハイブリッドアプローチの例を見ていきましょう。ユーザーの入力に応じて、動的にフィールドが変化するフォームです。

シナリオ設定

アンケートフォームで、「その他」を選択したときだけ、自由記述欄が表示される実装を考えます。選択肢はアンコントロールド、自由記述欄はコントロールドで管理するのが効率的でしょう。

mermaidsequenceDiagram
    participant User as ユーザー
    participant Select as 選択肢(アンコントロールド)
    participant Signal as シグナル
    participant TextArea as 自由記述欄(コントロールド)

    User->>Select: "その他"を選択
    Select->>Signal: onChange イベント
    Signal->>TextArea: 表示状態を更新
    TextArea-->>User: 入力欄を表示
    User->>TextArea: テキスト入力
    TextArea->>Signal: リアルタイム更新

上記のシーケンス図で、ユーザーの操作から UI の変化までの流れを示しました。

実装コード

選択肢の変更を検知して、条件付きで自由記述欄を表示します。

typescriptimport { createSignal, Show } from 'solid-js';

function SurveyForm() {
  // "その他"が選択されているかを管理
  const [isOtherSelected, setIsOtherSelected] =
    createSignal(false);
  // 自由記述の内容を管理(コントロールド)
  const [otherText, setOtherText] = createSignal('');

  const handleCategoryChange = (e: Event) => {
    const value = (e.currentTarget as HTMLSelectElement)
      .value;
    setIsOtherSelected(value === 'other');

    // "その他"以外が選択されたら自由記述をクリア
    if (value !== 'other') {
      setOtherText('');
    }
  };

  return <form>{/* フィールド定義 */}</form>;
}

選択肢の変更イベントだけをシグナルで管理し、他のフィールドはアンコントロールドのままにしています。

条件付き表示の実装

Showコンポーネントを使って、条件付きでフィールドを表示しましょう。

typescriptreturn (
  <form onSubmit={handleSubmit}>
    <div>
      <label>カテゴリを選択してください</label>
      <select
        name='category'
        onChange={handleCategoryChange}
      >
        <option value=''>選択してください</option>
        <option value='product'>製品について</option>
        <option value='service'>サービスについて</option>
        <option value='support'>サポートについて</option>
        <option value='other'>その他</option>
      </select>
    </div>

    <Show when={isOtherSelected()}>
      <div>
        <label>詳細をご記入ください</label>
        <textarea
          value={otherText()}
          onInput={(e) =>
            setOtherText(e.currentTarget.value)
          }
          placeholder='その他の内容を入力してください'
          rows={3}
        />
      </div>
    </Show>

    <div>
      <label>お名前</label>
      <input type='text' name='name' required />
    </div>

    <button type='submit'>送信</button>
  </form>
);

必要な部分だけをコントロールドにすることで、パフォーマンスと機能性のバランスを取っています。

送信処理の統合

アンコントロールドとコントロールドの値を統合して送信する処理です。

typescriptconst handleSubmit = (e: Event) => {
  e.preventDefault();
  const form = e.currentTarget as HTMLFormElement;
  const formData = new FormData(form);

  // アンコントロールドの値を取得
  const category = formData.get('category') as string;
  const name = formData.get('name') as string;

  // コントロールドの値を追加
  const submitData = {
    category,
    name,
    otherText: isOtherSelected() ? otherText() : '',
  };

  console.log('送信データ:', submitData);
  // API送信処理など
};

このように、両方のアプローチの値を統合して、一つのデータオブジェクトとして扱えます。

パフォーマンス比較

実際のパフォーマンスについても触れておきましょう。SolidJS のリアクティビティは非常に効率的なため、コントロールドフォームでもパフォーマンスの懸念は少ないです。

ただし、以下のような状況では、アンコントロールドの方が有利になる場合があります。

#シナリオコントロールドアンコントロールド推奨
1フィールド数が 50 以上シグナル管理コスト大DOM 管理のみアンコントロールド
2入力頻度が非常に高い更新処理の積み重ねイベント処理のみアンコントロールド
3バリデーションが重い入力ごとに実行送信時のみアンコントロールド
4リアルタイム連携必須即座に反映可能実装困難コントロールド

実際のプロジェクトでは、これらの要因を総合的に判断して、最適なアプローチを選択していきましょう。

まとめ

SolidJS におけるフォーム設計では、コントロールドとアンコントロールドの特性を理解し、適切に使い分けることが重要です。

コントロールドアプローチは、シグナルやストアを使って入力値を状態として管理します。リアルタイムバリデーション、入力値の即時変換、条件付きフィールド表示など、インタラクティブな機能を実装する際に最適でしょう。デバッグやテストもしやすく、複雑なロジックにも対応できます。

一方、アンコントロールドアプローチは、DOM に値の管理を委ねるシンプルな実装です。送信時のみ値を取得する単純なフォームや、パフォーマンスを最優先したい場合に適しています。FormData API と組み合わせることで、非常に簡潔なコードになりますね。

実務では、ハイブリッドアプローチを採用することで、両方の利点を活かせます。重要なフィールドや動的な振る舞いが必要な部分はコントロールドに、シンプルな入力フィールドはアンコントロールドに。このように使い分けることで、パフォーマンスと機能性、開発効率のバランスを最適化できるでしょう。

SolidJS のきめ細かなリアクティビティは、コントロールドフォームのパフォーマンス懸念を大幅に軽減してくれます。そのため、他のフレームワークよりも積極的にコントロールドアプローチを採用できるのが、SolidJS の大きな魅力と言えますね。

あなたのプロジェクトに最適なフォーム設計を見つけて、ユーザーにとって使いやすく、開発者にとってメンテナンスしやすいアプリケーションを構築していきましょう。

関連リンク