🕶️

Render Propsも現役だよ

3 min read

状態管理ロジックを分離する手法として、Hooks以外はお役御免だと思っていませんか?

HOCもRender propもいくらかのライブラリでは未だ現役なので読める必要がありますが、モダンなライブラリでRender propを優先的に採用しているケースもあります。ここが少し気になって調べた結果、Render propは他にないメリットがあることが分かりました。

Render propは任意のHTML構造を合わせて出力できる...だけでなくもう一つの利点があります。それは...パフォーマンスチューニングが楽なのです!

サンプル

フォーム部品とその他のコンポーネントを子に持つ親コンポーネントを実装します。フォームのvalueやonChangeハンドラは親から流し込みます。ここでフォーム部品を触るとフォーム部品しかレンダリングされないようにするには...Render propが最も近道です。

Hooks

フォームに入力するたびにコンソールが表示されます。

useInputs.ts
import React, { useCallback, useState } from "react";

export default function useInputs<Values>(init: Values) {
  const [values, setValues] = useState<Values>(init);

  const handleChange = useCallback((e: React.ChangeEvent<any>) => {
    setValues((prevValues) => ({
      ...prevValues,
      [e.target.name]: e.target.value,
    }));
  }, []);

  return {
    values,
    handleChange,
  };
}

index.tsx
import React from "react";
import useInputs from "./useInputs";

type FormProps = {
  values: { name: string; post: string };
  onChange: (e: React.ChangeEvent<any>) => void;
};

function Form({ values, onChange }: FormProps) {
  return (
    <form>
      <input type="text" name="name" value={values.name} onChange={onChange} />
      <input type="text" name="post" value={values.post} onChange={onChange} />
    </form>
  );
}

function Another() {
  console.log("Another render.");

  return <div>Lorem ipsum dolor sit amet consectetur.</div>;
}

export default function Demo01() {
  const { values, handleChange } = useInputs({ name: "", post: "" });

  return (
    <div>
      <Form values={values} onChange={handleChange} />
      <Another />
    </div>
  );
}

Render prop

フォームに入力してもコンソールが反応することはありません。

InputsProvider.tsx
import React, { Fragment, useState } from "react";

type ChildrenProps<Values> = {
  values: Values;
  handleChange: (e: React.ChangeEvent<any>) => void;
};

type InputsProviderProps<Values> = {
  init: Values;
  children: ({ values, handleChange }: ChildrenProps<Values>) => JSX.Element;
};

export default function InputsProvider<Values>(
  props: InputsProviderProps<Values>
) {
  const { init, children } = props;

  const [values, setValues] = useState<Values>(init);

  const handleChange = (e: React.ChangeEvent<any>) => {
    setValues((prevValues) => ({
      ...prevValues,
      [e.target.name]: e.target.value,
    }));
  };

  return <Fragment>{children({ values, handleChange })}</Fragment>;
}

index.tsx
import React from "react";
import InputsProvider from "./InputsProvider";

type FormProps = {
  values: { name: string; post: string };
  onChange: (e: React.ChangeEvent<any>) => void;
};

function Form({ values, onChange }: FormProps) {
  return (
    <form>
      <input type="text" name="name" value={values.name} onChange={onChange} />
      <input type="text" name="post" value={values.post} onChange={onChange} />
    </form>
  );
}

function Another() {
  console.log("Another render.");

  return <div>Lorem ipsum dolor sit amet consectetur.</div>;
}

export default function Demo01() {
  return (
    <div>
      <InputsProvider init={{ name: "", post: "" }}>
        {({ values, handleChange }) => (
          <Form values={values} onChange={handleChange} />
        )}
      </InputsProvider>
      <Another />
    </div>
  );
}

備考

こんなものを自作するならFormikを使うほうが多いでしょう。FormikにもHooksとRender propのAPIがありますが、後者がおすすめです!前者の方がフレームワーク感がなく取っ付きやすく見えますが。

Discussion

ログインするとコメントできます