Open4

FEのDDDやりたい

あんどうあんどう

FEでDDDやりたいので、アイデアが欲しい。

ざっくり、MVVMを基本にVM層をapplicationと捉えてdomain layerにする感じ、、、

あんどうあんどう

やりたいこととしては、最近FEのライブラリ依存が非常に高く抜け出したい。
具体で言うと、実装をしたい→どのライブラリを使うか調査になってしまっている。理想の状態を定義してからライブラリは参考程度・もしくはドンピシャで合うものがあれば使うくらいにしたい。

あんどうあんどう

そもそもstateを使わなくても実装できる機能を使う前提で組み立ててしまうことを辞めたい

あんどうあんどう

これはどっちがいいのか

stateで管理する

import { useCallback, useState } from "react";

function MyForm() {
  const [value, setValue] = useState("");
  const [error, setError] = useState("");

  // バリデーション関数を Memoize(入力値依存で再生成)
  const validate = useCallback((val: string) => {
    if (val.length < 5) return "5文字以上入力してください";
    return "";
  }, []);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const val = e.target.value;
    setValue(val);
    setError(validate(val));
  };

  return (
    <div>
      <input value={value} onChange={handleChange} aria-invalid={!!error} />
      {error && <p role="alert">{error}</p>}
    </div>
  );
}

export default MyForm;

onInput等で毎回validateする

import { useRef } from "react";

function MyForm() {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleChange = () => {
    const input = inputRef.current;
    if (!input) return;
    if (input.value.length < 5)
      input.setCustomValidity("5文字以上入力してください");
    else input.setCustomValidity("");

    input.reportValidity();
  };

  return (
    <div>
      <input ref={inputRef} onInput={handleChange} required minLength={5} />
    </div>
  );
}

export default MyForm;

onsubmitでvalidity

import { useRef } from "react";
import { z } from "zod";

const getInputElement = (
  form: HTMLFormElement,
  name: string
): HTMLInputElement => {
  const found = form.querySelector(`input[name="${name}"]`);
  if (!(found instanceof HTMLInputElement))
    throw new Error(`Input element with name ${name} not found`);
  return found;
};

type IValidate = {
  customValidity: (v: string) => void;
  value: unknown;
  schema: z.ZodSchema;
};

const validate = (input: IValidate): void => {
  const result = input.schema.safeParse(input.value);
  if (!result.success) {
    input.customValidity(result.error.errors.map((e) => e.message).join("\n"));
  } else {
    input.customValidity("");
  }
};

const validateAll = (inputs: IValidate[]): void => {
  inputs.forEach(validate);
};

function MyForm() {
  const formRef = useRef<HTMLFormElement>(null);

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const form = e.currentTarget;

    validateAll([
      {
        customValidity: (v: string) =>
          getInputElement(form, "username").setCustomValidity(v),
        value: getInputElement(form, "username").value,
        schema: z.string().min(5, "5文字以上で入力してください"),
      },
    ]);

    // ブラウザ組み込みのチェック
    if (!form.checkValidity()) {
      // ここで不正なフィールドにフォーカスが移り、tool-tip風のメッセージが出る
      form.reportValidity();
      return;
    }
  };

  return (
    <form ref={formRef} onSubmit={handleSubmit} noValidate>
      <div>
        <label>
          名前(5文字以上):
          <input name="username" type="text" required minLength={5} />
        </label>
      </div>

      <div>
        <label>
          年齢(正の整数):
          <input name="age" type="number" required min={1} />
        </label>
      </div>

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

export default MyForm;