Комментарии 37
Есть идеальный паттерн?
Для причёсывания этих проблем нам нужны или расширения языка шаблонов, более крутые, чем имеющееся поколение в виде JSX, tagged template literals и т.д, или вообще принципиально другой шаблонизатор, в котором вопросы привязки данных будут решаться сразу (типа $mol, только написанный для людей).
По первому пункту — я говорю именно о реализации во Vue и WebComponents, хотя я возможно недостаточно ясно это написал.
По второму и третьему — тут у нас небольшое расхождение понятий. Слоты — не неймспейсы, а определения заранее того, куда именно будет прокинут компонент. У тебя в проекте в чилдрены можно пробросить что угодно, а слоты именно о том, что Start всегда будет в начале, а End — всегда в конце, и их нельзя поменять местами, но можно, например, подставлять что-нибудь по умолчанию, если End — нет (на базовом уровне).
По вопросу «спускания» данных, в твоем проекте, насколько я понимаю, у тебя все данные и действия с ними идут через внешний менеджер состояния, redux (что я описал в №3), поэтому мне сложно это сопоставить с подходом №4.
Это и было первым, что я процитировал в своей статье. Если пройти по ссылке, там написано, чем, по мнению создателей фреймворка стоит ограничить использование контекста:
- "например, текущий аутентифицированный пользователь, UI-тема или выбранный язык"
То есть как достаточно ограниченный инструментарий для задач именно по контексту, а не "исчерпывающие решения по управлению состоянием приложения"
Из этого следует, что:
- "Если вы хотите избавиться от передачи некоторых пропсов на множество уровней вниз, обычно композиция компонентов является более простым решением, чем контекст."
То есть вопрос именно о логике и данных, а не "про организацию компонентов".
Естественно, тут обсуждается написание react.js-приложений с этими аксиомами, но есть и другие подходы.
Также, ты не совсем прав, в твоём приложении логика вынесена вместе с данными, например, resetTasksList и unshareTask. И было бы странно оставлять это в компоненте, а не энкапсулировать это вместе с данными, если использовать методологию ООП.
Если честно, я так и не понял проблем, о которых ты говоришь. Вот компонент (а вот ещё) с моего недавнего проекта, я никаких проблем со спусканием данных даже близко не ощущал, и все, что идёт до 4-го пункта — это, если честно, для меня какой-то не понятно, чем вызванный, code smell.
Если вы приводите в пример код компонента, в котором нужно сначала сильно так промотать вниз, чтоб найти собственно сам компонент, потому что перед ним такой толстый слой обёрточных типов и кода; а потом еще и в шаблоне без поллитры не разобраться, потому что он представляет из себя сплошной тернарный оператор — то чёт мне кажется, что вы пропустили очень-очень много иронии в вашем комментарии, написав «не понял проблем».
Вы серьезно не видите проблем в том, что о назначении вашего компонента можно догадаться либо по его имени, либо после доброго часа вникания в его код?
В вашем компоненте больше половины — типовой код, который следовало бы вынести в отдельные абстракции ( в том числе и в отдельные компоненты), а не копипастить его из компонента в компонент.
Вас самого-то не смущает, что в коде вы 8 раз ветвитесь по searchOpened
в каждой ветке копипастя одну и ту же логику? А ручной менеджмент этих бесконечных loading
? А ручная реализация очередного переключателя и одинаковой логики переключения страниц?
function SplitPane(props) {
return (
<div className="SplitPane">
{
props.left &&
<div className="SplitPane-left">
{props.left}
</div>
}
{
props.right &&
<div className="SplitPane-right">
{props.right}
</div>
}
</div>
);
}
Для сравнения то же самое на view.tree:
$my_split_pane $mol_view
sub /
<= Left $my_view sub <= left /
<= Right $my_view sub <= right /
И примеры использования:
OnlyContacts $my_split_pane
left <= contacts_views /
Right null
OnlyChat $my_split_pane
Left null
right <= message_views /
FullView $my_split_pane
left <= contacts_views /
right <= message_views /
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
function App() {
return (
<SplitPane>
<LeftPane>
<Contacts />
</LeftPane>
<RightPane>
<Chat />
</RightPane>
</SplitPane>
);
}
1. А что если кто-то будет пере-использовать компонент SplitPane и поменяет местами LeftPane и RightPane? В случае float верстки это может быть критично.
2. А если в корень SplitPane засунуть другой компонент или div?
3. Мне кажется, это изобретение очередного table — сурового и беспощадного со своей спецификацией.
В моем приложении у меня как раз если список чатов и список контактов. И я использовал тот же подход что и вас «паттерн №5 – слоты». Только в моем случае SplitPane — это компонент обертка над css-table, а LeftPane и RightPane — обертка над css-td. Дочерних компонентов может быть или один или два и они даже могут быть изменены местами. В других случаях паттерн №5 мне кажется избыточен.
Именно, меня раздражает "ад оберток", так как это разрушает достаточно удобный и интуитивный для меня формат — JSX — и заставляет либо выносить даже небольшие кусочки JSX в отдельные JS-переменные, либо разбираться в уровнях вложения фигурных скобок.
По слотам — это не совсем так, то что я предлагаю — и синтактический сахар над обёртками пропсов (по аналогии коллбэками, для которых есть сахар в виде промисов, а для промисов в виде async функций), и некоторое расширение возможностей которое позволяет не писать каждый раз часто используемые сценарии (как then-catch).
То есть, leftpane и rightpane вставлялись бы так же как и с композицией, только в одном месте, и их нельзя было бы поменять местами или использовать для них другой компонент (не используя override, который может изначально быть не включаемым по решению создателя слота).
Я изначально собирался сразу написать о слотах, но когда я выстувал по этому вопросу на митапе, основные вопросы были именно по обоснованию/проблематике, так что в статье я решил начать именно с этого момента.
Если в SplitPane использовать left и right в виде props то будет один уровень вложенности. В вашем случае уровней будет два. И где тут решение проблемы «ад оберток»?
Между render-props и children-slots разница больше визуальная. По сути этот 1 и тот же подход. Вы так или иначе передаёте это через props. В случае render-props напрямую, в случае children-slots через children property в виде древа. В отличии от Vue (наверное) в React есть некоторые проблемы с обработкой children на нижележащих слоях — вы вынуждены искать нужные вам компоненты чтобы обходиться с ними как со слотами. И это препятствует тому, чтобы можно было их как-нибудь обернуть своим компонентом. Не то чтобы это всегда сильно было нужно. Но когда в очередной раз натыкаешься на эту родовую травму React хочется в очередной раз отказаться от любых абстракций на уровне children.
const element = (
<h1 className="greeting">
Привет, мир!
</h1>
);
// или
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Привет, мир!'
}
};
— это ведь одно и тоже!Однако уровень вложенности в devtools все равно будет один, так как это те же пропсы, просто выраженные внутри пропса children, а не как отдельный пропсв вашем случае будет один лишний уровень вложенности для LeftPane и для RightPane — проверьте, если не верите.
выносить даже небольшие кусочки JSX в отдельные JS-переменныевсе правильно, так и нужно делать
Действительно, я имел в виду именно неудобный синтаксися использую Visual Studio Code с prettier по нажатию save файла. Этого вполне достаточно чтобы не путаться в скобочках
Тяжелый случай.выносить даже небольшие кусочки JSX в отдельные JS-переменныевсе правильно, так и нужно делать
Разбивать код на части следует, если от этого есть польза. Иначе это лишь ухудшит читаемость кода.
В таком маленьком куске кода создается ошибочное ощущение, что все хорошо.А если переписать так?
Когда компоненте будет хотя бы 50 строк, а SplitPane будет между другими компонентами (например, пара десятков div-ов над ним и под ним), то уже будет ухудшена читаемость кода. Т.к. тесно связанные SplitPane, contacts, chat отделены друг от друга. В случае слотов такого не будет.
— это ведь одно и тоже!const element = ( <h1 className="greeting"> Привет, мир! </h1> ); // или const element = { type: 'h1', props: { className: 'greeting', children: 'Привет, мир!' } };
Самая идея этих слотов противоречит первоначальной цели: избавление от ада оберток, потому что добавляет лишний слой вложенности.
Я использую Babel вместе с Webpack для компиляции кода, и там есть встроенные механизмы оптимизации которые уберут мои созданные переменные и подставят коды компонентов в нужные места.
Вы сами используете эти слоты?
Я не использовал слоты. В реакте их нет. А реализовывать самому неохота.
Использовал только сторонние компоненты со слотами вроде такого: react-bootstrap.github.io/components/modal/#modals-live
3. Мне кажется, это изобретение очередного table — сурового и беспощадного со своей спецификацией.а тут вы как раз кидаете пример и там прям так все и расписано. В случае с bootstrap 4 очень даже не плохо выглядит, Хотя статус беты немного отпугивает и то что нужно еще учить документацию по тому как bootstrap обернут в слоты.
После упоминания про beta я понял, что использовал reactstrap, а не react-bootstrap)
reactstrap.github.io/components/modals
Рискну закинуть свой велосипед — react-templated
2. Необязательность означала бы перегружение компонента SplitPane логикой
4. Эту логику отображения пришлось бы писать заново для каждого компонента, принимающего пропсы.
Эти проблемы можно решить просто введя еще одну обертку:
function SafeDiv({children, ...props}) {
return (
children ?
<div {...props}>
{children}
</div>
:
null
);
}
Теперь можно смело писать:
function SplitPane(props) {
return (
<div className="SplitPane">
<SafeDiv className="SplitPane-left">
{props.left}
</SafeDiv>
<SafeDiv className="SplitPane-right">
{props.right}
</SafeDiv>
</div>
);
}
Проблемы основных паттернов создания data-driven apps на React.JS