内部フック: React Hooks の新しいアイデア


序章



この投稿は、最近思いついたアイデアについてです.

これは、react 開発チームや Facebook の公式コンセプトではありません.私はどこにでもいるただのプログラマーですが、ある程度の経験はあります.ですから、私の考えはあなたを満足させるものではないかもしれませんが、React Hooks に関する新しい概念を説明し、それに興味を持っているすべての人に議論したいと思います.私はそれを「インナーフック」と呼んでいます.

このコンセプトに沿ったライブラリを実験的に作っています.こちらは my repository of it です.必要に応じて playground で試してみてください.

インナーフックとは



内部フックのアイデアは、props を渡すことによって、コンポーネントの子スコープで反応フックのみを使用できるようにします.それ以上でもそれ以下でもありません.私のライブラリは、HOC を作成することで実現します.

なぜこれが必要なのですか?



私のレポジトリの README ドキュメントからの焼き直しになりますが、利点を説明します.こちらも気になる方はこちらもご覧ください.

まず、jsx の記述を開始する前にフックを作成する必要があります.たとえば、次の例のように、条件付きでレンダリングされたコンポーネント間にフックを記述することはできませんでした.

const Example = (props) => {
  const { initialized, data } = useFetchData();
  if (!initialized) return null;
  const options = useOptions();
  return <Component {...data} options={options} />;
};


過剰な脂肪成分に遭遇した場合、この事実はあなたを悩ませるかもしれません.あなたがそう感じるかもしれない例を示します.

const Example = (props) => {
    const options = useOptions()
    const [optionValue, setOptionValue] = useState()
    const {initialized, data} = useFetchData()
    const someValue = ''
    const someChange = () => {}
    if (!initialized) return null
    return (
        <Component>
            <Child>
              <AnnoyedField
                value={someValue}
                onChange={someChange}
                class='test'
                otherProps
              />
              <AnnoyedField
                value={someValue}
                onChange={someChange}
                class='test'
                otherProps
              />
              <AnnoyedField
                value={someValue}
                onChange={someChange}
                class='test'
                otherProps
              />
              <AnnoyedField
                value={someValue}
                onChange={someChange}
                class='test'
                otherProps
              />
              <AnnoyedField
                value={someValue}
                onChange={someChange}
                class='test'
                otherProps
              />
              <AnnoyedField
                value={someValue}
                onChange={someChange}
                class='test'
                otherProps
              />
              <AnnoyedField
                value={someValue}
                onChange={someChange}
                class='test'
                otherProps
              />
              <Select
                value={optionValue}
                onChange={setOptionValue}
                options={options}
                otherProps
              />
              <AnnoyedField
                value={someValue}
                onChange={someChange}
                class='test'
                otherProps
              />
              <AnnoyedField
                value={someValue}
                onChange={someChange}
                class='test'
                otherProps
              />
            <Child/>
        </Component>
    )
}


これは宣言的な方法で書かれていますが、読みたくない場合でも読むことができます.実際には、ハンドラーは矢印関数である可能性があり、一部のアマチュア エンジニアは、抽象化せずに長いコードを直接記述する場合があります.その場合、変化する状態の影響の範囲や、イベント ハンドラーで使用される状態の派生元を見つけるのは困難です.

コンテナコンポーネントを使用して問題を解決したことがあります
IOC(Inversion of Control) 理論のように疎結合コンポーネントごとに具体的な動作を注入しますが、これを行うには親からいくつかの子コンポーネントを分離する必要があるという欠点があります.別の方法としては、react フックをカプセル化されたロジックとコンポーネントを 1 つのコンポーネントに混在させることができます.しかし、上記の例で見たように、フックには弱点もあります.

最終的には、フックとコンテナー レイヤーのようなプレゼンテーション コンポーネントを 1 つのコンポーネントにまとめることはできますが、大きいほど分離した方がよい場合があります.

InnerHooks はこの問題に取り組み、場合によってはビジネス ロジックを 1 つのコンポーネントに完全にカプセル化できることを認識しています.

たとえば、Redux を使用する場合、

    <NumberInput
      innerHooks={() => {
        const num = useSelector(({num}) => { return num})
        const dispatch = useDispatch()
        return {
          value,
          onChange: (e) => {
            dispatch({type: 'mutateNum', payload: num})
          }
        }
      }}
    />


withInnerHooks api generate hoc add innerHooks prop は、入力された Component に対して hoc の中間層で呼び出されることがわかりました. innerHooked の戻り値は、コンポーネント タグから指定された他の props とマージされます.

これを一度書いたら、カットアンドペーストでどこでも別の場所で使用または移動できます.これは、React のフック レンダリング ルールと宣言ポリシーに厳密に従うよりも便利な場合があります.

私の遊び場の例から、それらが疎結合であり、独立した独立したロジックであることがわかります

import "./styles.css";
import React, { useEffect } from "react";
import { useStateFactory, withInnerHooks } from "react-inner-hooks-extension";

function Child(props: any) {
  return (
    <div>
      <input type={props.type} onChange={props.onChange} value={props.value} />
    </div>
  );
}

function Text(props: any) {
  return <div>{props.value}</div>;
}

const NumberField = withInnerHooks(Child);
const StringField = withInnerHooks(Child);
const Timer = withInnerHooks(Text);

export default function App() {
  const [state, usePartialState, setState] = useStateFactory<{
    num: number;
    str: string;
    timer: number;
  }>({
    num: 1,
    str: "foo",
    timer: 0
  });

  return (
    <div className="App">
      <form
        onSubmit={(e) => {
          e.preventDefault();
          // dummy submit method
          const submit = console.log;
          submit(state);
          // RestState
          setState({
            num: 1,
            str: "foo",
            timer: 0
          });
        }}
      >
        <NumberField
          type="number"
          innerHooks={() => {
            const [value, setValue] = usePartialState("num");
            return {
              value,
              onChange: (e: any) => setValue(e.target.value)
            };
          }}
        />
        <StringField
          type="string"
          innerHooks={() => {
            const [value, setValue] = usePartialState("str");
            return {
              value,
              onChange: (e: any) => setValue(e.target.value)
            };
          }}
        />
        <input type="submit" value={"reset"} />
      </form>
      <Timer
        innerHooks={() => {
          const [value, setValue] = usePartialState("timer");
          // This is warned by linter but it can be used.
          useEffect(() => {
            const i = setInterval(() => {
              setValue((state: number) => state + 1);
            }, 1000);
            return () => {
              clearInterval(i);
            };
          }, []);
          return {
            value
          };
        }}
      />
      <div>current:{JSON.stringify(state)}</div>
    </div>
  );
}


この例では、各コンポーネントは、prop スコープ内の関連するロジックのみを囲みます.

これらは、コンテナのように宣言的に記述できます.違いは、それがどのように動作するかを決定できることです.
親コンポーネントのスコープ.

const useHooksContainer = () => {
  const num = useSelector(({num}) => { return num})
  const dispatch = useDispatch()
  return {
    value,
    onChange: (e) => {
      dispatch({type: 'mutateNum', payload: num})
    }
  }
}

() => (
    <NumberInput
      innerHooks={useHooksContainer}
    />
)


懸念



内部フックは React の宣言型ポリシーとは反対に見えますが、カスタム フックによってカプセル化および抽象化することもできます.そして、この機能は React ライブラリ自体に実装するか、レンダリング機能を可能な限り拡張して、パフォーマンスと、どこでも withInnerHooks hoc を書くことの繰り返しを回避することを効果的にする必要があると思います.複数の react-hooks ルールで eslint を使用する場合、このライブラリはそれらのいくつかに違反します.したがって、それらを無視する必要がある場合があります.

ご意見募集中!



ディスカッションにあなたの意見を自由に投稿してください.読んでくれてありがとう.

2022/02/17追記



この議論を参照して、私たちのために my library を改善することができました.参加してくださった皆様、ありがとうございました!