web-dev-qa-db-ja.com

Reactは、参照と命令コードはダイアログの表示と非表示に適したツールではないと述べています。なぜでしょうか?

docs はこれを明示的に述べています:

宣言的に行うことができるものには、参照を使用しないでください。

たとえば、Dialogコンポーネントでopen()メソッドとclose()メソッドを公開する代わりに、isOpenプロパティをそれに渡します。

私はアイデアを手に入れ、再利用可能なコンポーネントについてほぼ同意します。コンポーネント内に隠された状態(参照を介して操作する必要がある)がないと、推論と再利用が容易になります。これがアプリケーションの具体的なダイアログにも適用できるかどうか疑問に思っています。もしそうなら、適切なツールを使用するための理解が欠けているようです。

たとえば、私のアプリケーションでは、ファイルを作成、名前変更、および削除するためのオプションを含むメニューを表示するファイルツリーがあります。各オプションには適切なダイアログが表示され、これらのダイアログは独自のコンポーネントにカプセル化されます。コンポーネント階層の最下部には、再利用可能なDialogコンポーネントがあります。

FileTree
  FileMenu
  CreateFileDialog
    Dialog
  RenameFileDialog
    Dialog
  DeleteFileDialog
    Dialog

ここで2つのアプローチを見ることができます。

  • FileTreeは、3つのダイアログすべての可視状態を管理します。つまり、参照はありません。
  • 個々のダイアログ、または可視性の状態を管理する基本的な再利用可能なダイアログ、必要に応じてダイアログを表示するための参照を使用

私の具体的なケースでは、Dialogはサードパーティのステートレスコンポーネントですが、2番目のアプローチにステートフルラッパーを追加することもできます。そして私には、それははるかに優れているようです。「正しい」コンポーネントにカプセル化された状態処理コードのインスタンスは1つしかありません。 FileTreeに可視状態の3つのインスタンスがあることは、これらの状態(createFileDialogVisibleなどの長い名前またはcreateFileDialog.visibleなどの名前空間オブジェクト)を区別する方法が必要であることを意味します。

(実際には、新しいファイルの名前など、管理する追加の状態がありますが、それはタイトルが要求する範囲の外です。それは、すべてのダイアログ状態をFileTree。)

オプション2の方が明らかに優れていると思うので、ガイドラインが最初のものを奨励することを目的としているとは思えません。別のアプローチがないのですか、それともこのガイドラインの範囲外にあるものですか?


付録

私の推論における、私が間違っていることの良い候補であると考えるステップ:

  • 参照を使用する唯一の方法は、親コンポーネントに状態を取得することです
  • 親コンポーネントで状態を管理すると、記述されたコードの重複/名前空間の問題が避けられなくなります
  • 参照を使用すると、実際にはコードの重複が少なくなり、人間工学的コードが増える

次のコードサンプルは、ステートレスダイアログ(Dlg)、ステートフルラッパー(Dialog)、およびそのステートフルダイアログのConsumerを示しています。アプローチ1では、FileTreeDialogに対応しますが、可視状態が複数存在する場合は例外です。アプローチ2では、FileTreeConsumerに対応し、ここでも複数の参照があります。

import * as React from 'react';

function Dlg({ visible, onClose }) {
  return visible ? (
    <div>
      dialog is shown
      <button onClick={onClose}>close</button>
    </div>
  ) : (
    <dvi>dialog is hidden</dvi>
  );
}

function Dialog(props, ref) {
  const [visible, setVisible] = React.useState<boolean>(false);

  const show = () => setVisible(true);
  const hide = () => setVisible(false);

  React.useImperativeHandle(ref, () => ({ show, hide }));

  return <Dlg visible={visible} onClose={hide} />;
}

Dialog = React.forwardRef(Dialog);

function Consumer() {
  const dialogRef = React.useRef(null);

  const showDialog = () => {
    // eslint-disable-next-line no-throw-literal
    if (dialogRef.current === null) throw 'ref is null';

    dialogRef.current.show();
  };

  return (
    <div>
      <div>
        <button onClick={showDialog}>open</button>
      </div>
      <Dialog ref={dialogRef} />
    </div>
  );
}
1
Silly Freak

間違っていることが判明したのは、私の推論の2番目のステップです。

親コンポーネントで状態を管理すると、記述されたコードの重複/名前空間の問題が避けられなくなります

すでにフックを使用している場合、これを独自のフックに抽出して、この方法でAPIを提供するのは簡単です。 refsと比較した場合の追加の利点は、最初のレンダリングの前にフックがnullではないため、型チェッカーを満足させるためにnullチェックを行う必要がないことです。実現すべきことは次のとおりです:Dialogコンポーネントは純粋な動作であり、Dlgがすでに行ったこと以外にレンダリングを追加しませんでした。動作はフックで抽象化するのが最適です。

このアプローチは、たとえば material-ui-popup-state で使用されます。この質問の例に適用されます:

_import * as React from 'react';

function Dlg({ visible, onClose }) { /* unchanged */ }

function useDialog() {
  const [visible, setVisible] = React.useState(false);

  const show = () => setVisible(true);
  const hide = () => setVisible(false);

  const bindDlg = () => ({
    visible,
    onClose: hide,
  });

  return { show, hide, bindDlg };
}

function Consumer() {
  const dialog = useDialog();

  return (
    <div>
      <div>
        <button onClick={dialog.show}>open</button>
      </div>
      <Dlg {...dialog.bindDlg()} />
    </div>
  );
}
_

_material-ui-popup-state_は、<Menu {...bindMenu(popupState)}>の代わりに<Menu {...popupState.bindMenu()}>を作成するという点で、少し異なります。そのようにする理由は2つあります。

  • bindMenuは通常の関数として宣言できます。useCallbackを介して関数をメモするかどうかは問題ありません。
  • bindMenuの他にbindPopoverなどもあります。フックの一部にならないようにすることで、フック自体が、プロップを接続するコンポーネントにとらわれないままになります。
0
Silly Freak

複数のダイアログを同時に開くことができるかどうかは明確ではありませんでした。

可能な場合は、独自の可視状態を処理できます。

彼らはできないので、実際にはFileTreeで小さな状態マシンを使用するか、またはReact Contextに保持してドリルを防止します。

このようにして、false、「作成」、「名前変更」、「削除」に設定できる一般的な「setOpenDiog」状態があります。現在の選択に応じて正しいものを表示します。

0
Stan Lindsey