Проблемы основных паттернов создания data-driven apps на React.JS

Для создания интерфейсов React рекомендует использовать композицию и библиотеки по управлению состоянием (state management libraries) для построения иерархий компонентов. Однако при сложных паттернах композиции появляются проблемы:


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

Для большинства разработчиков проблема может быть неочевидна, и они перекидывают ее на уровень управления состоянием. Это обсуждается и в документации React:


Если вы хотите избавиться от передачи некоторых пропсов на множество уровней вниз, обычно композиция компонентов является более простым решением, чем контекст.
Документация React. Контекст.

Если пройти по ссылке, мы увидим еще один аргумент:


В Facebook мы используем React в тысячах компонентов, и не находили случаев, когда бы рекомендовали создавать иерархии наследования компонентов.
Документация React. Композиция против наследования.

Конечно, если бы каждый, кто использует инструмент, читал документацию и признавал авторитет авторов, то этой публикации не было. Поэтому разберем проблемы существующих подходов.


Паттерн №1 – Напрямую контролируемые компоненты



Я начинал с этого решения из-за фреймворка Vue, который рекомендует такой подход. Берем структуру данных, которая приходит с бэка или дизайна в случае формы. Пробрасываем в наш компонент – например, в карточку фильма:


const MovieCard = (props) => {
 const {title, genre, description, rating, image} = props.data;
 return (
  <div>
   <img href={image} />
   <div><h2>{title}</h2></div>
   <div><p>{genre}</p></div>
   <div><p>{description}</p></div>
   <div><h1>{rating}</h1></div>
  </div>
 )
}

Стоп. Мы уже знаем о бесконечном расширении требований к компоненты. Вдруг у названия будет ссылка на рецензию фильма? А у жанра – на лучшие фильмы из него? Не добавлять же теперь:


const MovieCard = (props) => {
 const {title: {title},
  description: {description},
  rating: {rating},
  genre: {genre},
  image: {imageHref}
 } = props.data;

 return (
  <div>
   <img href={imageHref} />
   <div><h2>{name}</h2></div>
   <div><p>{genre}</p></div>
   <div><h1>{rating}</h1></div>
   <div><p>{description}</p></div>
  </div>
 )
}

Так мы обезопасим себя от проблем в будущем, но открываем двери ошибке нулевого поинтера. Изначально мы могли прокидывать структуры прямиком из бэка:


<MovieCard data={res.data} />

Теперь каждый раз нужно дублировать всю информацию:


<MovieCard data={{
 title: {res.title},
 description: {res.description},
 rating: {res.rating},
 image: {res.imageHref}
 }} />

Однако мы забыли про жанр – и компонент упал. А если не поставили ограничители ошибок, то с ним и все приложение.


На помощь приходит TypeScript. Упрощаем схему с помощью рефакторинга карточки и элементов, которые ее используют. Благо, все подсвечивается в редакторе или при сборке:


interface IMovieCardElement {
 text?: string;
}

interface IMovieCardImage {
 imageHref?: string;
}

interface IMovieCardProps {
 title: IMovieCardElement;
 description: IMovieCardElement;
 rating: IMovieCardElement;
 genre: IMovieCardElement;
 image: IMovieCardImage;
}
...
 const {title: {text: title},
  description: {text: description},
  rating: {text: rating},
  genre: {text: genre},
  image: {imageHref}
 } = props.data;

Чтобы сэкономить время, все равно прокидываем данные «as any» или «as IMovieCardProps». Что же получается? Мы уже три раза (если используем в одном месте) описали одну структуру данных. И что имеем? Компонент, который до сих пор нельзя модифицировать. Компонент, который потенциально может обвалить все приложение.


Пришло время переиспользовать этот компонент. Рейтинг больше не нужен. У нас два варианта:


Пропс withoutRating ставьте везде, где не нужен рейтинг


