Русский
Русский
English
Статистика
Реклама

Slot

React слоты как у сына маминой подруги

09.09.2020 20:04:18 | Автор: admin

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

Для решения подобных задач в каждой популярной технологии сегодня применяется концепция "слотов". У Angular это ngContent, во View, Svelte и WebComponents это слоты. И только в популярной библиотеке React полноценной концепции слотов на сегодня нет.

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

  1. Компонент может либо отрендерить всех своих детей целиком, либо "залезть" в них через React.Children API и точечно манипуляровать потомками

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

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

Подход с renderProps, в целом, широко известен и не имеет каких-то принципиальных изъянов. Разве что пользоваться им не слишком удобно, в сравнении с полноценными слотами. В NPM есть несколько библиотек, таких как react-view-slot, но мне не кажется, что они достаточно удобно и, главное, просто, решают задачу.

Я решил попытаться исправить этот фатальный недостаток, и сейчас расскажу, как.

Вижу цель не вижу реализации

Перед тем, как что-то программировать, полезно знать, какое 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>          Inside slot</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, когда компонент рендерит своих потомков через {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/>.

Заключение

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

Готовую реализацию я опубликовал в виде библиотеки react-slotify на GitHub и в виде пакета в NPM. Уже на TypeScript и с поддержкой параметризации слотов. Буду рад конструктивной критике.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru