MVC


Эта статья о том, как построить архитектуру web-приложения в соответствии с принципами MVC на основе React и Redux. Прежде всего, она будет интересна тем разработчикам, кто уже знаком с этими технологиями, или тем, кому предстоит использовать их в новом проекте.



Model-View-Controller


Концепция MVC позволяет разделить данные (модель), представление и обработку действий (производимую контроллером) пользователя на три отдельных компонента:


  1. Модель (англ. Model):


    • Предоставляет знания: данные и методы работы с этими данными;
    • Реагирует на запросы, изменяя своё состояние;
    • Не содержит информации, как эти знания можно визуализировать;

  2. Представление (англ. View) — отвечает за отображение информации (визуализацию).


  3. Контроллер (англ. Controller) — обеспечивает связь между пользователем и системой: контролирует ввод данных пользователем и использует модель и представление для реализации необходимой реакции.

React в роли View


React.js это фреймворк для создания интерфейсов от Facebook. Все аспекты его использования мы рассматривать не будем, речь пойдет про Stateless-компоненты и React исключительно в роли View.


Рассмотрим следующий пример:


class FormAuthView extends React.Component {
   componentWillMount() {
      this.props.tryAutoFill();
   }
   render() {
      return (
         <div>
            <input 
               type = "text" 
               value = {this.props.login}
               onChange = {this.props.loginUpdate}
            />
            <input 
               type = "password" 
               value = {this.props.password}
               onChange = {this.props.passwordUpdate}
            />
            <button onClick = {this.props.submit}>
               Submit
            </button>
         </div>
      );   
   }
}

Здесь мы видим объявление компонента FormAuthView. Он отображает форму с двумя Input-ами для логина и пароля, а также кнопку Submit.


FormAuthView это Stateless-компонент, т.е. он не имеет внутреннего состояния и все данные для отображения получает исключительно через Props. Также через Props этот компонент получает и Callback-и, которые эти данные меняют. Сам по себе этот компонент ничего не умеет, можно назвать его "Глупым", так как никакой логики обработки данных в нем нет, и сам он не знает, что за функции он использует для обработки пользовательских действий. При создании компонента он пытается использовать Callback из Props для автозаполнения формы. Про реализацию функции автозаполнения формы этому компоненту тоже ничего неизвестно.


Это пример реализации слоя View на React.


Redux в роли Model


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


Использование Redux подразумевает существование одного единственного объекта Store, в State которого будет хранится состояние всего вашего приложения, каждого его компонента.


Чтобы создать Store, в Redux есть функция createStore.


createStore(reducer, [preloadedState], [enhancer])

Её единственный обязательный параметр это Reducer. Reducer это такая функция, которая принимает State и Action, и в соответствии с типом Action определенным образом модифицирует иммутабельный State, возвращая его измененную копию. Это единственное место в нашем приложении, где может меняться State.


Определимся какие Action-ы нужны, для работы нашего примера:


const EAction = {
   FORM_AUTH_LOGIN_UPDATE    : "FORM_AUTH_LOGIN_UPDATE",
   FORM_AUTH_PASSWORD_UPDATE : "FORM_AUTH_PASSWORD_UPDATE",
   FORM_AUTH_RESET           : "FORM_AUTH_RESET",
   FORM_AUTH_AUTOFILL        : "FORM_AUTH_AUTOFILL"
};

Напишем соответствующий Reducer:


function reducer(state = {
    login : "",
    password : ""
}, action) {
   switch(action.type) {
      case EAction.FORM_AUTH_LOGIN_UPDATE:
         return {
            ...state,
            login : action.login
         };
      case EAction.FORM_AUTH_PASSWORD_UPDATE:
         return {
            ...state,
            password : action.password
         };
      case EAction.FORM_AUTH_RESET:
         return {
            ...state,
            login : "",
            password : ""
         };
      case EAction.FORM_AUTH_AUTOFILL:
         return {
            ...state,
            login : action.login,
            password : action.password
         };
      default:
         return state;
   }
}

И ActionCreator-ы:


function loginUpdate(event) {
   return {
      type : EAction.FORM_AUTH_LOGIN_UPDATE,
      login : event.target.value
   };
}

function passwordUpdate(event) {
   return {
      type : EAction.FORM_AUTH_PASSWORD_UPDATE,
      password : event.target.value
   };
}

function reset() {
   return {
      type : EAction.FORM_AUTH_RESET
   };
}

