Архитектура веб-интерфейсов: деревянное прошлое, странное настоящее и светлое будущее

    image

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

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

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

    NB: В качестве примеров в статье будут использоваться только те фреймворки, с которыми непосредственно имел дело автор, и существенное внимание здесь будет уделено React и Redux. Но, несмотря на это, многие описываемые здесь идеи и принципы носят общий характер и могут быть более-менее успешно спроецированы на другие технологии разработки интерфейсов.

    Архитектура для чайников


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

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

    Что касается качества архитектуры, то обычно она выражается в следующий свойствах:

    Сопровождаемость: уже упомянутая предрасположенность системы к изучению и модификации (сложность обнаружения и исправления ошибок, расширения функциональности, адаптации решения к другой среде или условиям)
    Заменяемость: возможность изменения реализации любого элемента системы без затрагивания других элементов
    Тестируемость: возможность убедиться в корректности работы элемента (возможность управления элементом и наблюдения его состояния)
    Портируемость: возможность повторного использования элемента в рамках других систем
    Используемость: общая степень удобства системы при эксплуатации конечным пользователем

    Отдельного упоминания также стоит один из самых ключевых принципов построения качественной архитектуры: принцип разделения ответственности (separation of concerns). Заключается он в том, что любой элемент системы должен отвечать исключительно за одну единственную задачу (применяется, кстати говоря, и к коду приложения: см. single responsibility principle).

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

    Три самых важных слова


    Одним из самых известных паттернов разработки интерфейсов является MVC (Model-View-Controller), ключевой концепцией которого является разделение логики интерфейса на три отдельные части:

    1. Model — отвечает за получение, хранение и обработку данных
    2. View — отвечает за визуализацию данных
    3. Controller — осуществляет управление Model и View

    Данный паттерн также включает в себя описание схемы взаимодействия между ними, но здесь эта информация будет опущена в связи с тем, что спустя определенное время широкой общественности была представлена улучшенная модификация этого паттерна под названием MVP (Model-View-Presenter), которая эту исходную схему взаимодействия значительно упрощала:

    image

    Поскольку разговор у нас идет именно о веб-интерфейсах, то здесь использован еще один довольно важный элемент, который обычно сопровождает реализацию данных паттернов — роутер (router). Его задача — это считывание URL и вызов ассоциированных с ним презентеров.

    Работает представленная выше схема следующим образом:

    1. Router считывает URL и вызывает связанный с ним Presenter
    2-5. Presenter обращается к Model и получает из него необходимые данные
    6. Presenter передает данные из Model во View, который осуществляет их визуализацию
    7. При пользовательском взаимодействии с интерфейсом View уведомляет об этом Presenter, что возвращает нас ко второму пункту

    Как показала практика, MVC и MVP не являются идеальной и универсальной архитектурой, но они все равно делают одну очень важную вещь — обозначают три ключевые области ответственности, без которых в том или ином виде не может быть реализован ни один интерфейс.

    NB: По большому счету понятия Controller и Presenter обозначают одно и то же, а разница в их названии необходима только для дифференциации упомянутых паттернов, которые отличаются лишь в реализации коммуникаций.

    MVC и серверный рендеринг


    Несмотря на то, что MVC является паттерном для реализации клиента, он находит свое применение и на сервере. Более того, именно в контексте сервера проще всего продемонстрировать принципы его работы.

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

    — Router считывает данные из полученного HTTP-запроса (GET /user-profile/1) и вызывает связанный с ним Controller (UsersController.getProfilePage(1))
    — Controller обращается к Model для получения необходимой информации из базы данных (UsersModel.get(1))
    — Controller передает полученные данные во View (View.render('users/profile', user)) и получает из него HTML-разметку, которую передает обратно клиенту

    В данном случае View обычно реализовывается следующим образом:

    image

    const templates = {
      'users/profile': `
        <div class="user-profile">
          <h2>{{ name}}</h2>
          <p>E-mail: {{ email }}</p>
          <p>
            Projects: 
            {{#each projects}}
              <a href="/projects/{{id}}">{{name}}</a>
            {{/each}}
          </p>
          <a href=/user-profile/1/edit>Edit</a>
        </div>
      `
    };
    
    class View {
      render(templateName, data) {
        const htmlMarkup = TemplateEngine.render(templates[templateName], data);
        return htmlMarkup;
      }
    }
    

    NB: Код выше намеренно упрощен для использования в качестве примера. В реальных проектах шаблоны выносятся в отдельные файлы и перед использованием проходят через этап компиляции (см. Handlebars.compile() или _.template()).

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

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

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

    Что касается самого подхода с генерацией HTML-разметки средствами сервера, то в силу низкого UX этот подход постепенно начал вытесняться SPA.

    Backbone и MVP


    Одним из первых фреймворков, позволявших полностью вынести логику отображения на клиент, был Backbone.js. Реализация Router, Presenter и Model в нем достаточно стандартна, а вот новая реализация View заслуживает нашего внимания:

    image

    const UserProfile = Backbone.View.extend({
      tagName: 'div',
      className: 'user-profile',
      events: {
        'click .button.edit':   'openEditDialog',
      },
      openEditDialog: function(event) {
        // ...
      },
      initialize: function() {
        this.listenTo(this.model, 'change', this.render);
      },
      template: _.template(`
        <h2><%= name %></h2>
          <p>E-mail: <%= email %></p>
          <p>
            Projects: 
            <% _.each(projects, project => { %>
               <a href="/projects/<%= project.id %>"><%= project.name %></a>
            <% }) %>
          </p>
          <button class="edit">Edit</button>
      `),
      render: function() {
        this.$el.html(this.template(this.model.attributes));
      }
    });
    

    Очевидно, что реализация отображения существенно усложнилась — к элементарной шаблонизации добавилось прослушивание событий из модели и DOM, а также логика их обработки. Более того, для отображения изменений в интерфейсе крайне желательно выполнять не полный повторный рендеринг View, а осуществлять более тонкую работу с конкретными DOM-элементами (обычно средствами jQuery), что требовало написания большого количества дополнительного кода.

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

    И на этом проблемы с новой реализацией View не заканчивались:

    В дополнение к вышесказанному здесь достаточно затруднено использование вложенных друг в друга View. Со временем эта проблема была разрешена с помощью Regions в Marionette.js, но до этого разработчикам приходилось изобретать свои собственные трюки для решения этой достаточно простой и часто возникающей задачи.

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

    Но, несмотря на перечисленные проблемы, данный подход оказался более чем жизнеспособным, а ранее упомянутое развитие Backbone в виде Marionette до сих пор может успешно применяться для разработки SPA.

    React и пустота


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

    Give It Five Minutes
    React challenges a lot of conventional wisdom, and at first glance some of the ideas may seem crazy.

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

    React is a JavaScript library for creating user interfaces by Facebook and Instagram. Many people choose to think of React as the V in MVC.

    Главная концепция, которую нам предлагает React — это понятие компонента, который, собственно, и предоставляет нам новый способ реализации View:

    class User extends React.Component {
      handleEdit() {
        // ..
      }
      render() {
        const { name, email, projects } = this.props;
        return (
          <div className="user-profile">
            <h2>{name}</h2>
            <p>E-mail: {email}</p>
          <p>
            Projects: 
            {
              projects.map(project => <a href="/projects/{project.id}">{project.name}</a>) 
            }
          </p>
          <button onClick={this.handleEdit}>Edit</button>
          </div>
        );
      }
    }

    В использовании React оказался невероятно приятен. В числе его неоспоримых преимуществ были и по сей день остаются:

    1) Декларативность и реактивность. Больше нет необходимости в ручном обновлении DOM при изменении отображаемых данных.

    2) Композиция компонентов. Построение и изучение дерева View стало совершенно элементарным действием.

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

    Почему это записано в недостатки? Да потому, что сейчас React является наиболее популярным решением для разработки веб-приложений (пруф, еще пруф, и еще один пруф), он является точкой входа для новых фронтенд-разработчиков, но при этом совершенно не предлагает и не пропагандирует ни какую-либо архитектуру, ни какие-либо подходы и лучшие практики для построения полноценных приложений. Более того, он изобретает и продвигает свои собственные нестандартные подходы вроде HOC или Hooks, которые не имеют применения за пределами экосистемы React. Как результат — каждое приложение на React решает типовые проблемы как-то по-своему, и обычно делает это не самым правильным способом.

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

    If the only tool you have is a hammer, everything begins to look like a nail.

    С их помощью разработчики решают совершенно немыслимый диапазон задач, далеко выходящий за пределы визуализации данных. Собственно, с помощью компонентов реализуют абсолютно все — от media queries из CSS до роутинга.

    React и Redux


    Наведению порядка в структуре React-приложений в значительной степени способствовало появление и популяризация Redux. Если React — это View из MVP, то Redux предложил нам достаточно удобную вариацию Model.

    Главной идеей Redux является вынос данных и логики работы с ними в единое централизованное хранилище данных — так называемый Store. Данный подход полностью решает проблему дублирования и рассинхронизации данных, о которой мы говорили немного ранее, а также предлагает и множество других удобств, к которым среди прочего можно отнести легкость изучения текущего состояния данных в приложении.

    Еще одной не менее важной его особенностью является способ коммуникации между Store и другими частями приложения. Вместо прямого обращения к Store или его данным нам предлагают использование так называемых Actions (простых объектов с описанием события или команды), которые обеспечивают слабый уровень связанности (loose coupling) между Store и источником события, тем самым существенно увеличивая степень сопровождаемости проекта. Таким образом Redux не только вынуждает разработчиков использовать более правильные архитектурные подходы, но еще и позволяет пользоваться различными преимуществами event sourcing — теперь в процессе дебага мы легко можем просматривать историю действий в приложении, их влияние на данные, а при необходимости вся эта информация может быть экспортирована, что также крайне полезно при анализе ошибок из «production».

    Общая схема работы приложения с использованием React/Redux может быть представлена следующим образом:

    image

    За отображение данных по-прежнему отвечают React-компоненты. В идеале эти компоненты должны быть чистыми и функциональными, но при необходимости они вполне могут иметь локальное состояние и связанную с ним логику (к примеру, для реализации скрытия/отображения определенного элемента или базовой предобработки пользовательского действия).

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

    В качестве Presenter у нас выступают так называемые компоненты-контейнеры — именно они осуществляют контроль над компонентами отображения и их взаимодействие с данными. Создаются они с помощью функции connect, которая расширяет функциональность переданного в него компонента, добавляя к ним подписку на изменение данных в Store и позволяя нам определить, какие именно данные и обработчики событий следует в него передавать.

    И если с данными здесь все понятно (просто осуществляем маппинг данных из хранилища на ожидаемые «props»), то на обработчиках событий хотелось бы остановиться немного подробнее — они не просто осуществляют отправку Actions в Store, но и вполне могут содержать дополнительную логику обработки события — к примеру, включать в себя ветвление, осуществлять автоматические редиректы и выполнять любую другую работу, свойственную презентеру.

    Еще один важный момент, касающийся компонентов-контейнеров: в силу того, что они создаются через HOC, разработчики довольно часто описывают компоненты отображения и компоненты-контейнеры в рамках одного модуля и экспортируют исключительно контейнер. Это не самый правильный подход, так как для возможности тестирования и повторного использования компонента отображения он должен быть полностью отделен от контейнера и желательно вынесен в отдельный файл.

    Ну и последнее, что мы еще не рассмотрели — это Store. Он служит для нас достаточно специфичной реализацией Model и состоит из нескольких составных частей: State (объект, содержащий все наши данные), Middleware (набор функций, осуществляющих предобработку всех полученных Actions), Reducer (функция, выполняющая модификацию данных в State) и какой-либо обработчик сайд-эффектов, отвечающий за исполнение асинхронных операций (обращение к внешним системам и т.п).

    Больше всего вопросов здесь вызывает форма нашего State. Формально Redux не накладывает на нас никаких ограничений и не дает рекомендаций касательно того, что из себя должен представлять этот объект. Разработчики могут хранить в нем совершенно любые данные (в т.ч. состояние форм и информацию из роутера), данные эти могут иметь любой тип (не запрещается хранить даже функции и инстансы объектов) и иметь любой уровень вложенности. На деле это снова приводит к тому, что из проекта в проект мы получаем совершенно иной подход к использованию State, который через раз вызывает определенное недоумение.

    Для начала согласимся с тем, что нам совсем не обязательно держать в State абсолютно все данные приложения — об этом явно говорит документация. Хранение части данных внутри состояния компонентов хоть и создает определенные неудобства при перемещении по истории действий в процессе дебага (внутреннее состояние компонентов всегда остается неизменным), но вынос этих данных в State создает еще больше трудностей — это значительно увеличивает его размер и требует создания еще большего количества Actions и редюсеров.

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

    А если речь идет о хранении данных из внешних источников, то исходя из того факта, что при разработке интерфейсов мы в подавляющем большинстве случаев имеем дело с классическим CRUD, то для хранения данных с сервера имеет смысл относиться к State как к РСУБД: ключи являются названием ресурса, а за ними хранятся массивы загруженных объектов (без вложенности) и опциональная информация к ним (к примеру, суммарное количество записей на сервере для создания пагинации). Общая форма этих данных должна быть максимально единообразной — это позволит нам упростить создание редюсеров для каждого типа ресурса:

    const getModelReducer = modelName => (models = [], action) => {
      const isModelAction = modelActionTypes.includes(action.type);
      if (isModelAction && action.modelName === modelName) {
        switch (action.type) {
          case 'ADD_MODELS':
            return collection.add(action.models, models);
          case 'CHANGE_MODEL':
            return collection.change(action.model, models);
          case 'REMOVE_MODEL':
            return collection.remove(action.model, models);
          case 'RESET_STATE':
            return [];
        }
      }
      return models;
    };

    Ну и еще один момент, который хотелось бы обсудить в контексте применения Redux — это реализация сайд-эффектов.

    В первую очередь полностью забудьте о Redux Thunk — предлагаемое им превращение Actions в функции с сайд-эффектами хоть и является рабочим решением, но оно перемешивает основные концепты нашей архитектуры и сводит ее преимущества на нет. Намного более правильный подход к реализации сайд-эффектов нам предлагает Redux Saga, хотя и к его технической реализации тоже есть некоторые вопросы.

    Следующее — старайтесь максимально унифицировать ваши сайд-эффекты, осуществляющие обращения к серверу. Подобно форме State и редюсерам мы практически всегда можем реализовать логику создания запросов к серверу с помощью одного единого обработчика. К примеру, в случае с RESTful API этого можно добиться с помощью прослушивания обобщенных Actions вроде:

    { 
      type: 'CREATE_MODEL', 
      payload: { 
        model: 'reviews', 
        attributes: {
          title: '...',
          text: '...'
        }
      } 
    }
    

    … и создавая на на них такие же обобщенные HTTP-запросы:

    POST /api/reviews
    
    {
      title: '...',
      text: '...'
    }

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

    Светлое будущее


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

    Если попытаться заглянуть в будущее, то скорее всего там мы увидим следующее:

    1. Компонентный подход без JSX

    Концепция компоненов крайне успешно себя зарекомендовала, и, скорее всего, мы увидим еще большую их популяризацию. А вот сам JSX может и должен умереть. Да, он действительно достаточно удобен в использовании, но, тем не менее, он не является ни общепринятым стандартом, ни валидным JS-кодом. Библиотеки для реализации интерфейсов, как бы они не были хороши, не должны изобретать новые стандарты, которые потом раз за разом приходится реализовывать во всем возможном тулинге разработчиков.

    2. Стэйт-контейнеры без Redux

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

    3. Повышение взаимозаменяемости библиотек

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

    К чему все это?


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

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

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

    И на этом, наверное, у меня все. Большое спасибо всем, кто нашел в себе силы прочитать статью до конца. Если у вас остались какие-либо вопросы или замечания — приглашаю вас в комментарии.
    Поделиться публикацией

    Комментарии 14

      –1
      Началось за здравие… Кончилось за реакт… Очень надеюсь, что светлое будущее наступит с vue или svelte…
        0
        Автор не про реакт пишет, если вы весь текст прочитали. Реакта в статье — примерно на треть.
        0
        Очередная панацея. Было таких уже десятки.
          +1
          Большое человеческое спасибо за статью. Если хоть еще один человек в мире что-то узнает об MVC и о том, что не надо всё запихивать в компоненты — мир уже станет лучше.
            0
            >> не надо всё запихивать в компоненты

            А у JS-фанов нет выбора. Компоненты в других языках программирования давно и прочно стали эффективным способом понижения сложности. И вот этот общепризнанный подход в его весьма скромной части нашёл путь в мир JS. В результате понижать сложность захотелось с помощью тех инструментов, которые показали JS-кодерам, как можно писать программы гораздо проще. Ну и далее пошло по стандартному пути — теперь у нас есть молоток, значит мы можем забить даже ломик в совершенно неподходящее для него отверстие! И не беда, что долго, сложно, неудобно, ведь главное — у нас есть молоток!

            В целом в мире JS нет инструментов с вменяемой гибкостью. Гибкость же даётся не за красивые глаза (броскую рекламу), а за изучение более зрелых языков и способов разработки. И пока сообщество JS будет продолжать игнорировать давно существующий опыт, он так и будет забивать своим молотком тот самый неудобный ломик. Просто потому, что ломик в проекте должен быть, а из способов его включения в проект — лишь молоток.

            Альтернативный путь сложен — надо изучать другие языки и их экосистему. В итоге можно будет использовать, например, GWT, дающую возможность очень гибко моделировать реальность при помощи гораздо более подходящего для этого языка и с использованием всех накопленных для этого языка архитектурных подходов. Но да, при этом нужно уходить от привычного JS и учить что-то новое…
              +3
              А у JS-фанов нет выбора.

              Вы немного не о том. «Выбор» в огромном количестве случаев нафиг никому не сдался, интерфейсы писать — это не rocket science и банальный квадратно-гнездовой ООП с этим прекрасно справляется.

              Под «компонентами» нынче мало кто понимает компоненты в широком смысле слова, а гораздо чаще — в самом что ни на есть конкретном понимании: что вот этот class MyGreatApp extends React.Component — вот это и есть компонент.

              Лично я, например, в гробу видел этот ваш GWT или еще более другие языки вместе с их экосистемами: от того, что какой-нибудь фронтэндщик возомнит себя звездой и начнёт писать интерфейсы с переподвывертом, обычно только лишь появляется много боли для других программистов (которым это разгребать) и очередное легаси, в которое никто даже палкой тыкать не хочет.

              Речь тут о том, что используя какой-то подход (объектный, компонентный, что угодно) — надо его использовать. А не читать какой-нибудь туториал о том, как сделать to do list, а потом пытаться точно так же писать код в проекте на десятки тысяч строк.
                0
                Компоненты в других языках программирования давно и прочно стали эффективным способом понижения сложности
                Мне интересно — в каких? Я пока-что не слышал, чтобы компоненты за пределами геймдева были общепризнанным подходом.
                  0
                  Частный случай компонентов — классы. Но в более общем (и часто используемом) виде это набор классов, слабо связанных с другими наборами. Слабость связи обычно реализуется на интерфейсах (чего нет в JS), а за интерфейсом может стоять тот самый набор классов, ничего не знающий о других наборах, то есть явно выделенный компонент, хоть и разбитый на несколько классов.
                    0
                    Мда, видимо компонентный подход за пределы геймдева все еще не вышел в том виде, в котором он там(
                    Там компонент — это небольшой объект с данными или функционалом, который в любое время можно добавить в список объекта-контейнера, либо удалить из него. Комбинирую компоненты в объекте-контейнере, получаем нужный итоговый объект. При таком подходе и интерфейсы в большинстве случаев излишни. Без них получается слабая связь.
                    Компоненты на миксины в старом react похожи, только не мержатся с объектом-контейнером, а являются самостоятельными объектами. Да и в объекте-контейнере нет логики в отличие от компонентов react.
                      0
                      >> небольшой объект с данными или функционалом, который в любое время можно добавить в список объекта-контейнера

                      В куче языков именно так можно работать с простыми классами или структурами. Просто кидаем их в список и получаем то, что вы называете «компонентный подход». Но это всего лишь один из способов использования классов/структур.
                        0
                        Да, но вместо этого большинство городят наследование на наследовании или еще что-нибудь, как во фронтенде.
                  +1
                  >>> не надо всё запихивать в компоненты
                  >> А у JS-фанов нет выбора.

                  У JS-фанов компонент — это View. У них своя терминология и ветвь эволюции с неизвестными корнями. То что десятилетиями было очевидно в зрелых языках, они осознают только сейчас.
                    0
                    В целом в мире JS нет инструментов с вменяемой гибкостью.

                    Есть.


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

                    Речь о Java? И чем же она более подходящая?

                  0

                  А с какими типами архитектур вы ещё работали, кроме Backbone и React/Redux?

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

                  Самое читаемое