web-dev-qa-db-ja.com

React.cloneElementを使用して要素を拡張するのはアンチパターンですか?

ReactでUIのポップオーバーコンポーネントを作成しています。そのコンポーネントには、ポップオーバーの表示をトリガーするボタンが含まれています。ボタンは構成可能である必要があるため(ラベル、クラス、プロパティ)、これらの構成パラメーターを子に渡す必要があります。私がこれが起こっているのを私が見る主な方法は2つあります。

  1. Labelとpropsを属性として渡します。
<PopoverComponent buttonLabel={label} buttonProps={buttonProps} />
  1. 実際のボタンを渡します。
const button = <Button>Show</Button>;

<PopoverComponent button={button} />

合併症はポップオーバーの中にある。要素を配置するには、DOMノードにrefが必要なので、PopoverComponent内のボタン要素に小道具を追加する必要があります。 Reactの場合、これはケース1では簡単で標準です。カスタムの小道具をボタンに広げるだけです。例:<Button ref="popoverButton" onClick={this.onClick} {...extraProps }>。ただし、ケース2では、React.cloneElementと小道具を変更します。例えば。、

// INSIDE popoverComponent

const { button } = this.props;

const extendedButton = React.cloneElement(
  button,
  Object.assign(
    {},
    button.props, {
      ref: "popoverButton",
      onClick: this.onClick,
    }),
);

return <div className="popover-control">{ extendedButton }</div>;

使用するのはアンチパターンですかReact.cloneElementおよびReactの子コンポーネントの小道具を変更しますか?

7
James

アンチパターンかどうかは結論には達していませんが、それぞれのアプローチには賛否両論があります。私の実際の実装では、下流の開発者による使いやすさを向上させるために、両方のインターフェースをサポートしています。開発者は、フルコントロールの実際のReact要素またはデフォルトの動作の小道具を提供できます。


cloneElementを使用する

長所

  • アクセス/追加/変更する必要がある小道具を除いて、ボタンインスタンスの完全な制御を消費者に与えます。

短所

  • 何を上書きするかを正確に把握する必要があります(ネストされた要素で保守できなくなります)。したがって、本質的には表面的な変更にのみ適しています。
  • 下流の要素を拡張/変更するのが難しい(前のポイント)。

通過props

長所

  • JSXを介して要素を拡張する方がはるかに簡単です。
  • 拡張機能は、Reactの親/子の従来のパターンに従います。

短所

  • 消費者へのコントロールが少ない。彼らはページに特定のボタンコンポーネントを持っているかもしれません、そして突然、彼らはこのコンポーネントを制御するために小道具/ラベルでのみ渡すことができます。
2
James

コンポーネントに追加の小道具を追加することが React.cloneElement の目的です。

開始点としてelementを使用して、新しいReact要素を複製して返します。結果の要素には、元の要素の小道具と新しい小道具が浅くマージされます。

通常、これを React.Children API の他のメソッドと組み合わせて使用​​して、特定の機能を親コンテナーコンポーネントにカプセル化します。たとえば、任意の数のチェックボックスのシーケンス用のチェックされた状態管理コンテナ:

class CheckboxContainer extends React.Component {
  constructor (props) {
    super();
    this.onClick = this.onClick.bind(this);
    this.state = {
      checked: Array(React.Children.count(props.children)).fill(false)
    };
  }

  onClick (index) {
    this.setState({
      checked: Object.assign([], this.state.checked, {
        index: !this.state.checked[index]
      })
    });
  }

  render () {
    return React.Children.map(this.props.children, (child, index) => {
      return React.cloneElement(child, {
        index: index,
        onClick: () => this.onClick(index),
        checked: this.state.checked[index]
      });
    });
  }
}

class Checkbox extends React.Component {
  render () {
    return (
      <div>
        <input type='checkbox'
          id={`checkbox-${this.props.index}`}
          value={this.props.checked} />
        <label for={`checkbox-${this.props.index}`}>
          {`Child ${this.props.index}`}
        </label>
      </div>
    );
  }
}

class Test extends React.Component {
  render () {
    return (
      <CheckboxContainer>
        <Checkbox />
        <Checkbox />
        <Checkbox />
      </CheckboxContainer>
    );
  }
}

同様のことができます。親のPopoverComponentで開いた状態を管理し、Buttonコンポーネントを子として使用します。ボタンに宣言されているボタンだけに関連するプロップはすべて、親でクローンを作成するときに、ボタンの宣言時にポップオーバー状態の管理に関連するすべてのプロップをマージします。

<PopoverComponent>
  <Button label='label' />
</PopoverComponent>

ただし、ポップオーバーは、基になるHTML要素の上にレンダリングされるように見える必要があるため、特別なケースだと思います。これは、明確な親子関係ではありません。 React.cloneElement を使用する場合、keyrefは他のプロップとは異なる方法で処理されることに注意してください。

元の要素のkeyrefは保持されます。

5
Josh Broadhurst