const MovieCard = ({withoutRating, ...props}) => {
 const {title: {title},
  description: {description},
  rating: {rating},
  genre: {genre},
  image: {imageHref}
 } = props.data;
 return (
  <div>
   <img href={imageHref} />
   <div><h2>{name}</h2></div>
   <div><p>{genre}</p></div>
   { withoutRating &&
   <div><h1>{rating}</h1></div>
   }
   <div><p>{description}</p></div>
  </div>
 )
}

Быстро, но мы нагромождаем пропсы и сооружаем четвертую структуру данных.


Делаем rating в IMovieCardProps необязательным. Не забываем сделать его пустым объектом по умолчанию


const MovieCard = ({data, ...props}) => {
 const {title: {text: title},
  description: {text: description},
  rating: {text: rating} = {},
  genre: {text: genre},
  image: {imageHref}
 } = data;
 return (
  <div>
   <img href={imageHref} />
   <div><h2>{name}</h2></div>
   <div><p>{genre}</p></div>
   {
    data.rating &&
    <div><h1>{rating}</h1></div>
   }
   <div><p>{description}</p></div>
  </div>
 )
}

Хитрее, но становится сложно читать код. Опять же, повторяемся четвертый раз. Контроль над компонентом не очевиден, так как непрозрачно управляется структурой данных. Скажем, нас попросили сделать пресловутый рейтинг ссылкой, но не везде:


  rating: {text: rating, url: ratingUrl} = {},
  ...
   {
    data.rating &&
     data.rating.url ?
     <div>><h1><a href={ratingUrl}{rating}</a></h1></div>
     :
     <div><h1>{rating}</h1></div>
   }

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


Паттерн №2 – Компоненты с собственным состоянием и редюсерами



Одновременно странный и популярный подход. Я использовал его, когда начал работать с React и функционала JSX во Vue стало не хватать. Не раз слышал от разработчиков на митапах, что такой подход позволяет пропускать более обобщенные структуры данных:


  1. Компонент может принимать множество структур данных, верстка остается прежней
  2. При получении данных он обрабатывает их по нужному сценарию
  3. Данные сохраняются в состоянии компонента, чтобы не запускать редюсер при каждом рендере (опционально)

Естественно, к проблеме непрозрачности (1) добавляется проблема перегруженности логикой (2) и добавление состояния в финальный компонент (3).


Последнее (3) диктуется внутренней сохранностью объекта. То есть глубинно проверяем объекты через lodash.isEqual. Если случай продвинут или JSON.stringify, все только начинается. Еще можно добавить timestamp и проверять по нему, если все потеряно. Надобность в сохранении или мемоизации отпадает, так как по сложности вычисления оптимизация может оказаться сложнее редюсера.


Данные пробрасываются с названием сценария (как правило, строкой):


<MovieCard data={{
 type: 'withoutRating',
 data: res.data,
}} />

Теперь пишем компонент:


const MovieCard = ({data}) => {
 const card = reduceData(data.type, data.data);
 return (
  <div>
   <img href={card.imageHref} />
   <div><h2>{card.name}</h2></div>
   <div><p>{card.genre}</p></div>
   { card.withoutRating &&
   <div><h1>{card.rating}</h1></div>
   }
   <div><p>{card.description}</p></div>
  </div>
 )
}

И логику:


const reduceData = (type, data) = {
 switch (type) {
  case 'withoutRating':
   return {
    title: {data.title},
    description: {data.description},
    rating: {data.rating},
    genre: {data.genre},
    image: {data.imageHref}
    withoutRating: true,
   };
  ...
 }
};

На этом шаге возникает несколько проблем:


  1. Добавляя слой логики, мы окончательно теряем прямую связь между данными и отображением
  2. Дублирование логики для каждого кейса значит, что в случае, когда всем карточкам понадобится возрастной рейтинг, его нужно будет прописать в каждом редюсере
  3. Остаются прочие проблемы из шага №1

Паттерн №3 – Перенос логики отображения и данные в управление состоянием



