React: slots como o filho do amigo da mãe

Ao compor componentes, muitas vezes surge a tarefa de personalizar o conteúdo de um componente. Por exemplo, temos um componente DatePicker e precisamos exibir diferentes botões Aplicar em diferentes partes do aplicativo da web.

Para resolver esses problemas, toda tecnologia popular hoje usa o conceito de "slots". Angular possui ngContent , Vue , Svelte e WebComponents possuem slots. E apenas a popular biblioteca React não tem um conceito completo de slots hoje.

Existem várias abordagens para resolver este problema no React:

  1. Um componente pode renderizar todos os seus filhos inteiramente ou "subir" neles por meio da API React.Children e manipular os filhos de forma pontual

  2. Um componente pode declarar os chamados renderProps e renderizar o conteúdo retornado deles nos lugares certos:

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

A abordagem renderProps é geralmente bem conhecida e não tem falhas fundamentais. A menos que não seja muito conveniente usá-lo, em comparação com slots completos. O NPM tem várias bibliotecas, como react-view-slot , mas não acho que sejam convenientes o suficiente e, o mais importante, simplesmente resolvem o problema.

Decidi tentar consertar essa falha fatal e agora direi como.

Eu vejo o objetivo - não vejo a implementação

Antes de programar, é útil saber qual API você deseja obter. É assim que meu esboço do resultado desejado parecia:

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

A ideia era criar os slots necessários e armazenar o componente de função em uma propriedade estática, e então usá-lo apropriadamente nos lados de envio e recebimento.

, . – , , , -, . , , 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/>.

Assim, em 20 linhas de código, implementamos um conceito que muitos desenvolvedores em outras tecnologias gostam muito e que falta no React.

Publiquei a implementação concluída como uma biblioteca react-slotify no GitHub e como um pacote no NPM . Já em TypeScript e com suporte para parametrização de slots . Eu ficaria feliz em receber críticas construtivas.




All Articles