Коллеги со стороны бакэнда иногда любезно спрашивают: "а нафига вам тут реакт"? Будем честны и ответим, что без него можно написать довольно приличный код, отдать его на ревью коллеге-фулстеку и получить аппрув после 15 секунд (так быстро не потому, что коллега не беспокоится за качество кода, а просто код весьма компактен, cмотрите ниже). Если подумать чуть-чуть дольше (например, за время заварки чая, которое, как все знают, равно трем минутам), можно найти не менее шести ошибок, а может и больше. Любая ошибка, конечно, весьма субъективна, но я постараюсь объективно объяснить каждую из них.

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

<body>
<script>  

class Component {
  constructor(
    container,
    callback, // callback on add
    emails = [], // state 
  ) {
    this.container = container;
    this.callback = callback;
    this.emails = emails;
  }

  templateForOneEmail(title, body) {
    return `
      <h4>${title}</h4>
      <div>${body}</div>
      <hr />
    `;
  }

  template() {
    const str = this.emails
      .map(([title, body]) => this.templateForOneEmail(title, body))
      .join("");
    return `
      <div id="my-emails">${str}</div>
      <button id="my-button">Add</button>
    `;
  }

  subscribe() {
    const node = this.container.querySelector("#my-button");
    if (node) 
      node.addEventListener("click", this.callback);
  }

  unsubscribe() {
    const node = this.container.querySelector("#my-button");
    if (node)
      node.removeEventListener("click", this.callback);
  }

  render() {
    this.unsubscribe();
    this.container.innerHTML = this.template();
    this.subscribe();
  }

  clear() {
    this.unsubscribe();
    this.container.innerHTML = "";
  }
}


function main() {
  const container = document.createElement("div");
  document.body.appendChild(container);
  const emails = [];

  let comp = null;
  const callback = () => {
    if (!comp) { return; }
    emails.push(["title", "body"]);
    comp.clear();
    comp = new Component(container, callback, emails);
    comp.render();
  }
  comp = new Component(container, callback, emails);
  comp.render();
}

main();

</script>
</body>

