Closed8

React手抜きテクニック集

空雲空雲

formやinputを非制御で最も簡単に使う方法

useStateやuseRefを使わずに、最小限の記述でフォームに入力したデータをとることが出来ます

Reactの公式ではvalueプロパティを使って制御する形を推奨しています
しかしvalueに対応するsetStateが上位の層にいると、テキストを入力するごとに下位コンポーネントの再評価が行われ重力地獄に落ちることがあるので、特段の理由が無ければ私は非制御を推奨します
重力地獄はmemo化で対処は出来るといえば出来るのですが、それなら最初から余計な動きをさせないのが一番です

また、リアルタイムなバリデーションチェックはsetStateに関係なく、onChangeイベントで入力内容のチェックが可能です

const Form = ({ onSubmit }: { onSubmit: (value: string) => void }) => (
  <form
    onSubmit={(e) => {
      e.preventDefault();
      const a = e.target['a'].value;
      const b = e.target['b'].value;
      onSubmit(JSON.stringify({ a, b }));
    }}
  >
    <input name="a" />
    <input name="b" />
    <button>実行</button>
  </form>
);

const App = () => {
  const [value, setValue] = useState<string>(null);
  return (
    <>
      <Form onSubmit={(v) => setValue(v)} />
      <div>{value}</div>
    </>
  );
};
空雲空雲

非制御だけどinputの内容を外から制御する

一番最後のinputをユーザ入力は自由に出来るようにしつつ、ラジオボタンが切り替わったときだけ内容が書き換わります

inputにdefaultValueを使った場合、通常だと後から内容を書き換えることは出来ません
ここで登場するのがkeyです

これを書き換えることによってinputが作り直しになるので、defaultValueが再評価されることになります
keyはループ以外にも用途が存在するわけです

const App = () => {
  const [value, setValue] = useState('default');
  return (
    <>
      <label>
        <input name="a" type="radio" onChange={() => setValue('あいうえお')} />
        あいうえお
      </label>
      <label>
        <input name="a" type="radio" onChange={() => setValue('かきくけこ')} />
        かきくけこ
      </label>
      {/* ↓の内容を書き換える */}
      <input key={value} defaultValue={value} />
    </>
  );
};
空雲空雲

プルダウンメニュー的なイベント処理

プルダウンメニュー的なコンポーネントを作成したとき、面倒になるのが他にフォーカスが移ったときの非表示処理です
これを最小限のコード量で記述してみます

対象ノードにフォーカスを与えてblurイベントで非表示にするのはよくある流れなのですが、useRefやuseEffectを使わずfocus()を実行しています
refはDOM作成時にインスタンスを渡してくれるので、それを直接利用します

const App = () => {
  const [visible, setVisible] = useState(false);
  return (
    <>
      <button onClick={() => setVisible(true)}>表示</button>
      {visible && (
        <div
          style={{ border: 'solid' }}
          tabIndex={0}
          ref={(e) => e?.focus()}
          onBlur={() => setVisible(false)}
        >
          表示内容
        </div>
      )}
    </>
  );
};
空雲空雲

hooksとデータ保存領域

「カウント」を押すとサイレントに押した回数を計測し、「結果」を押すとコンソールに押した回数が出力されます

クラスコンポーネントと違って作業データを保存するにはhookを使うことになるのですが、コンポーネントの再評価が必要なもの以外にuseStateは使ってはいけません
データを入れ替えるごとにコンポーネントの処理が再実行されてしまいます

今回のように情報を記憶しておけば良いだけのデータはuseRefでデータ領域を作りましょう

const App = () => {
  const count = useRef(0);
  return (
    <>
      <button onClick={() => count.current++}>カウント</button>
      <button onClick={() => alert(count.current)}>結果</button>
    </>
  );
};
空雲空雲

Hooks側で仮想DOMとデータ両方の制御を行う

子コンポーネントのボタンを押すと親コンポーネントで表示しているカウントが増えます
この動作を普通に書くと、親にカウントの増加を通知するためにコンポーネントからイベントを作成する流れになります
親はそのイベントを受け取り、useStateで作ったdispatchにデータを設定しなければなりません

今回の書き方だと、それらのロジックをhook側に閉じ込めて、親コンポーネントは送られてきた値をそのまま使うだけとなり、記述は最小限です

const useCount = () => {
  const [count, setCount] = useState(0);
  const Counter = useMemo(
    () => () => <button onClick={() => setCount((v) => v + 1)}>ボタン</button>,
    []
  );
  return { count, Counter };
};

const App = () => {
  const { count, Counter } = useCount();
  return (
    <>
      <Counter />
      {count}
    </>
  );
};
空雲空雲

useStateで重要なのはデータ自体では無くDispatchの方

Hooksを利用するとき、useStateを使ったことが無いという人はまずいないと思います
しかし何も考えずに使っていると、本質が分からずになんとなくそう書いているという状態になります

useStateでstateを扱うためのhookなので、重要なのはでは無く、いつもはset何とかと名付けているDispatchの方なのです

値を格納するだけならuseRefで領域を作って格納しても良いのです
ただ、それだと値の変更が通知されず関数が再評価されないので、dispatchを呼び出す必要があるのです
ぶっちゃけてしまえば、データ領域を全てrefにして、useStateは一個だけという構成でもコンポーネントは成り立ちます

const App = () => {
  const [, dispatch] = useState<{}>();
  const count = useRef(0);
  return (
    <div
      onClick={() => {
        count.current++;
        dispatch({});
      }}
    >
      {count.current}
    </div>
  );
};
空雲空雲

useStateのデータ取得の非同期化

特定のコールバック環境下ではuseStateの値が更新されず、正確な情報を取得するためにはdispatchを呼び出して値を取得する必要があります

利用する値が単体の時は単純に書けるので問題ないのですが、複数のデータを同時に利用したい場合にdispatchの中でdispatchを呼び出したりと、記述する処理が複雑になります

今回のコードはそれを回避するためにPromiseを使ってデータを取り出す処理になります

値を取り出す部分はPromise.allを使った方が適切ですが、今回は分かりやすく書くため使っていません

ちなみにtsxだと<T>がエラーとなるので<T,>と書かなければなりません

const getStateValue = <T,>(dispatch: React.Dispatch<React.SetStateAction<T>>): Promise<T> => {
  return new Promise<T>((resolve) => {
    dispatch((v) => {
      resolve(v);
      return v;
    });
  });
};

const App = () => {
  const [, setA] = useState('');
  const [, setB] = useState('');
  const [c, setC] = useState('');
  const handleClick = useCallback(async () => {
    setC((await getStateValue(setA)) + (await getStateValue(setB)));
  }, [/*あえてここは使わず、関数の再構築はしない*/]);
  return (
    <>
      <input onChange={(e) => setA(e.target.value)} />
      <input onChange={(e) => setB(e.target.value)} />
      <button onClick={handleClick}>設定</button>
      <div>{c}</div>
    </>
  );
};
空雲空雲

tsxでコンポーネントにgenericsを渡す方法

なんか気持ち悪いですが、タグの中で<>を使います

const Test = <T,>({ value }: { value: T }) => <div>{value}</div>;

const App = () => {
  return (
    <>
      <Test<string> value="abc" />
    </>
  );
};
このスクラップは2021/11/23にクローズされました