При составлении компонентов очень часто возникает задача тонкой настройки содержимого компонента.
Например, у нас есть компонент DatePicker , и в разных частях веб-приложения нам нужно отображать разные кнопки «Применить».
Для решения подобных задач каждая популярная сегодня технология использует концепцию «слотов».
В Angular есть такое нгКонтент , в Вю , Стройный И Веб-компоненты это слоты.
И только популярная библиотека React на сегодняшний день не имеет полноценного понятия слотов.
Есть несколько подходов к решению этой проблемы в React:
- Компонент может либо полностью визуализировать все свои дочерние элементы, либо «попасть» в них через Реагировать.
Дети
API и точно манипулировать потомками - Компонент может объявлять так называемые РендерПропс и отображать возвращаемый от них контент в нужных местах:
<MyComponent renderFooter={data => (<h1>Bye, ${data.name}</h1>)}/>
Вот только пользоваться им не очень удобно, по сравнению с полноценными слотами.
У 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
-
Услуги По Ремонту Настольных Компьютеров
19 Oct, 24 -
Выпущена Mac Os X 10.5.3
19 Oct, 24 -
Статический Анализатор Кода Pc-Lint
19 Oct, 24 -
Мы Не Обижаемся
19 Oct, 24 -
Verywant.ru
19 Oct, 24 -
Где Мой Телефон?
19 Oct, 24