web-dev-qa-db-fra.com

Est-il contre-indiqué d'utiliser React.cloneElement pour étendre un élément?

Je crée un composant popover pour une interface utilisateur dans React. Ce composant contient un bouton qui déclenche l'affichage du popover. Parce que le bouton doit être configurable - étiquette, classes, propriétés - j'ai besoin de transmettre ces paramètres de configuration à l'enfant. Il y a deux façons principales de voir cela se produire.

  1. Transmettez l'étiquette et les accessoires comme attributs:
<PopoverComponent buttonLabel={label} buttonProps={buttonProps} />
  1. Passez le bouton réel.
const button = <Button>Show</Button>;

<PopoverComponent button={button} />

La complication vient à l'intérieur du popover. Afin de placer l'élément, j'ai besoin d'un ref au nœud DOM, donc je dois ajouter des accessoires à l'élément bouton à l'intérieur du PopoverComponent. Dans React c'est simple et standard dans le cas 1. Il suffit de répartir tous les accessoires personnalisés sur le bouton. Par exemple, <Button ref="popoverButton" onClick={this.onClick} {...extraProps }>. Cependant, dans le cas 2, nous devons React.cloneElement et muter les accessoires. Par exemple.,

// 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>;

Est-il contre-indiqué d'utiliser React.cloneElement et modifier les accessoires dans les composants enfants dans React?

7
James

Je ne suis pas arrivé à la conclusion si c'est anti-modèle ou non, mais il y a des avantages et des inconvénients à chaque approche. Dans ma mise en œuvre actuelle, je prends en charge les deux interfaces pour améliorer la convivialité des développeurs en aval. Le développeur peut fournir un élément réel React pour un contrôle total ou des accessoires pour un comportement par défaut.


en utilisant cloneElement

Avantages

  • Donnez au consommateur le contrôle total de l'instance du bouton, à l'exception des accessoires dont vous avez besoin pour accéder/ajouter/modifier.

Contre

  • Vous devez savoir exactement quoi écraser (deviendra impossible à maintenir sur les éléments imbriqués). Il n'est donc essentiellement bon que pour une modification superficielle.
  • Plus difficile à étendre/modifier l'élément en aval (point précédent).

Passing props

Avantages

  • Il est beaucoup plus facile d'étendre l'élément via JSX.
  • L'extension suit le modèle traditionnel de parent/enfant dans React.

Contre

  • Moins de contrôle pour le consommateur. Ils peuvent avoir des composants de bouton spécifiques sur leur page, et tout d'un coup, ils ne peuvent passer que des accessoires/étiquette pour contrôler ce composant.
2
James

Ajouter des accessoires supplémentaires à un composant est le but de React.cloneElement :

Clonez et renvoyez un nouvel élément React en utilisant element comme point de départ. L'élément résultant aura les accessoires de l'élément d'origine avec les nouveaux accessoires fusionnés de manière superficielle.

Je l'utilise généralement en combinaison avec d'autres méthodes de React.Children API pour encapsuler certaines fonctionnalités dans un composant de conteneur parent. Par exemple, un conteneur de gestion d'état vérifié pour une séquence d'un nombre quelconque de cases à cocher:

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>
    );
  }
}

Vous pourriez faire quelque chose de similaire. Demandez à votre parent PopoverComponent de gérer l'état ouvert et d'utiliser le composant Button en tant qu'enfant. Quels que soient les accessoires liés uniquement au bouton déclarer sur le bouton, et fusionnez tous les accessoires liés à la gestion de l'état popover lorsque vous le clonez dans le parent:

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

Cependant, je pense que les popovers sont des cas spéciaux car ils devraient apparaître au-dessus des éléments HTML sous-jacents, donc ce n'est pas une relation parent/enfant claire. Notez également que key et ref sont traités différemment des autres accessoires lors de l'utilisation de React.cloneElement :

key et ref de l'élément d'origine seront conservés.

5
Josh Broadhurst