Тут мы отказываемся от шины данных для построения интерфейсов, которой является React. Используем технологию с собственной моделью логики. Вероятно, это самый распространенный способ создания приложений на React, хоть руководство предупреждает, что не стоит использовать контекст таким образом.


Используйте подобные инструменты там, где React не предоставляет достаточного инструментария – например, в маршрутизации. Скорее всего, вы используете react-router. В этом случае для пробрасывания сессии во все страницы больший смысл имело бы использование контекста, а не пробрасывания коллбэка из компонента каждого маршрута верхнего уровня. В React нет отдельной абстракции для асинхронных действий кроме той, что предлагает язык Javascript.


Казалось бы, есть плюс: можем переиспользовать логику в будущих версиях приложения. Но это обман. С одной стороны, она привязана к API, с другой – к структуре приложения, а логика обеспечивает эту связь. При изменении одной из частей, ее требуется переписывать.


Решение: Паттерн №4 – композиция



Метод композиции очевиден, если следовать следующим принципам (не считая аналогичного подхода в книге Design Patterns):


  1. Фронтенд-разработка – разработка пользовательских интерфейсов – использует для верстки язык HTML
  2. Для получения, передачи и обработки данных используется язык Javascript

Поэтому переводите данные из одного домена в другой как можно раньше. В React для шаблонизации HTML используется абстракция JSX, а на деле – набор методов createElement. То есть, к JSX и компонентам React, которые тоже являются элементами JSX, стоит относиться как к методу отображения и поведения, а не трансформации и обработки данных, которые должны происходить на отдельном уровне.


На этом шаге многие и используют способы, перечисленные выше, но они не решают ключевую проблему расширения и модификации отображения компонентов. Как это делать по мнению создателей библиотеки, показано в документации:


function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

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


Увы, этот способ тоже оказался негибким. Вот почему:


  1. Оба пропса – обязательные. Это ограничивает переиспользование компонента
  2. Необязательность означала бы перегружение компонента SplitPane логикой
  3. Вложенности и множественности отображаются не очень семантично.
  4. Эту логику отображения пришлось бы писать заново для каждого компонента, принимающего пропсы.

В итоге, подобное решение может разрастись в сложности даже для достаточно простых сценариев:


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

function App() {
  return (
    <SplitPane
      left={
       contacts.map(el =>
        <Contacts
           name={
             <ContactsName name={el.name} />
           }
           phone={
             <ContactsPhone name={el.phone} />
           }
         />
        )
      }
      right={
        <Chat />
      } />
  );
}

В документации подобный код, в случае компонентов высшего порядка (HOC) и рендер-пропсов называют «адом оберток» (wrapper hell). С каждым добавлением нового элемента читаемость кода становится все сложнее.


Один ответ на эту проблему — слоты — присутствует в технологии Веб-компонентов и в фреймворке Vue. В обеих местах, однако, присутствуют ограничения: во-первых, слоты определены не символом, а строкой, что усложняет рефакторинг. Во-вторых, слоты ограничены по функционалу и не могут сами управлять своим отображением, передавать дочерним компонентам другие слоты или быть переиспользоваными в других элементах.


Если коротко, примерно вот такое, назовем его паттерн №5 – слоты:


function App() {
  return (
    <SplitPane>
      <LeftPane>
        <Contacts />
      </LeftPane>
      <RightPane>
        <Chat />
      </RightPane>
    </SplitPane>
  );
}

