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:
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
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.