Reaccionar: tragamonedas como el hijo del amigo de mamá

Al componer componentes, muy a menudo surge la tarea de personalizar el contenido de un componente. Por ejemplo, tenemos un componente DatePicker y necesitamos mostrar diferentes botones Aplicar en diferentes partes de la aplicación web.

Para resolver estos problemas en todas las tecnologías populares de hoy, se utiliza el concepto de "tragamonedas". Angular tiene ngContent , Vue , Svelte y WebComponents tienen ranuras. Y solo la popular biblioteca React no tiene un concepto completo de tragamonedas en la actualidad.

Hay varios enfoques para resolver este problema en React:

  1. Un componente puede representar todos sus elementos secundarios por completo o "escalar" en ellos a través de la API React.Children y manipular puntualmente a los elementos secundarios

  2. Un componente puede declarar los llamados renderProps y renderizar el contenido devuelto por ellos en los lugares correctos:

    <MyComponent renderFooter={data => (<h1>Bye, ${data.name}</h1>)}/>

El enfoque de renderProps es generalmente bien conocido y no tiene fallas fundamentales. A menos que no sea muy conveniente usarlo, en comparación con las máquinas tragamonedas completas. NPM tiene varias bibliotecas, como react-view-slot , pero no creo que sean lo suficientemente convenientes y, lo más importante, simplemente resuelven el problema.

Decidí intentar arreglar esta falla fatal , y ahora te diré cómo.

Veo el objetivo, no veo la implementación

Antes de programar cualquier cosa, es útil saber qué API desea obtener. Así es como se veía mi boceto del resultado deseado:

const Component = props => {
  Component.NameSlot = useSlot(props.children);

  return (
    <div>
      <h1>
        <Component.NameSlot.Receiver>
          Default value
        </Component.NameSlot.Receiver>
      </h1>

      Hello {props.children}
    </div>
  );
};

function App() {
  return (
    <div>
      Hello!
      <Component>
        Foo
        <Component.NameSlot>
          Bobobo
        </Component.NameSlot>
      </Component>
    </div>
  );
}

La idea era crear las ranuras necesarias y almacenar el componente de función en una propiedad estática, y luego usarlo apropiadamente en los lados de envío y recepción.

, . – , , , -, . , , API, :

import {createSlot} from 'react-slotify';

export const MySlot = createSlot();

export const Component = ({children}) => {
  return (
    <div>
      This component contains slot:
      
      <MySlot.Renderer childs={children}>
        This is default slot content
      </MySlot.Renderer>
      
      <div>It also renders children: {children}</div>
    </div>
  );
};
import {Component, MySlot} from './component';

const App = () => {
  return (
    <div>
      <Component>
        <MySlot>Slotted content</MySlot>
        Other content
      </Component>
    </div>
  );
};

, API, , –  MySlot, {children}, , MySlot.Renderer. , JS-, :

export function createSlot() {
  const Slot = ({ children, showChildren }) => {
    return showChildren ? children : null;
  }

  const Renderer = ({ childs, children }) => {
    const slotted = React.Children.toArray(childs).find(child => {
      return React.isValidElement(child) && child.type === Slot;
    });

    if (!slotted || !React.isValidElement(slotted)) {
      return children;
    }
    return React.cloneElement(slotted, { showChildren: true });
  };

  Slot.Renderer = Renderer;

  return Slot;
}

-, 20 . , React- , . . –  Slot. , :

export function createSlot() {
  const Slot = ({ children, showChildren }) => {
    return showChildren ? children : null;
  }
  return Slot;
}

, Slot –  , showChildren={true}. , , Slot .

– Renderer. –  -, Slot-, , showChildren={true}:

  const Renderer = ({ childs, children }) => {
    const slotted = React.Children.toArray(childs).find(child => {
      return React.isValidElement(child) && child.type === Slot;
    });

    if (!slotted || !React.isValidElement(slotted)) {
      return children;
    }
    return React.cloneElement(slotted, { showChildren: true });
  };

, Renderer , , Slot . .

–  Renderer Slot, : <MySlot.Renderer/>.

Así, en 20 líneas de código, hemos implementado un concepto que gusta mucho a muchos desarrolladores de otras tecnologías y del que React carece.

Publiqué la implementación terminada como una biblioteca react-slotify en GitHub y como un paquete en NPM . Ya en TypeScript y con soporte para parametrización de slots . Me encantaría recibir críticas constructivas.




All Articles