Реакция: Слоты, Как У Сына Подруги Моей Матери

При составлении компонентов очень часто возникает задача тонкой настройки содержимого компонента.

Например, у нас есть компонент DatePicker , и в разных частях веб-приложения нам нужно отображать разные кнопки «Применить».

Для решения подобных задач каждая популярная сегодня технология использует концепцию «слотов».

В Angular есть такое нгКонтент , в Вю , Стройный И Веб-компоненты это слоты.

И только популярная библиотека React на сегодняшний день не имеет полноценного понятия слотов.

Есть несколько подходов к решению этой проблемы в React:

  1. Компонент может либо полностью визуализировать все свои дочерние элементы, либо «попасть» в них через Реагировать.

    Дети API и точно манипулировать потомками

  2. Компонент может объявлять так называемые РендерПропс и отображать возвращаемый от них контент в нужных местах:
      
      
      
      
      
      
       

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

Подход renderProps в целом широко известен и не имеет принципиальных недостатков.

Вот только пользоваться им не очень удобно, по сравнению с полноценными слотами.

У NPM есть несколько библиотек, таких как слот для реагирования , но мне не кажется, что они решают задачу удобно и, главное, достаточно просто.

Я решил попробовать это исправить Роковая ошибка , и сейчас я вам расскажу как.



Я вижу цель, но не вижу реализации

Прежде чем что-либо программировать, полезно знать, какой API вы хотите получить.

Вот как выглядел мой набросок желаемого результата:

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

Идея заключалась в том, чтобы создать необходимые слоты и сохранить их в статическом свойстве функции компонента, а затем использовать их соответственно на отправляющей и принимающей стороне.

Но при попытке реализовать сразу стало понятно, что в таком виде это не очень удобно.

Статическое свойство компонента — это, по сути, синглтон, и не следует писать там что-то, что отличается от экземпляра компонента к экземпляру.

Таким образом, после нескольких попыток у нас теперь есть 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.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, не лежит на поверхности.

Давайте попробуем разобраться.

Основная задача функции — создать и вернуть компонент Слот .

Если удалить все остальное, результат тривиален:

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

Все, что умеет созданный компонент Слот - это значит прятать своих детей до тех пор, пока ему не перейдут приличия showChildren={true} .

Когда мы используем слот при использовании компонента, мы не передаем его, и поэтому Слот Он просто скрывает свое содержимое.

Здесь создается еще один компонент — Рендерер .

Его задача — принять все дочерние компоненты своего пользовательского компонента и найти среди них тот, который нам нужен.

Слот -компонент и визуализировать его, клонировав и передав его 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 }); };

Обратите внимание, что Рендерер также принимает своих потомков и притягивает их, если Слот не найдено.

Это гарантирует отображение содержимого слота по умолчанию.

И последнее, но не менее важное: компонент Рендерер записывается в статическое свойство вновь созданного компонента Слот чтобы его можно было использовать следующим образом: .



Заключение

Таким образом, в 20 строках кода мы реализовали концепцию, которая очень нравится многим разработчикам других технологий и которой нет в React. Готовую реализацию я опубликовал в виде библиотеки реакция-слотификация на GitHub и как пакет в НПМ .

Уже в TypeScript и с поддержкой параметризация слота .

Буду рад конструктивной критике.

Теги: #Разработка сайтов #JavaScript #react.js #react #slot #slots #библиотека javascript

Вместе с данным постом часто просматривают: