[ReactJS, JavaScript, Разработка веб-сайтов] React: слоты как у сына маминой подруги
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
При композиции компонентов очень часто возникает задача точечной кастомизации содержимого какого-либо компонента. Например, у нас есть компонент DatePicker, и в разных частях веб-приложения нам нужно отображать разные кнопки "Применить".Для решения подобных задач в каждой популярной технологии сегодня применяется концепция "слотов". У Angular это ngContent, во View, Svelte и WebComponents это слоты. И только в популярной библиотеке React полноценной концепции слотов на сегодня нет. Для решения этой проблемы в React известны несколько подходов:
- Компонент может либо отрендерить всех своих детей целиком, либо "залезть" в них через React.Children API и точечно манипуляровать потомками
- Компонент может объявлять так называемые 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 и с поддержкой параметризации слотов. Буду рад конструктивной критике.
===========
Источник:
habr.com
===========
Похожие новости:
- [HTML, JavaScript] Ajax в обход Mixed Content
- [JavaScript, Интерфейсы, Управление разработкой, TypeScript] Как перенести на TypeScript большую кодовую базу React UI-компонентов
- [JavaScript, Программирование, Разработка веб-сайтов] JavaScript: полное руководство по классам (перевод)
- [Assembler, Игры и игровые приставки, Ненормальное программирование, Разработка игр] Эмуляция NES/Famicom/Денди на веб-технологиях. Доклад Яндекса
- [JavaScript] Прототипы в JS и малоизвестные факты
- [1С-Битрикс, JavaScript, PHP] Меняем страницу просмотра элемента универсальных списков в коробочном Битрикс24
- [CMS, Разработка веб-сайтов] Что такое Headless CMS и почему за ними будущее
- [Nginx, JavaScript, DevOps] Я сделал свой PyPI-репозитарий с авторизацией и S3. На Nginx
- [Разработка веб-сайтов, JavaScript, Математика] Математика верстальщику не нужна, или Временные функции и траектории для покадровых 2D анимаций на сайтах
- [JavaScript, Программирование, Разработка веб-сайтов] JavaScript: делегирование событий простыми словами (перевод)
Теги для поиска: #_reactjs, #_javascript, #_razrabotka_vebsajtov (Разработка веб-сайтов), #_react, #_slot, #_sloty (слоты), #_biblioteka_javascript (библиотека javascript), #_reactjs, #_javascript, #_razrabotka_vebsajtov (
Разработка веб-сайтов
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 23:58
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
При композиции компонентов очень часто возникает задача точечной кастомизации содержимого какого-либо компонента. Например, у нас есть компонент DatePicker, и в разных частях веб-приложения нам нужно отображать разные кнопки "Применить".Для решения подобных задач в каждой популярной технологии сегодня применяется концепция "слотов". У Angular это ngContent, во View, Svelte и WebComponents это слоты. И только в популярной библиотеке React полноценной концепции слотов на сегодня нет. Для решения этой проблемы в React известны несколько подходов:
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> ); } 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> ); }; 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; } export function createSlot() {
const Slot = ({ children, showChildren }) => { return showChildren ? children : null; } return Slot; } 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 }); }; =========== Источник: habr.com =========== Похожие новости:
Разработка веб-сайтов ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 23:58
Часовой пояс: UTC + 5