function tryAutoFill() {
   if(cookies && (cookies.login !== undefined) && (cookies.password !== undefined)) {
      return {
         type : EAction.FORM_AUTH_AUTOFILL,
         login : cookies.login,
         password : cookies.password
      };
   } else {
       return {};
   }
}

function submit() {
   return function(dispatch, getState) {
      const state = getState();
      dispatch(reset());  
      request('/auth/', {send: {
          login : state.login,
          password : state.password
      }}).then(function() {
          router.push('/');
      }).catch(function() {
          window.alert("Auth failed")
      });
   }
}

Таким образом, данные приложения и методы работы с ними описаны с помощью Reducer и ActionCreators. Это пример реализации слоя Model с помощью Redux.


React-redux в роли Controller


Все React-компоненты так или иначе будут получать свой State и Callback-и для его изменения только через Props. При этом ни один React-компонент не будет знать о существовании Redux и Actions вообще, и ни один Reducer или ActionCreator не будет знать о React-компонентах. Данные и логика их обработки полностью отделены от их представления. Я хочу особенно обратить на это внимание. Никаких "Умных" компонентов не будет.


Напишем Controller для нашего приложения:


const FormAuthController = connect(
    state => ({
        login : state.login,
        password : state.password
    }),
    dispatch => bindActionCreators({
        loginUpdate,
        passwordUpdate,
        reset,
        tryAutoFill,
        submit
    }, dispatch)
)(FormAuthView)

На этом всё: React-компонент FormAuthView получит login, password и Callback-и для их изменения через Props.


Результат работы этого демо-приложения можно посмотреть на Codepen.


Что нам дает такой подход


  • Использование только Stateless-компонентов. Большую часть которых можно написать в виде Functional-component, что является рекомендованным подходом, т.к. они быстрее всего работают и потребляют меньше всего памяти
  • React-компоненты можно переиспользовать с разными контроллерами или без них
  • Легко писать тесты, ведь логика и отображение не связаны между собой
  • Можно реализовать Undo/Redo и использовать Time Travel из Redux-DevTools
  • Не нужно использовать Refs
  • Жесткие правила при разработке делают код React-компонентов однообразным
  • Отсутствуют проблемы с серверным рендерингом

Что будет, если отступить от MVC


Велик соблазн сделать какие-то компоненты поудобнее и написать их код побыстрее, завести внутри компонента State. Мол какие-то его данные временные, и хранить их не нужно. И всё это будет работать до поры до времени, пока, например, вам не придется реализовать логику с переходом на другой URL и возвращением обратно — тут всё сломается, временные данные окажутся не временными, и придется всё переписывать.


При использовании Stateful-компонентов, чтобы достать их State, придется использовать Refs. Такой подход нарушает однонаправленность потока данных в приложении и повышает связность компонентов между собой. И то и другое — плохо.


Что будет, если отступить от MVC


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


А еще следует помнить, что в Redux Action-ы обратимы, но изменения State внутри компонентов — нет, и если смешать такое поведение — ничего хорошего не получится.


Заключение


Надеюсь, описание честного MVC подхода при разработке с использованием React и Redux будет полезно разработчикам для создания правильной архитектуры своего web-приложения.


Если есть возможность в полной мере использовать концепцию MVC, то давайте её использовать, и не нужно изобретать что-то другое. Это подход проверенный на прочность десятилетиями, и все его плюсы, минусы и подводные камни давно известны. Придумать что-то лучше навряд ли получиться.


Каждый раз, когда ты смешиваешь Логику и Отображение, Бог убивает котенка