Итак, ошибки:

  1. Начнем, пожалуй, с самой безобидной - Array.join в методе template. Вот, не свойство компонента конвертировать между массивом и строкой для рендинга. [jsx]

  2. Перерисовка всего экрана при добавлении элемента. Интуитивно хочется дорисовать, а не перерисовать :) [virtual dom]

  3. Вынужденные неточности с подписками - вызов unsubscribe (ресурсный метод) то тут, то там. Можно возразить, что в данном примере unsubscribe в рендере не нужен, так как мы можем вызвать clear следом, но в этом случае мы, к сожалению, теряем idempotence рендера (см. плиз https://en.wikipedia.org/wiki/Idempotence) [lifecycle]

  4. Ссылка на самих себя в колбеке. Тут хороший вопрос, насколько это плохо для движка V8, но интуитивно создание цикла из ссылок не очень хорошая идея. [тут тег, в котором реакт не одобряет такое]

  5. Создание компонента на каждый колбэк. Тут, возможно, и нет ничего плохого, но данное действие, скорее, увеличивает энтропию (о понимании программы) в целом, чем уменьшает ее. [state observers]

  6. Ручная чистка ресурсов (метод clear нужно вызывать извне), как следствие, легко что-то забыть или потерять. [ownership]

  7. И напоследок, вопрос о композиции таких компонентов - но это, как и на самом среднем проекте, обычно думается потом.

Комментарии (27)


  1. Luchnik22
    08.04.2023 15:44

    Если это вся суть проекта, то да, но если на этом не останавливаться, то лучше использовать хорошо задокументированную, протестированную, абстракцию с которой большинство разработчиков знакомы (привет React), чем писать велосипед шаблонизатор без тестов, без документации, с которым придётся разбираться новым разработчикам


    1. nin-jin
      08.04.2023 15:44

      Если это вся суть проекта, то да, но если на этом не останавливаться, то

      .. получится..

      велосипед без тестов, без документации, с которым придётся разбираться новым разработчикам

      А если вы к нему добавите ещё и..

      хорошо задокументированную, протестированную, абстракцию с которой большинство разработчиков знакомы (привет React)

      ..то погоды это не сделает. Более того, это ухудшит архитектуру проекта.


  1. dopusteam
    08.04.2023 15:44
    +4

    Вот, не свойство компонента конвертировать между массивом и строкой для рендинга

    Что это значит? Несколько раз перечитал, но так и не понял


    1. xyli0o Автор
      08.04.2023 15:44

      [на интуиции] Массив каким-либо образом нужно конвертнуть в строку для отрисовки, так? И вот именно это знание не нужно держать в компоненте. Интереснее, как мне кажется, это знание отдать движку (jsx).


    1. Zimtir
      08.04.2023 15:44

      Речь про SRP (single responsibility principle)

      Да и DI сделать сложнее


      1. Zimtir
        08.04.2023 15:44

        А вообще пишите функциональный код, если ваш проект пока не определился со стеком технологий, так хотя бы модули на ФП вы сможете потом использовать, а если они еще и юнит-тестами обложены, цены вам нет

        Но если бы вы стали городить тут абстракции а-ля адаптеры и так далее — тогда вы делаете велосипед.

        https://anywhere.epam.com/en/blog/javascript-functional-programming-vs-oop


      1. dopusteam
        08.04.2023 15:44

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


  1. yakimchuk-ry
    08.04.2023 15:44
    +1

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

    Как сказали выше, если проект из серии написать и больше не трогать, то можно сделать как угодно, да.


  1. nightlord189
    08.04.2023 15:44

    Если уж отходить от реакта, то можно отойти и от логики "перерендер на каждое изменение", выкинуть все render/subscribe/unsubscribe и добавлять в html новую строчку прямо в обработчике кнопки. Это и куда производительнее будет.


    1. xyli0o Автор
      08.04.2023 15:44

      Кажется интерфейс у компонента будет немного другой, но тз покроет :)


    1. nin-jin
      08.04.2023 15:44
      +1

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


      1. nightlord189
        08.04.2023 15:44

        Согласен, так что если приложение большое, то лучше сразу Реакт и взять.


        1. nin-jin
          08.04.2023 15:44
          +1

          Если приложение большое, то Реакт тем более лучше не брать.


    1. nin-jin
      08.04.2023 15:44

      .


    1. Zimtir
      08.04.2023 15:44

      Первый вопрос, который проект себе должен задать — зачем это мне?

      React — реагировать, если вы делаете простой layout без масштабирования и конечных автоматов — тогда вам и js наверное не нужен и при правильной подаче используйте конструкторы типа WordPress, бизнес, что не понимает зачем ему конкретный стек ориентируется на выживание, а не качество кода.

      Если бизнес выжил — он уже начнёт пилить 2.0, а тут фактически и не масштабируемое решение, чтобы проверить гипотезу на рынке и не стабильный стек для частых релизов


      1. nin-jin
        08.04.2023 15:44

        А что мешает сразу сделать масштабируемое решение, чтобы потом не переписывать всё по нескольку раз?


        1. Zimtir
          08.04.2023 15:44

          Например, реальность


          1. nin-jin
            08.04.2023 15:44
            +1

            Тогда как вы объясните, что одним эта "реальность" мешает, а другим - нет?



  1. amakhrov
    08.04.2023 15:44

    не свойство компонента конвертировать между массивом и строкой для рендинга

    Нет принципиального отличия от "конвертировать объект ({title, body}) в строку для рендеринга". И то, и другое - сериализация данных в строку.

    вызов unsubscribe (ресурсный метод) то тут, то там

    По-моему, тут ансабскрайб вообще не нужен нигде. Его GC соберет - на него нигде не осталось ссылок. В древние века в каком-то браузере это бы привело к утечкам памяти - но не в 2023.

    Ссылка на самих себя в колбеке

    Не вижу ничего страшного.

    Создание компонента на каждый колбэк

    Необязательно же так делать. Можно сделать comp.setData(emails) . И компонент просто перерисуется


    1. amakhrov
      08.04.2023 15:44

      Основные проблемы с этим кодом - отсутствие экранирования (привет, XSS) и полная перерисовка страницы на каждый чих. Первую проблему можно решить легким шаблонизатором. Вторую - только точечной подпиской на изменения и скурпулезной работой с DOM. И вот тут-то мы получим, в том числе, и проблему с композицией. А если мы перерисовываем все всегда, то компоновать копмоненты - тривиально.


      1. xyli0o Автор
        08.04.2023 15:44

        Про xss - да, хороший поинт


    1. Zimtir
      08.04.2023 15:44

      Тут любой ревьювер должен понять что это программирование ради программирования, кейс с xss — это еще цветочки

      Мы пишем код не ради написания кода, а ревьюверы не чтобы тешить свое чсв

      Если вы пилите качественный Энтерпрайз, то ревьювер обязан reject-ить такой PR/MR на этапе его создания

      Если вы пишите гипотезу абы-как тогда откуда вас тут появилась культура ревью? Форс-пушьте в мастер. Если это какие-то инженерные вилы (не люблю реакт = не понимаю его), тогда бизнес просто тратит деньги на то, чтобы разработчики делали вид, что они не самозванцы, раз так глубоко шарят в надежде написать свой велосипед

      Отсюда же вытекает следующее:

      Энтерпрайз с большим прошлым и будущим — пишем норм на стабильном решении

      Тестим гипотезу — берите no code или ucoz

      Нет фронтендеров? Привет ASP.NET MVC

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

      Культура написания качественного кода != культура написания своих велосипедов

      В разработке нужно отталкиваться от принципов, никто не любит приходить на проекте и разгребать легаси за тем, кого поторопили и кто не разобрался и не смог сказать бизнесу «нет», единственная книга Мартина для рекомендации «идеальный программист»


  1. dom1n1k
    08.04.2023 15:44
    +4

    Вообще, идея хорошая — показать на практическом примере, какие неочевидные проблемы решает популярный фреймворк (помимо общих рассуждений про удобство и упрощение найма).


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


    Вот и здесь было бы интересно почитать. Типа есть такая типичная задача, в лоб она решается вот таким подходом, но в этом коде спрятаны с такие-то проблемы. А авторы фреймворка их предусмотрели так-то и так-то.
    Но реализация (статьи) неудачная: ничего толком не понятно — ни постановка задачи, ни логика решения, ни объяснения проблем.


  1. Overdozed
    08.04.2023 15:44

    Вы надоели уже экспериментировать. Наэксперементируются и ливают проект. А потом рефактори за ними их эксперименты.


  1. PavelZubkov
    08.04.2023 15:44

    Минутка продаж


    Если жалко брать большой реакт для маленького приложения, возьмите $mol

    https://pavelzubkov.github.io/my_app

    декларативное описание приложения https://github.com/PavelZubkov/my_app/blob/master/app.view.tree

    логика к нему на ts
    https://github.com/PavelZubkov/my_app/blob/master/app.view.ts

    стили
    https://github.com/PavelZubkov/my_app/blob/master/app.view.css.ts


    1. PavelZubkov
      08.04.2023 15:44

      Если декларативное описание сложно прочитать, можно спросить у chatgpt