Об этом расскажу в следующей статье о существующих решениях для слотового паттерна в React и о моем собственном решении.

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 36

    0

    Есть идеальный паттерн?

      0
      нет
        +1
        Если сказать очень кратко, то то, что описано в статье — это неизбежные грабли привязки данных (биндинга) в декларативные описания. Настройка привязки данных по сути должна быть описана в декларации, а html ничего не знает и не может знать про какую-то там «привязку данных» — он совсем о других вещах. Отсюда у нас или тысячи оберток, которые по сути представляют собой описание привязки данных в JS согласно логике декларативного описания, или же большая свалка данных а-ля реактовский контекст, в котором каждый из объектов будет как-то самостоятельно рыться, надеясь найти данные для себя.

        Для причёсывания этих проблем нам нужны или расширения языка шаблонов, более крутые, чем имеющееся поколение в виде JSX, tagged template literals и т.д, или вообще принципиально другой шаблонизатор, в котором вопросы привязки данных будут решаться сразу (типа $mol, только написанный для людей).
          0

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

        +1

        Если честно, я так и не понял проблем, о которых ты говоришь. Вот компонентвот ещё) с моего недавнего проекта, я никаких проблем со спусканием данных даже близко не ощущал, и все, что идёт до 4-го пункта — это, если честно, для меня какой-то не понятно, чем вызванный, code smell.


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


        Я так понимаю, об этом ты и собираешься рассказать в статье про слоты. Но ты сказал, что "во-первых, слоты определены не символом, а строкой, что усложняет рефакторинг", поэтому я сомневаюсь, что то, что меня, похоже на то, о чем говоришь ты. Скорее всего, сильно отличается реализация, но тем не менее внешнее сходство есть, поэтому скажу про слоты, которые реализованы у меня:


        1. "слоты ограничены по функционалу и не могут сами управлять своим отображением" — могут, так как это точно такие же реакт компоненты, в которые можно спустить пропс
        2. "передавать дочерним компонентам другие слоты" — опять же могут по той же причине, например, можно написать для компонента Card реализацию CardDefault с пропсом title в качестве строки, которое будет вставляться в слот Card.Header, а children использовать для слота тела Card.Body по умолчанию
        3. "быть переиспользоваными в других элементах" — то же самое, это всего лишь компонент

        Плюс ко всему можно, как в моем Card компоненте, использовать рут компонент как неймспейс для всех семантически с ним связанных слотов, что делает их использование очень удобным

          0
          Спасибо за детальный комментарий.

          По первому пункту — я говорю именно о реализации во Vue и WebComponents, хотя я возможно недостаточно ясно это написал.
          По второму и третьему — тут у нас небольшое расхождение понятий. Слоты — не неймспейсы, а определения заранее того, куда именно будет прокинут компонент. У тебя в проекте в чилдрены можно пробросить что угодно, а слоты именно о том, что Start всегда будет в начале, а End — всегда в конце, и их нельзя поменять местами, но можно, например, подставлять что-нибудь по умолчанию, если End — нет (на базовом уровне).

          По вопросу «спускания» данных, в твоем проекте, насколько я понимаю, у тебя все данные и действия с ними идут через внешний менеджер состояния, redux (что я описал в №3), поэтому мне сложно это сопоставить с подходом №4.
            0

            Во-первых, мне не очень понятно, а как композиция мешает использованию глобального состояния. Композиция про организацию компонентов, глобальное состояние про организацию данных, одно на другое вообще не влияет и не должно, в этом в том числе и есть смысл redux.


            Во-вторых, я так понял, что ты пришел в React из Vue и других фреймворков, я, конечно, не знаю, как там это работает, но в реакте доступны 3 основных способа управления данными, это: mobx, context и redux. Они предоставляют исчерпывающие решения по управлению состоянием приложения и все остальное лично для меня выглядит странно, зачем что-то спускать вручную (речь про данные), поэтому я просто пропустил первые 3 пункта, особо не вникая.


            3 пункт я пропустил, т.к. ты написал "Перенос логики отображения и данные в управление состоянием". Перенос логики, мягко сказать, сильно не поощряется в реакте и это не про redux вообще-то, данные — да, а логика все ещё в компоненте, примером тому — приведенные мной с моего проекта

              0

              Это и было первым, что я процитировал в своей статье. Если пройти по ссылке, там написано, чем, по мнению создателей фреймворка стоит ограничить использование контекста:


              • "например, текущий аутентифицированный пользователь, UI-тема или выбранный язык"

              То есть как достаточно ограниченный инструментарий для задач именно по контексту, а не "исчерпывающие решения по управлению состоянием приложения"


              Из этого следует, что:


              • "Если вы хотите избавиться от передачи некоторых пропсов на множество уровней вниз, обычно композиция компонентов является более простым решением, чем контекст."

              То есть вопрос именно о логике и данных, а не "про организацию компонентов".


              Естественно, тут обсуждается написание react.js-приложений с этими аксиомами, но есть и другие подходы.


              Также, ты не совсем прав, в твоём приложении логика вынесена вместе с данными, например, resetTasksList и unshareTask. И было бы странно оставлять это в компоненте, а не энкапсулировать это вместе с данными, если использовать методологию ООП.

                –1
                Говоря «исчерпывающие решения по управлению состоянием приложения» я имел в виду, что используя эти три подхода: MobX, Redux и Context и/или их комбинации — можно построить все, что необходимо с точки зрения управления данными приложения (поэтому сюда НЕ относится локальный спуск пропсов через композицию). Ты почему-то услышал только про контекст и прицепился к нему.

                Далее, видимо, ты не совсем понимаешь, что такое композиция. Композиция — передача данных (или компонентов) в пропсы другого компонента (в т.ч. через проп children), чтобы отредерить их, собственно, в этом компоненте. Здесь, куда ты, кстати, и ссылаешься, приведены примеры ее использования. Композиция в т.ч. может быть использована для передачи данных, но только на не слишком большую глубину, например, когда требуется передать что-то в дочерний функциональный компонент футера из большого компонента страницы, которые префетчит данные (кол-во страниц, например).
                Именно в таких случаях, когда передача может быть осуществлена легко из-за малой глубины и малого кол-ва спускаемой информации, либо когда эта информация используется только в рамках одного компонента и его потомков, документация говорит не использовать контекст.
                Говорить, что композиция — подход, с помощью которого можно только передавать данные приложения и таким образом выстраивать архитектуру — просто безграмотно.

                Я, например, в своем проекте использовал redux как глобальное хранилище данных, контекст для легкого получения данных о теме, ну и композицию для вышеперечисленных случаев. Одно другому не мешает, а лишь выполняет свои поставленные задачи

                «То есть вопрос именно о логике и данных, а не „про организацию компонентов“» — нет, вопрос о том, чтобы не использовать контекст там, где можно обойтись без него, а также не использовать наследование

                «ты не совсем прав, в твоём приложении логика вынесена вместе с данными» — так речь не про логику обработки данных, а про логику отображения, которая все еще в компоненте.
              0

              Про слоты я понял, интересно посмотреть, как ты это реализовал

                0
                • render props — частный случай слотов
                • React.Children.map
              0
              Если честно, я так и не понял проблем, о которых ты говоришь. Вот компонент (а вот ещё) с моего недавнего проекта, я никаких проблем со спусканием данных даже близко не ощущал, и все, что идёт до 4-го пункта — это, если честно, для меня какой-то не понятно, чем вызванный, code smell.

              Если вы приводите в пример код компонента, в котором нужно сначала сильно так промотать вниз, чтоб найти собственно сам компонент, потому что перед ним такой толстый слой обёрточных типов и кода; а потом еще и в шаблоне без поллитры не разобраться, потому что он представляет из себя сплошной тернарный оператор — то чёт мне кажется, что вы пропустили очень-очень много иронии в вашем комментарии, написав «не понял проблем».

              Вы серьезно не видите проблем в том, что о назначении вашего компонента можно догадаться либо по его имени, либо после доброго часа вникания в его код?
                0

                Я серьезно не вижу проблем с подобным кодом, так как его сплит в другие компоненты был бы связан с ненужной болью в очке. Мой компонент решает поставленные задачи минимально доступным образом, мне интересно было бы посмотреть, как ты подобное реализуешь. Если ты не можешь с ходу разобраться, как он работает, это не означает, что он плохой

                  0

                  В вашем компоненте больше половины — типовой код, который следовало бы вынести в отдельные абстракции ( в том числе и в отдельные компоненты), а не копипастить его из компонента в компонент.

                    0
                    Где ты увидел там типовой код? И где копипаста? Ни слова конкретно. Что можно вынести в отдельные компоненты я вынес — футер, карточка задачи. Каким образом ты предлагаешь вынести стейт и методы серча?
                      0

                      Вас самого-то не смущает, что в коде вы 8 раз ветвитесь по searchOpened в каждой ветке копипастя одну и ту же логику? А ручной менеджмент этих бесконечных loading? А ручная реализация очередного переключателя и одинаковой логики переключения страниц?

                        0
                        «А ручной менеджмент этих бесконечных loading? А ручная реализация очередного переключателя и одинаковой логики переключения страниц?» — покажешь готовый аналог, выполняющий те же задачи и более кратко — я буду только счастлив, а пока твои претензии выглядят довольно пустословно.

                        «каждой ветке копипастя одну и ту же логику» — какую? Строку скажи, мне самому интересно, где у меня там копипаста
                          0
                          покажешь готовый аналог, выполняющий те же задачи и более кратко — я буду только счастлив

                          mol.js.org


                          какую? Строку скажи, мне самому интересно, где у меня там копипаста

                          ctrl+f => searchOpened

                            –1
                            АХАХ, ясно
                              0

                              Выставляете себя ещё большим дураком такими комментариями.

                                –1
                                ок бумер
                      0
                      Разве что в dashboard.tsx есть карточки одинаковые, тут согласен, но мне слишком лень сплитить их
                –3
                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 /
                  0
                  Мне кажется, ваша основная проблема в том, что вам просто чисто эстетически не нравится передавать компоненты в виде props.
                  А этот код вызывают у вас как минимум раздражение
                  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 мне кажется избыточен.
                    0

                    Именно, меня раздражает "ад оберток", так как это разрушает достаточно удобный и интуитивный для меня формат — JSX — и заставляет либо выносить даже небольшие кусочки JSX в отдельные JS-переменные, либо разбираться в уровнях вложения фигурных скобок.


                    По слотам — это не совсем так, то что я предлагаю — и синтактический сахар над обёртками пропсов (по аналогии коллбэками, для которых есть сахар в виде промисов, а для промисов в виде async функций), и некоторое расширение возможностей которое позволяет не писать каждый раз часто используемые сценарии (как then-catch).


                    То есть, leftpane и rightpane вставлялись бы так же как и с композицией, только в одном месте, и их нельзя было бы поменять местами или использовать для них другой компонент (не используя override, который может изначально быть не включаемым по решению создателя слота).


                    Я изначально собирался сразу написать о слотах, но когда я выстувал по этому вопросу на митапе, основные вопросы были именно по обоснованию/проблематике, так что в статье я решил начать именно с этого момента.

                      0
                      wrapped-hell — «ад оберток» — это вот:
                      image

                      Если в SplitPane использовать left и right в виде props то будет один уровень вложенности. В вашем случае уровней будет два. И где тут решение проблемы «ад оберток»?
                        0
                        Действительно, я имел в виду именно неудобный синтаксис. Однако уровень вложенности в devtools все равно будет один, так как это те же пропсы, просто выраженные внутри пропса children, а не как отдельный пропс. Опять же, если использовать возможности React API, появляется достаточно богатый выбор сценариев рендера — слот может быть и контейнером стилей и сам может быть компонентом, который можно при желании подменять в манере DI, или просто быть указателем того, куда рендерятся его children (и если их множество, то как и в случае с пропсом нужна либо обертка Fragment (что в слот можно подставлять автоматически, а в пропс нужно писать руками), либо map-функция в рендере дочернего компонента).
                          0

                          Между render-props и children-slots разница больше визуальная. По сути этот 1 и тот же подход. Вы так или иначе передаёте это через props. В случае render-props напрямую, в случае children-slots через children property в виде древа. В отличии от Vue (наверное) в React есть некоторые проблемы с обработкой children на нижележащих слоях — вы вынуждены искать нужные вам компоненты чтобы обходиться с ними как со слотами. И это препятствует тому, чтобы можно было их как-нибудь обернуть своим компонентом. Не то чтобы это всегда сильно было нужно. Но когда в очередной раз натыкаешься на эту родовую травму React хочется в очередной раз отказаться от любых абстракций на уровне children.

                            0
                            Красота JSX это по сути синтаксический сахар. А вы начинаете городить из этого свой велосипед.
                            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 файла. Этого вполне достаточно чтобы не путаться в скобочках
                              0
                              выносить даже небольшие кусочки JSX в отдельные JS-переменные
                              все правильно, так и нужно делать
                              Тяжелый случай.
                              Разбивать код на части следует, если от этого есть польза. Иначе это лишь ухудшит читаемость кода.

                              А если переписать так?
                              image
                              В таком маленьком куске кода создается ошибочное ощущение, что все хорошо.
                              Когда компоненте будет хотя бы 50 строк, а SplitPane будет между другими компонентами (например, пара десятков div-ов над ним и под ним), то уже будет ухудшена читаемость кода. Т.к. тесно связанные SplitPane, contacts, chat отделены друг от друга. В случае слотов такого не будет.
                                0
                                Автор публикации не смог предоставить каких либо аргументов в качестве доказательства удобства теории слотов. Я нашел его репозитарий на гитхабе там нет оценок кроме его собственной. Кроме того, после
                                моего примера
                                const element = (
                                  <h1 className="greeting">
                                    Привет, мир!
                                  </h1>
                                );
                                // или
                                const element = {
                                  type: 'h1',
                                  props: {
                                    className: 'greeting',
                                    children: 'Привет, мир!'
                                  }
                                };
                                
                                — это ведь одно и тоже!
                                автор вообще не смог ничего ответить. Видимо, это связано с поверхностными знаниями в том как работает React и как код трансформируется в JSX.

                                Самая идея этих слотов противоречит первоначальной цели: избавление от ада оберток, потому что добавляет лишний слой вложенности.

                                image

                                Я использую Babel вместе с Webpack для компиляции кода, и там есть встроенные механизмы оптимизации которые уберут мои созданные переменные и подставят коды компонентов в нужные места.

                                Вы сами используете эти слоты?
                                  0
                                  Я не согласен с автором статьи, что «избавление от ада оберток» является целью слотов. Они для того же, для чего и render props, только с той разницей, что JSX код содержимого компонента не выносится вне этого компонента. Да, добавляется дополнительный слой вложенности. Но логически понятный. Что мы видим в редакторе, то же отображается в devtools. Так что не вижу в этом особой проблемы.

                                  Я не использовал слоты. В реакте их нет. А реализовывать самому неохота.
                                  Использовал только сторонние компоненты со слотами вроде такого: react-bootstrap.github.io/components/modal/#modals-live
                                    0
                                    Я где то выше писал:
                                    3. Мне кажется, это изобретение очередного table — сурового и беспощадного со своей спецификацией.
                                    а тут вы как раз кидаете пример и там прям так все и расписано. В случае с bootstrap 4 очень даже не плохо выглядит, Хотя статус беты немного отпугивает и то что нужно еще учить документацию по тому как bootstrap обернут в слоты.
                                      +1
                                      Ну а что поделать. Если из коробки нет нужного функционала и общепринятых решений тоже нет, каждый решает свои проблемы как может.

                                      После упоминания про beta я понял, что использовал reactstrap, а не react-bootstrap)
                                      reactstrap.github.io/components/modals
                      0
                      [delete]
                        0

                        Рискну закинуть свой велосипед — react-templated

                        Only users with full accounts can post comments. Log in, please.