Поделиться с друзьями
-->

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


  1. Ryppka
    18.07.2016 07:21
    +1

    Ну зачем же представлять Господа, как злого и мстительного тимлида, навязывающего всем свое мнение?)))


    1. Mgrin
      18.07.2016 07:56
      +1

      Ну или даже вот такой вопрос — зачем писать используя Flux и называть это MVC? По сути у вас все view обращаются к одной модели через контроллеры этих вьюх посредством кидания экшнов, а модель автоматически обновляет вьюхи… эм, ну то есть у вас однонаправленное движение данных, как и нужно в Flux.


      1. TheShock
        18.07.2016 10:17
        +1

        А кто сказал, что однонаправленное движение противоречит MVC? Такое придумано далеко не во Flux и применяется давным давно.


    1. Mgrin
      18.07.2016 07:56

      простите, не туда откомментировал :)


  1. Mgrin
    18.07.2016 07:49
    +2

    А зачем строить MVC на редуксе с реактом когда их связка создана ради Flux-архитектуры? А если нет возможности использовать Flux то зачем использовать редукс когда можно использовать что-либо более MVC с реактом?


    1. MrCheater
      18.07.2016 09:02
      -2

      Мне кажется, что я использую связку React+Redux вполне стандартным образом. Просто я ввел некоторые ограничения, которые гарантируют, что все плюсы, которые дает нам Redux не будут сведены на нет. Про возможное убийство плюсов я писал.
      Насчет "если нет возможности использовать Flux то зачем использовать редукс" не могу согласиться. Redux — всё-таки это не Flux (про отличия от предшественников можно почитать тут https://github.com/reactjs/redux/blob/master/docs/introduction/PriorArt.md )
      Зачем строить MVC? Потому что плюсов масса, и потому что это легко сделать — посмотрите код всего приложения, там никаких кастомных решений нет — всё беру из коробки.


      1. VasilioRuzanni
        18.07.2016 10:33

        Ну вы и не строите здесь MVC, а просто используете терминологию. Причем тут MVC вообще? Тут он никак не подходит. Если проводить аналогию, React-компоненты — это View+Controller (с точки зрения UI), а с точки зрения Redux-приложения — уже редьюсеры/mapPropsToState — это «контроллер» (но даже язык не поворачивается это так называть). Модель — ее тут вообще нет. Есть состояние, и есть контейнер состояния (Redux), реализующий Flux-паттерн. И зачем пытаться подогнать это под совершенно иной и не имеющий отношение к делу паттерн? Какие плюсы в назывании всего этого MVC?


        1. MrCheater
          18.07.2016 11:05
          -1

          Вы говорите, что модели здесь вообще нет.


          Описание модели из русской википедии:


          Модель (англ. Model):

          Предоставляет знания: данные и методы работы с этими данными;
          Реагирует на запросы, изменяя своё состояние;
          Не содержит информации, как эти знания можно визуализировать;

          Экземпляр Store из Redux:
          Предоставляет знания: данные и методы работы с этими данными — YES
          Реагирует на запросы, изменяя своё состояние — YES
          Не содержит информации, как эти знания можно визуализировать — YES


          Не вижу противоречий.


          Controller связывавает View и Model. Здесь всё стандартно


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


          1. VasilioRuzanni
            18.07.2016 11:32
            -1

            Дело в том, что React-компонент может выполнять все роли из MVC
            Вот поэтому тут не стоит даже упоминать MVC, чтобы избежать любой путаницы.

            Если лезть в детали — Store не меняет свое состояние сам, и не содержит сам по себе никаких методов работы с этими данными. Более того, там могут храниться как данные приложения (domain data), так и различные аспекты состояния UI. Redux — это «state container», а не модель. И не пытается быть последней. Редьюсеры меняют любое состояние, и отнюдь не обязательно связанное с бизнес-логикой приложения. С очень большой натяжкой можно назвать всю эту часть «моделью», и то только для тех, кто вот без MVC вообще не может понять что за что отвечает. Но, как по мне, это изначально неверный способ думать об архитектуре Redux-приложений.


            1. TheShock
              18.07.2016 11:55
              -1

              Если лезть в детали — Store не меняет свое состояние сам, и не содержит сам по себе никаких методов работы с этими данными

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


              1. VasilioRuzanni
                18.07.2016 12:09
                +1

                Да Redux — это вообще не модель, ни тонкая, ни какая-то еще (в терминах привычных реализаций MVC).

                Да не, модным никто и не пытается казаться, и кто-то говорил что-то про «поганый мир ООП» (я так не считаю)? Я только о том, что не нужно все под одну гребенку MVC, потому что так вроде бы проще. Элементов больше, стыки между M, V и C без натяжки не сделать, да и незачем.


    1. Mgrin
      18.07.2016 09:54
      +3

      Я еще раз внимательно перечитал ваш пост — да, вы на самом деле используете редукс так же как и предлагает документация, только называете все терминами MVC. Не могли бы вы привести пример использования middlewares в том же ключе MVC? Просто я в вашем коде вижу чистейший флюкс (ну хорошо, да, не флюкс а редукс, но все же) просто с подогнанными названиями из MVC которые очень хорошо легли на пример из вашей статьи. И в связи с этим ощущаю некий диссонанс :)


      1. MrCheater
        18.07.2016 10:40
        -1

        Насчет middlewares: я использую redux-thunk. Без проблем прикрутится redux-devtools, какой-нибудь логгер и т.д… По-идее middlewares не должны мешать


        Пример использования redux-thunk:


        function submit() {
           return function(dispatch, getState) {
              const state = getState();
              dispatch(reset());  
              request('/auth/', {send: {
                  login : state.login,
                  password : state.password
              }}).then(function() {
                  router.push('/');
              }).catch(function() {
                  window.alert("Auth failed")
              });
           }
        }


  1. braska
    18.07.2016 10:03
    +1

    При том, что статья полезная и шикарная, не могу согласиться, что называть это MVC — корректно. Да, провести аналогии можно, но не более. MVC — это подход, который подразумевает множество потоков данных между множеством моделей и отображений через множество контроллеров. Flux же — это идея о «едином источнике правды» и как следствие об однонаправленном потоке данных. Хоть Вы выше и говорите, что Redux не Flux, но это правда лишь от части. Redux реализует Flux (как идею), но не реализует её так же как эту идею реализует библиотека Flux. В общем и целом, я о том что нужно называть вещи своими именами.


    1. arestov
      18.07.2016 10:24

      MVC — это разделение на M, V и С. А если коротко, то — разделение данных и представления. Ничего больше в этом подходе не подразумевается.

      То что подход многими реализуется через одно место, вовсе не означает, что это подразумевается подходом.


      1. VasilioRuzanni
        18.07.2016 10:36

        Не согласен с утверждением. MVC все же определяет конкретные блоки. Кроме того, в статье «лейблы» M, V и C вешаются на вполне конкретные вещи, так что стоит рассматривать паттерн именно в контексте конкретной реализации (а их у MVC может быть полно).


      1. VolCh
        18.07.2016 14:49

        разделение данных и представления

        Очень грубое упрощение. MVC про разделение бизнес-логики (с данными) и логики представления (может быть тоже со своими данными).


      1. indestructable
        18.07.2016 16:02
        +1

        Тогда MVVM — тоже MVC? Document-Model — тоже MVC?
        Redux — это не совсем MVC, но выполняет ту же функцию: разделяет данные, представления и изменения данных.


  1. aharata
    18.07.2016 10:29

    Не стоит делать MVC потому что это легко, лучше, наоборот, MVC — чтобы хоть как то организовать архитектуру и чтобы далее было легче.


    1. VasilioRuzanni
      18.07.2016 10:38

      Так «архитектуру организовывают» компонентный подход, Flux-паттерн (односторонний data-flow, предсказуемое управление состоянием), причем тут MVC-то?


    1. MrCheater
      18.07.2016 10:49
      -1

      Про "легко" и "MVC" я написал, потому что для реализации его принципов в связке React и Redux не нужно никаких надстроек, не нужно своего кастомного кода — нужно лишь придерживаться пары простых принципов, как раз для хорошей организации кода


  1. staticlab
    18.07.2016 10:37
    +2

    У redux есть проблема в том, что он хранит глобальное состояние приложения. Тогда как компонентный подход подразумевает инкапсуляцию данных. Кроме того, Javascript не проектировался для работы с иммутабельными объектами, поэтому бизнес-логика сильно затуманивается вспомогательным кодом, работающим с данными через Immutable или подобные библиотеки. Наконец, сам по себе redux достаточно низкоуровневый, не зря существует множество вспомогательных библиотек типа redux-actions, redux-act, redux-transducers и т.п. Беда в том, что архитектурные сложности не очевидны сразу, а в реальной разработке часто некогда собирать собственный фреймворк из этого лего.


    1. Miraage
      18.07.2016 10:49

      Если уж приложение растет, никто не запрещает сделать 20 редьюсеров. А так же, использование библиотек вроде normalizr и reselect подразумевается априори.


      1. staticlab
        18.07.2016 11:26
        +1

        Вот, кто «априори» знает хотя бы о FSA, не говоря уже о normalizr. Вообще, я говорю, скорее, о случаях, когда подразумевается динамический выбор компонентов. То есть свои редьюсеры потребуется оформлять в стиле redux-form — функциями-декораторами с отдельным общим редьюсером. И много других подобных мелочей. Но при этом во всех статьях для новичков видим, фактически, только пересказ официального руководства, в котором, понятное дело, таких «маленьких хитростей» практически не предлагается. Потому очень не хватает вменяемой цельной архитектуры, эффективно масштабирующейся и прозрачной для бизнес-логики.


    1. indestructable
      18.07.2016 16:05
      +1

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


      1. staticlab
        19.07.2016 11:53

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

        Или другой пример: обычная форма с полями и валидацией. Поля ввода — обычные textbox. В проекте для форм используется redux-form. Внезапно приходит задача рядом с каждым полем рисовать кнопку undo, если его значение отличается от сохранённого. Казалось бы — UndoableTextBox, — но ведь внутреннее состояние использовать нехорошо, а если вынести на уровень формы, то протечёт абстракция. В простых случаях этим можно пожертвовать, но что если потребуется что-то более сложное?


        1. indestructable
          19.07.2016 12:44

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


          1. staticlab
            19.07.2016 13:04
            +1

            Хорошо, а кто тогда должен предзагружать график и на сколько месяцев вперёд?


            1. indestructable
              19.07.2016 13:25

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


  1. turbo_exe
    18.07.2016 13:50
    +1

    При использовании Stateful-компонентов, чтобы достать их State, придется использовать Refs.

    вы хотите зачем-то знать state дочерних компонентов? зачем использовать refs для того, чтобы читать this.state?


    1. MrCheater
      18.07.2016 14:03

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


  1. VolCh
    18.07.2016 15:05

    Хранение каких-то данных в представлении или контроллере не противоречит MVC, а хранение данных представления или контроллера в модели — противоречит. Единственное хранилище данных в MVC-приложении означает, что у представления нет своих данных, в примере же данные у представления есть, собственно только они и есть, нет даже поля типа isAuthenticated, которое можно было бы отнести к модели.


  1. farwayer
    18.07.2016 16:03
    +1

    Спасибо, статья хорошая, только она как раз для новичков, которые только знакомятся с redux. Поэтому, уж извините, я вставлю свои пару копеек ;) Есть пара серьезных замечаний:

    1. Не называйте это MVC. Во-первых, то что, вы показали — это ближе к MVVM, во-вторых, не пытайтесь подогнать redux под какие-то другие термины. Не ставьте себя в рамки. Redux — это redux, определенная архитектура приложения со своими особенностями и best practices

    2. Может сложится впечатление, что state — это что-то плохое. Это не так! Использовать state можно и нужно, если этот state связан только с внутренним состоянием компонента, а не с состоянием приложения. Например, можно кешировать сложные вычисления уровня представления (смещения, цвет и т.д.). Не стоит загаживать хранилище такими вещами, иначе потом там сам черт ногу сломит.

    3.
    componentWillMount() {
    this.props.tryAutoFill();
    }

    Не делайте так! Вы сами себе противоречите. Компонент не должен знать про автозаполнение. Одной строкой вы сделали из «глупого» компонента «умный». Разделение должно происходить на уровне логики, а не паттернов (callbacks, props). По факту ваш компонент теперь «знает» про redux (точнее про какую-то логику, которую нужно дернуть, чтобы себя заполнить).

    4. В «умных» компонентах нет ничего плохого, при правильном их использовании. Ваш react-redux код по факту и есть «умный» компонент.


    1. MrCheater
      18.07.2016 16:46
      -1

      1. Ставить рамки это хорошо. Чем жестче ограничения, тем однообразнее будет код.


      2. Да — я думаю, что state внутри компонента это плохая идея, и стоит избегать его использования. Единственный случай, когда это правда необходимо — это разработка компонента, который может работать в обоих, в Stateful и Stateless, режимах. Классический пример это React-овский Input. Он умеет Controlled/Uncontrolled режимы. И ведет себя сильно по-разному.


      3. tryAutoFill ничем не отличается от updateLogin. Т.е. Компонент знает, что какие-то callback-и будут, знает интерфейс, но не имеет представления о реализации


      4. "Умные" компоненты появляются только, когда лень реализовывать правильно.
        Не стоит загаживать хранилище такими вещами, иначе потом там сам черт ногу сломит.

      Неужели вы ни разу не упирались в то, что с каким-то Stateful-компонентом со временным State не удается реализовать нужный сценарий, и приходится много переписывать?
      Вас Undo/Redo не беспокоит? Или серверный рендеринг?
      Проблемы не надуманы — они реально существуют


      1. farwayer
        18.07.2016 19:33
        +1

        1. Вопрос философский. По моему скромному мнению разработка — это поиск гармонии, и крайности только мешают в этом деле.

        2. Внутренняя кухня компонента должна оставаться внутренней кухней. Наверх выносятся только те callback и свойства, которые могут быть интересны внешним компонентам. Но тут очень важно чувствовать границу. Приходит с опытом.

        3. >> Т.е. Компонент знает, что какие-то callback-и будут, знает интерфейс

        У вас не вызывает диссонанс фразы «глупый компонент» и «компонент знает»? Это неверно. Компонент предоставляет (!) интерфейс для внешних компонентов. Единственное, о чем знает глупый компонент — это свое внутреннее устройство.

        Он принимает свойства и вызывает callback'и при изменение СВОЕГО состояние. Есть очень простое правило: если вызов callback'а отвечает на вопрос «почему?» — все ок, а если на вопрос «зачем?» — что-то не так. Поэтому, кстати, и принято у callback'ов делать префикс «on». Посмотрите на реактовские onChange, onClick и т.д — такая маленькая подсказка.

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

        В вашем случае вызов tryAutoFill() — это знание о внешней логике. Этот вызов должен делать кто-то более «умный», а в «глупый» компонент должны уйти уже нужные значения.

        4. Поймите, у вас тоже есть умный компонент, только вы назвали его Controller.


      1. hellosandrik
        19.07.2016 09:15
        +1

        Согласен почти со всем, но насчет локального state мне кажется, что ограничения можно сделать полегче. В частности, использовать state только тогда, когда данные в нем нужны только самому этому компоненту и его дочерним элементам. Если они становятся нужны где-то еще, то вынести их в основной store. Простой пример: в компоненте для вкладок номер текущей вкладки хранить в state, но если потребуется переключать вкладку извне, то вынести это в store. Весь рефакторинг будет заключаться в замене setState на dispatch и передаче добавленных в store данных по цепочке компонентов через props. Это может занять немного времени, но по-крайней мере это прямолинейная, рутинная работа, над которой не нужно думать (может быть можно даже сделать плагин для IDE, который будет делать это сам). А вот через ref такие проблемы решать — на самом деле антипаттерн.


        1. Ikarr
          19.07.2016 14:06

          Если речь о redux-приложении, лучше хранить в локальном state только параметры отображения (развернут/свернут пункт меню, например), а всё, что касается данных — в самом redux.


  1. dem0n3d
    19.07.2016 10:29

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


    1. MrCheater
      19.07.2016 11:00

      TODO-App. Там не все идеально, но вот большой пример с тем же подходом https://github.com/MrCheater/react-spa-todo


      1. dem0n3d
        19.07.2016 11:42

        Ну теперь я по крайней мере точно вижу что это не MVC, т.к. модель хранит «filteringMode», т.е. параметр отображения. Ещё впечатляет огромный объём кода для такого простенького приложения. Но того о чём я спрашивал там нет и близко. Как быть если список todos придётся загружать частично, а потом подгружать по мере необходимости? А когда пользователь изменит настройки фильтрации/сортировки? А если их (элементы списка) спользуют другие представления (компоненты)?


  1. Ikarr
    19.07.2016 13:53
    +1

    1. Компонент с componentWillMount — это уже не stateless компонент. Stateless-компонент не должен иметь никаких lifecycle-методов, иначе, по определению, это уже не stateless компонент, т.к. его жизненный цикл это тоже состояние. Настоящий stateless-компонент это чистая функция от props.
    2. Используйте в reducer метод combineReducers: невозможно же читать, тестировать и поддерживать такой код. Кроме того, кто-нибудь же посмотрит и сделает так же.


    1. MrCheater
      19.07.2016 14:49

      Насчет stateless и lifecycle: тут весьма неоднозначно. Сам компонент не знает в какой стадии жизненного цикла находится — из функции render и из других кастомных функций нельзя узнать в какой стадии жизненного цикла сейчас компонент.


      1. Ikarr
        19.07.2016 17:59

        Всё проще, чем вы думаете. Любой stateless-компонент, по определению, можно записать в виде чистой функции от props, например:

        const StatelessName = ({ name, color }) => <strong style={{ color }}>{name}

        Ваш же пример уже нельзя так записать — из-за побочного эффекта в виде, по-сути, коллбека. Корректнее будет сделать HOC вокруг stateless-компонента для описания внешней логики, например, так:

        class NotSoStatelessName extends Component {

        componentWillMount() {

        }
        render() {
        return <StatelessName {...this.props} color={this.state.color} />
        }
        }

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


  1. overmes
    19.07.2016 15:27

    А как в этом MVC логику положить в модель? Это больше на какой-то VC похоже.