Введение в React и Redux для бекенд-разработчиков



    Если вы как я долгое время считали, что JavaScript – это такой «игрушечный» язык на котором пишут анимашки для менюшек и падающие снежинки на форумах под новый год, а потом очнулись в 2016 году с мыслями WTF: react, flux redux, webpack, babel,… не отчаивайтесь. Вы не одиноки. Материалов по современному фронтенду в сети много, даже слишком много. Под катом еще одно альтернативное мнение о том, каково это учить JavaScript в 2016 году.

    Итак, нам потребуются: React, React Dev Tools, React-Hot-Loader, React-Router, Thunk, Redux, Redux Dev Tools, Semantic-UI, Webpack, Babel и npm.

    На первый взгляд много. Сравним с бекендом: MVC-фреймворк, ORM, Data Mapper, IOC-контейнер, логер, профайлер, очереди, управление конфигурациями, сборка и выкладка… Список можно продолжить, но думаю идея понятна и так: с ростом сложности решаемых задач растет и сложность инструментов. Все чаще мы употребляем термин Web App вместо Web Site, акцентируя внимание на богатых возможностях современных веб-приложений.

    Почему именно этот стек?


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

    React + Redux VS Angular VS Yet Another JS Framework


    Только ленивый не сравнил Angular с React’ом (приписав при этом, дескать, сравнение не корректно, Angular – фреймворк, React – библиотека). Пойдем от обратного. Почему бы не выбрать что-нибудь эдакое, типа Vue, Ember или, упаси боже, Elm?

    1. Поддержка крупных вендоров
    2. Размер сообщества

    Благодаря этим факторам, вероятность выживания Angular и React выше. Простите, другие замечательные хипстерские решения, нам не по пути. Итак, почему React? Лично для меня выбор был не простым:

    1. Меня пугали jsx-файлы
    2. Я кое-что умел на Angular 1.x и переходить на другую технологию было психологически не комфортно
    3. ng2 по-умолчанию идет в комплекте с TypeScript, что мне как стороннику статической типизации ближе
    4. ng подкупал подходом «работает из коробки». Копаться в многообразии npm-пакетов решительно не хотелось.

    Короче, я заставил себя изучить все статьи-сравнилки и написать Todo App на React, что склонило чашу весов в противоположную сторону. Ключевыми факторами для меня стали:

    1. HTML-шаблоны Angular — ужасны и их синтаксис меняется от версии к версии. В React шаблон – это JavaScript, потому что компонент – не более чем View. Сообщения об ошибках в React лучше.

    2. Как ни странно, TypeScript. При более детальном изучении оказалось, что не все так прекрасно. Во-первых TypeScript – это не полноценный язык со статической типизацией, а транспилер. Это сильно ограничивает возможности использования шаблонов и мета-программирования. Во-вторых, далеко не все npm-пакеты идут в комплекте с d.ts-файлами. Короче, Flow показался проще в прикручивании. В-третьих, у TypeScript есть как ярые фанаты, так и противники. Если фанаты TS сравнительно лояльны к ES6, то обратное – не верно. ES6 получает дополнительное очко к Bus Factor.
      Если вам нравится TypeScript, ничто не мешает использовать его вместе с React. Просто конкретно мне он пока не дал критического объема преимуществ, чтобы заставить тратить время на еще один элемент в стеке.
    3. Доклад Дэна Абрамова про «путешествия во времени». Если ваш опыт в бекенде похож на мой, то вы без труда увидите, что новомодный flux – это CQRS и Event Sourcing «вид в профиль». Вместо проекций – редюсеры, а вместо команд и доменных событий – экшны. А если вы работали, например, с WPF, то разобраться с React – вообще дело пары вечеров.
      Да, Redux можно использовать и с Angular, он никак не привязан к React, но для React уже есть react-redux и react-hot-loader. Наверное, для Angular тоже есть, но мейнтейнер Redux’а явно на стороне React.

    Для React и Redux доступно два расширения Chrome. Рекомендую поставить оба, чтобы сделать отладку приятной.

    Таким образом, связка React + Redux:
    1. Более-менее проста в изучении, потому что в основе лежит простая идея ui = f(state => props), где f — это реакт-компонент, state — redux, а state => props — это react-redux.
    2. Не тащит за собой дополнительных зависимостей
    3. Обладает лучшим на данный момент Tool Support (IDE и плагины для Chrome)

    Есть еще всякие ништяки, вроде React Native, но я им не пользовался, поэтому поделиться на эту тему мне, к сожалению, нечем.

    А flux и все эти модные словечки. Как это работает?


    Возможно, для фронтенда flux — это некое откровение. Для бекендщика разница между CQRS и flux — не велика. React — это наше представление. Оно может зависеть от props (read-only) или state (mutable). В зависимости от state и props компонент может отображаться по-разному. Эта логика содержится в методе render. Компонент сам себя не перерисовывает. Вместо этого, он полагается на экосистему React. Мы можем либо изменить свое состояние (с помощью метода setState), либо быть перерисованными извне (переданы новые props). Обработчики событий для UI-элементов передаются через props. Получается такой код
    <button onClick={this.props.handleClick} />.
    Состояние приложение хранится в Redux и представляет собой json-объект. Redux следит за изменением объекта. Изменением считается изменение ссылки, поэтому вместо изменения текущего состояния необходимо конструировать новое путем копирования и модификации старого. Проще всего это сделать через spread-оператор:
    const newState = {...prevState, somethingNew: 'blah-blah'}
    

    Пакет react-redux осуществляет односторонний байндинг redux state => react component с помощью метода connect. При изменении состояния Redux сам перерисует необходимые компоненты, передав в props dispatch и часть общего state, хранимого в Redux. Какую часть состояния и какие функции на основании store.dispatch передавать — решать вам. Я рекомендую передать все обработчики событий компонента и не «светить» dispatch в компонентах.

    State содержится в Redux, но и у компонентов есть свой state. Какой из них использовать?


    Разработчик Redux предлагает делать как удобнее. Это не совсем формальный совет. У нас сложилась практика использовать state компонента только для форм или в целях оптимизации.

    JSX


    JSX – это не JavaScript. Да, React можно писать без JSX, но проще тогда без React’а. Вообще ситуация с HTML-шаблонами напоминает мне засилье шаблонизаторов для PHP лет десять назад. Самым монструозным из всех был конечно Smarty. Мне казалось, что люди сошли с ума. Как иначе можно было объяснить желание написать шаблонизатор… для шаблонизатора?

    JSX – простой и понятный способ использовать JavaScript в шаблонах. Вам не нужно учить дополнительный ЯП, просто оберните теги в (скобки), а код внутри тегов {в другие скобки}. И все. Да, так просто. Если вас беспокоит разделение логики и представления, перестаньте беспокоиться прямо сейчас. React — это View-библиотека. За состояние приложения (в т.ч. поведение) отвечает Redux. React может «диспатчить» сообщения в store, а Redux будет их обрабатывать либо через редьюсеры (чистые функции), либо через специальные middleware (побочные эффекты).

    React позволяет создавать functional stateless-компоненты:
    const StatelessComponent = props => (<div>Hello, {props.name}!</div>)
    

    Или компоненты-классы:
    class Hello extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    

    Функциональная запись короче и лучше читается. Но я рекомендую не увлекаться экономией строк и вместо:
    export default props => (<div>{props.title}</div>)

    использовать чуть менее лаконичное, но более безопасное:
    const StatelessComponent = props => (<div>{props.title}</div>)
    StatelessComponent.propTypes = {…}
    export default StatelessComponent

    Во-первых, если вы не передадите параметры, то React недвусмысленно намекнет в консоли, что вы не правы. Во-вторых, WebStorm умеет анализировать PropTypes и при авто-дополнении заботливо вставит все required props.

    Babel


    Если вы не поняли на каком языке примеры кода выше, не расстраивайтесь. Это не JavaScript, ну не совсем JavaScript. Это ES6 + JSX. С JSX мы разобрались в параграфе выше – это просто синтаксический сахар для шаблонизации (почти как <?=$var?> в PHP или @Model.Param в Razor).

    С ES6 ситуация чуть сложнее и запутаннее. Если коротко:

    1. JavaScript собрали на коленке под нужны тогдашнего интернета, который представлял собой действительно в основном гипер-текст.
    2. Прошло некоторое время и в сайты начали пихать все что угодно, кроме текста.
    3. Язык в существующем виде перестал удовлетворять нуждам рынка. Консорциум ECMA стал придумывать всякие новые фичи и стандарты языка, да с такой скоростью, что браузеры не успели все внедрить.
    4. В сообществе психанули и написали Babel – транспилер из JavaScript в… JavaScript. Ну в смысле из «нового» JS в «старый», который браузеры поддерживают.

    Babel умеет транспилить не только JS, но и JSX, что позволяет писать React-приложения на ES6.
    Да, есть React.createElement. Можно писать на React-приложения и на ES5, но зачем?

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

    Webpack


    Так, то есть пишем мы на ES6 + JSX, а в бразуере выполняется минифицированный JS. Все это напоминает историю изобретения высокоуровневых ЯП. Люди могли писать более эффективные программы на ассемблере, но предпочли удобство и продуктивность. Раз есть исходники и компилятор (транспилятор в нашем случае), то потребуется и система сборки. Если в вашей пещере было достаточно тепло и уютно, возможно, названия grunt и gulp вам ничего не говорят. Что к лучшему. На данный момент, можно считать (слава богу), что для JS есть только один сборщик – Webpack — оставивший конкурентов позади. Можно считать, что webpack — аналог maven или msbuild (кому что ближе) в мире фронтэнда. Не смотря на то что, конфиги webpack’а на первый взгляд напоминают некромантские свитки, через какое-то время привыкаешь. Наверное, каждый любитель фронтенда должен хотя-бы раз в своей жизни написать tutorial по настройке webpack, также как каждый фанат ФП – tutorial по монадам.

    Что нужно знать про webpack:
    1. Вам потребуется кто-то, кто умеет его настраивать
    2. npm start — для запуска dev-сервера
    3. npm run build для сборки фронта на продакшн

    Вообще Webpack собирает не только JS, но еще и sass, svg, шрифты и вообще все что душе угодно, но я пока еще не готов написать полноценный туториал, так что поищите на просторах интернета.

    Npm


    У Ruby есть gem’ы, у php – composer, у .NET – nuget. Короче, JavaScript тоже потребовался пакетный менеджер. Изначально npm использовался в nodejs-разработке (отсюда и название — node package manager), а для фронта использовался bower. Потребность в последнем как-то отпала сама собой с повсеместным переходом использованием ES6, Webpack и TypeScript. Этот параграф добавлен лишь для того чтобы отметить, что npm использует файл package.json, внутри которого можно написать:

    "scripts": {
        "build": "webpack --config webpack/config.js -p",
        "start": "webpack-dev-server --config webpack/config.js"
      }

    Без этих строк npm run build и npm start не заработают.

    Было бы логичнее вызывать npm build, а не npm run build, но эта команда зарезервирована для внутренних целей npm, так что ничего не выйдет.

    React-Hot-Loader


    Ключ --hot запускает dev-сервер webpack’а с «горячей заменой». Согласитесь, билдить при каждом изменении – довольно уныло. HMR (hot moudle replacement) делает это за вас. react-hot-loader позволяет избежать при этом перезагрузки страницы и потери текущего состояния. Настройка hot-loader’а правда, довольно тонкая работа и вообще фича — экспериментальная и работает не всегда. Особенно сложные отношения у горячей замены с react-router. Но к хорошему быстро привыкаешь и рано или поздно вам захочется написать if(module.hot) для того, чтобы страница не перезагружалась.

    React-Router и Thunk


    Основная ниша React’а в Web – это конечно SPA-сайты. А какой SPA-сайт без навигации и общения с сервером. Первую задачу решает react-router. Здесь альтернатив нет. Из неприятных сюрпризов:

    1. Четвертая версия не совместима с третьей из-за чего у нее проблемы с пакетом history.
    2. Для hot-reload нужно соблюдать некоторые нюансы, иначе в консоли будут появляется предупреждения о том, что нельзя заменить route. К счастью ошибки достаточно информативные и исправить их просто.
    3. RouterMiddleware может сломать Redux плагин при совместном использовании.
    4. Не очевидно, что вместо тегов <Route /> можно использовать JavaScript-объекты и передать их в компонент роутера <Router routes={routes} />. Это бывает полезно, при разработке модульных приложений, когда структура маршрутов не известна заранее.

    Thunk – это middleware для redux, позволяющее диспатчить функции вместо объектов. Чаще всего используется для запросов к серверу. В компоненте делать запросы к серверу – не комильфо. Редьюсеры – вообще дожны быть чистыми функциями. Ничего не остается, как делегировать это middleware.

    Альтернатива thunk – redux-saga. Сага предоставляет больше возможностей, но у нее довольно крутая кривая обучения, и она тащит за собой полифиллы для генераторов. При правильном проектировании логики в компонентах нет, а connect к стейту Redux в основном производится через фабричный метод. В общем, мне показалось, что нет большой разницы, как именно управление перейдет к fetch и будет ли написано yield или then. На сайте Redux пример с thunk, так что по совокупности причин сагу я отложил до лучших времен.

    Semantic-UI


    Раз мы заговорили про SPA-приложения, то кроме навигации и запросов к серверу нужны еще компоненты, которые будут ту самую серверную информацию отображать. Для React есть обвязки Bootstrap, Material UI, Syncfusion Web Essentials (хотя эти обвязки не честные – там внутри jQuery). Наш выбор остановился на Sematic-UI. Решение удалось принять очень быстро – сначала отмели платные компоненты. Material UI не стали использовать из-за обилия анимации (сложнее модифицировать). Остались Bootstrap и Semantic. На Бутстрапе уже пол интернета сделано и в целом, Семантик показался более визуально-привлекательным. В общем, остановились на нем. Сразу оговорюсь, что использование Semantic UI – строго опционально, потому что минифицированная версия весит около 500кб.

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

    Подробнее
    Реклама
    Комментарии 181
    • –27
      >node
      > 0.1*0.1
      0.010000000000000002
      > 0.1*0.2
      0.020000000000000004
      > 0.1*0.3
      0.03
      > 0.1*0.4
      0.04000000000000001
      > 0.1*0.5
      0.05
      > 0.1*0.6
      0.06
      > 0.1*0.7
      0.06999999999999999
      > 0.1*0.8
      0.08000000000000002
      > 0.1*0.9
      0.09000000000000001
      

      У меня все.
      • +20

        IEEE 754


        У меня всё.

        • +4
          Что все? Это IEEE 754, во всех языках, которые его используют так будет.

          А статья — очередное «вступление в мир моды на быдлокод в JS», которых, как сам автор сказал, уже очень много и потому не нужна
          • 0
            Вот только в нормальных языках есть decimal.
            • +1
              … и long int или его аналог. В отличие от JS, в котором все, что не помещается в 4 байта преобразуется в double:
              console.log(12345678999999999-12345678999999998); // 2
              
              • +1

                В JS — всё double, тут никаких int нет by design, если не считать объектные расширения

                • 0
                  Да, извиняюсь, был неправ. Видимые отличия от использования целочисленных типов начинаются с 54 бит.
          • +2
            golang:
            package main
            import "fmt"
            
            func main() {
            	var a float32 = 0.1
            	var b float32 = 0.1
            	fmt.Println(a * b)
            	b = 0.9
            	fmt.Println(a * b)
            }
            

            > 0.010000001
            > 0.089999996
            
            • +2
              Erlang/OTP 19 [erts-8.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false]
              
              1> 0.1 * 0.1.
              0.010000000000000002
              2> 0.1 * 0.2.
              0.020000000000000004
              3> 0.1 * 0.3.
              0.03
              4> 0.1 * 0.9.
              0.09000000000000001
              
              
              • 0

                D:


                import std.stdio;
                
                void main()
                {
                    writeln( 0.1 + 0.2 ); // 0.3
                    writeln( 0.1 + 0.2 == 0.3 ); // true
                }
                • 0
                  а так?
                     0.1 + 0.2 == 0.3f
                  
                  • 0
                    Поздно заметил, что вы складываете. А вообще, никаких неожиданностей
                    writeln(0.1*0.2 == 0.02f); //false
                    
                    • 0

                      Тем не менее:


                      writeln( 0.1 * 0.2 ); //0.02
                      • 0
                            writeln( 0.1 * 0.2 == 0.02.to!double ); //true
                        • +4
                          И все равно не понимаю, что вы пытаетесь доказать? Что операции с числами с плавающей точкой в D не соответствуют стандарту IEEE754?
                          writefln("%1.18e", 0.1*0.2); //2.000000000000000042e-02
                          
                          • –2

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

                            • +4
                              Но речь в этой ветке совсем не про округление при выводе.
                              • –3

                                Именно про округление, перечитайте её :-)

                                • +4
                                  Про округление при выводе здесь никто кроме вас не писал :)
                                  • –3

                                    Вам, конечно, виднее.

                  • +1
                    В данном случае даже опаснее доверять выводу writeln, так как:
                    import std.stdio;
                    
                    void main() 
                    {
                    	double a = 0.1;
                    	double b = 0.2;
                    	if(a + b == 0.3) {
                    		writeln(true);
                    	}
                    	else {
                    		writeln(false);
                    	}
                    }
                    

                    > false
                    

                    Да и в целом в любом языке так, все случае не предугадаете, когда будет 0.3, а когда 0.3000000001
                    Поэтому в любом случае стоит делать проверку на диапазон или любой другой хак.
                    • +3

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

                  • –7
                    Я просто хотел сказать, что для каких-то важных, точных вычислений, JS абсолютно не годится. Вообще, числа с плавающей запятой — это отдельная тема.
                    • +5
                      Он не годится в той же мере, в какой не годятся вообще все вычисления в формате IEEE754 на всех платформах и ЯП.
                    • 0
                      Дай-ка угадаю. Гуманитарий?
                    • 0
                      > Первую задачу решает react-router. Здесь альтернатив нет
                      react-router-redux?
                      • +2
                        Разве эта библиотека — не построена поверх react-router? Мне казалось, что она просто дополнительно пропускает переходы через store, чтобы сохранить time travel. Нет?
                        • +1
                          Ну она синхронизирует текущую позицию в store и обратно — полезно для Time Travel, доступа к данным метонахождения через Redux, что позволяет компоненту в ряде случаев не зависеть от роутера. Но это совершенно точно не «альтернатива» :)

                          (Более чем) реальная альтернатива react-router-у — junctions.js.
                      • –1
                        Как короткое описание своего технологического стека c возможностью изучать тему «вглубь» по ссылкам — статья понравилась.
                        Немного критики. К сожалению, ряд моментов режет глаз.
                        Сравним с бекендом: MVC-фреймворк, ORM, Data Mapper, IOC-контейнер...
                        Почему MVC? Почему фреймворк? Почему ORM? И т.д. Это вовсе не необходимые компоненты бэкенда. Я бы назвал их «модными», но бэкенд может быть построен на совершенно других принципах. Кмк, такое сравнение тут не совсем к месту.
                        Самым монструозным из всех был конечно Smarty. Мне казалось, что люди сошли с ума. Как иначе можно было объяснить желание написать шаблонизатор… для шаблонизатора Perl?
                        Конечно же автор имеет в виду php, а не perl. Фраза о «шаблонизаторе для шаблонизатора» тоже модная, но слишком часто используется не в тему.
                        • +2
                          Автор действительно имеет в виду Perl — первая версия PHP была написана на Perl и под его сильным влиянием. $ перед каждой переменной? Это из Perl!
                          • 0
                            первая версия PHP была написана на Perl
                            Это шутка что ли или я туплю? Насколько мне помнится, php/fi сразу был написан на C.
                            Исправление… Понял о чем вы, о первом наборе скриптов на perl.
                          • 0
                            Почему MVC? Почему фреймворк? Почему ORM? И т.д. Это вовсе не необходимые компоненты бэкенда. Я бы назвал их «модными», но бэкенд может быть построен на совершенно других принципах. Кмк, такое сравнение тут не совсем к месту.

                            Потому что примерно 90% кода из мира веб-разработки, который я видел или поддерживал — это MVC-фрейворк + ORM и не более. На PHP — это чаще всего Active Record, на Java — Hibernate, на .NET — Entity Framework. Да, бывают другие стеки. Но мейнстрим — именно ORM + MVC = love
                          • 0

                            Как по мне, то для разработчика MV* ООП веб-бэкендов, не стоит начинать свой путь во фронте с React+Redux, достаточно одного React, или React+MobX, если практикуешь DDD. Redux — это полное разделение состояния и поведения, а не их инкапсуляция в объектах.

                            • +1
                              Action'ы redux'а — это же не доменные объекты, а скорее просто message. Что мешает ловить экшны, создающие эффекты в middleware, обрабатывать в любом удобном виде, в т.ч. с применением DDD и по завершению операции выбрасывать DomainEventSucceeded / Failed и уже их обрабатывать в редюсерах?
                              • 0

                                Доменные объекты инкапсулируют данные и поведение. Redux же их разделят по крайней мере на уровне рекомендуемых практик. Может и можно технически хранить в store полноценные объекты с методами, реализующими доменное поведение, а потом дергать их в редюсерах, но точно это будет шоком для подавляющего большинства активно использующих Redux. А скорее всего эта возможность либо просто заблокирована (может только в дев-режиме), либо ожидаемой реакции на вызов методов объекта не будет, поскольку redux+react не заметят, что изменилось состояние объекта в редьюсере, поскольку ожидает либо возврат того же объекта без изменений, либо нового объекта. В общем не прокатит что-то вроде (state, value) => state.domainObject.setValue(value).


                                Можно отдельно вынести мутабельную модель и проецировать её состояние на redux-стор в миддлварах, но что нам это даст, кроме "потому что можем"?

                                • +1
                                  А кто мешает применять DDD? Просто данные будут в сторе, а логика — отдельно — в функциях, сагах, и всяких таких штуках. Разделение ответственности же, до сих пор же идут споры о «правильном DDD» — никто не говорит, что для этого нужен один мутабельный объект модели для каждой сущности с методами поведения. Концептуально DDD никто не мешает использовать.

                                  P.S. На практике немного сложнее, в сторе обычно находятся не только данные «domain model», а куча разного рода другого состояния приложения.
                                  • 0

                                    По-моему сложно будет говорить о выделенном слое бизнес-логики, всё будет в одной куче и бизнес, и инфраструктура, и UI.

                                    • +1
                                      Ну, этот паттерн, который называют то «saga», то «process manager» — как раз об этом. Он описывает чистую логику, результатом которой являются те или иные «action»-ы. Тем самым хранилище состояния (Redux) только тупо хранит состояния и меняет его с помощью редьюсеров, а сама логика обрабатывается специализированными функциями.

                                      Тут не нужно придумывать отдельный паттерн для «бизнес-логики», чтобы как-то технически отделить ее от логики приложения. Просто не надо в кучу мешать (разные «саги», разные ключи в хранилище стейта, все такое).
                                  • 0
                                    state у редакса это готовое отображение мира для UI. О том, что состояние надо поменять как раз и говорят доменные объекты, которые умеют делать dispatch. Это в общем-то типичный CQRS+ES
                                    • +1

                                      DOM — это готовое отоброажение мира для UI. А state у вас — это промежуточная модель.

                                      • 0
                                        тогда замените UI в моем сообщении на DOM
                                        • 0

                                          А зачем? Может лучше state заменить на DOM?

                                          • 0
                                            у redux'a нет DOM, либо я вас не понял
                                            • 0

                                              Вот именно. Зачем дому редукс?

                                              • 0

                                                Он (или любая другая система хранения и управления состоянием приложения) нужен реакту, поскольку имеющаяся у него довольно примитивна.

                                                • 0

                                                  А зачем дому реакт? :-D

                                                  • 0

                                                    Дому реакт не нужен, реакт (или что-то подобное) нужны разработчику, чтобы управлять домом. Можно и другими средствами, но лично мне реакт кажется лучшим на сегодняшний день по балансу плюсов и минусов.

                                                    • 0

                                                      Это какие плюсы и минусы вы имеете ввиду?

                                                      • +2

                                                        Плюсы, навскидку:


                                                        • декларативное описание целевого состояния DOM без учёта текущего на языке очень близком к HTML с возможностью использовать все средства JS
                                                        • средства автоматического обновления DOM при изменении состояния
                                                        • компонентная модель с высоким уровне изоляции
                                                        • исключительно односторонний биндинг
                                                        • довольно заметные оптимизации ререндеринга DOM
                                                        • минимум три способа создавать компоненты (обычные функции, наследники Component, наследники PureComponent)
                                                          Минусы:
                                                        • нестандартный синтаксис, требующий компиляции в JS и изучения
                                                        • невозможность создавать компоненты, представимые в DOM типами, отличными от элементов (прежде всего напрягает отсутствие возможности представить компонент списком элементов)
                                                        • местами многословный синтаксис
                                                        • неопределенность в плане лучших практик
                                                        • доминирование отдельных не самых лучших в общем случае решений
                                                        • 0
                                                          декларативное описание… все средства JS

                                                          Что-то тут не так.


                                                          исключительно односторонний биндинг

                                                          Что плохого в двустороннем?


                                                          минимум три способа создавать компоненты

                                                          То есть чем больше способов создавать компоненты — тем лучше?

                                                          • 0
                                                            Что-то тут не так.

                                                            Всё так. Интерфейс сугубо декларативный, а логика вполне может быть императивной. Собственно все (ну или почти все, всякие ПЛИС не рассматриваем) так называемые декларативные средства современного ИТ сводятся к императивным командам тьюринг-подобных процессоров.


                                                            Что плохого в двустороннем?

                                                            Конфликты и циклы. Да даже без них — усложнение логики в виду отсутствия единого источника правды.


                                                            То есть чем больше способов создавать компоненты — тем лучше?

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

                                                            • 0
                                                              Всё так. Интерфейс сугубо декларативный, а логика вполне может быть императивной.

                                                              setState — какой-то совсем не декларативный интерфейс.


                                                              Конфликты и циклы. Да даже без них — усложнение логики в виду отсутствия единого источника правды.

                                                              Это всё особенности Ангуляра к двусторонним биндингам, в общем случае, отношения не имеющие.


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

                                                              Например? Что за условия могут потребовать создавать компоненты по разному?

                                                              • +1
                                                                setState — какой-то совсем не декларативный интерфейс.
                                                                Ну вот зачем придираетесь? Под «всеми средствами» JS, очевидно, имелся полный доступ к отрисовываемым объектам, фильры, сортировки, мерджи и т.п. при непосредственном рендеринге.

                                                                Это всё особенности Ангуляра к двусторонним биндингам, в общем случае, отношения не имеющие.
                                                                WPF и компания, видимо, в общем случае тоже к двусторонним бингингам отношения не имеют? Там это давно наболевшая проблема.

                                                                Например? Что за условия могут потребовать создавать компоненты по разному?
                                                                stateless vs stateful
                                                                • 0
                                                                  Под «всеми средствами» JS, очевидно, имелся полный доступ к отрисовываемым объектам, фильры, сортировки, мерджи и т.п. при непосредственном рендеринге.

                                                                  Например, взять и добавить атрибут в уже отрисованный дом?


                                                                  Там это давно наболевшая проблема.

                                                                  В $mol такой проблемы нет, а двусторонние биндинги есть. Что я делаю не так?


                                                                  stateless vs stateful

                                                                  И зачем их по разному создавать?

                                                                  • 0
                                                                    И зачем их по разному создавать?

                                                                    Уменьшение накладных расходов

                                                              • 0
                                                                setState — какой-то совсем не декларативный интерфейс.

                                                                Он не относится к описанию целевого состояния DOM. Целевой DOM является чистой функцией от props и state.


                                                                Это всё особенности Ангуляра к двусторонним биндингам, в общем случае, отношения не имеющие.

                                                                Это общая проблема двусторонних биндингов. В Excel/Calc попробуйте двумя ячейкам друг на друга сослаться. А лучше через десяток промежуточных.


                                                                Например? Что за условия могут потребовать создавать компоненты по разному?

                                                                • stateless компонент (чистая функция от свойств)
                                                                • stateful компонент с "нативным" setState
                                                                • stateful компонент со сторонним хранением и управлением состояния (redux, mobx, rxjs и т. п.)
                                                                • –1
                                                                  Он не относится к описанию целевого состояния DOM. Целевой DOM является чистой функцией от props и state.

                                                                  Зачем тут чистота, если достаточно идемпотентности?


                                                                  Это общая проблема двусторонних биндингов.

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


                                                                  В Excel/Calc попробуйте двумя ячейкам друг на друга сослаться. А лучше через десяток промежуточных.

                                                                  Эксель ругается на циклические зависимости.


                                                                  stateless компонент (чистая функция от свойств)
                                                                  stateful компонент с "нативным" setState
                                                                  stateful компонент со сторонним хранением и управлением состояния (redux, mobx, rxjs и т. п.)

                                                                  И зачем тут 3 разных способа создания компонента?

                                                                  • 0

                                                                    Чистота чище :)


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


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

                                                                    • +1

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


                                                                      Не позволяет. Формулы с циклическими зависимостями не работоают.


                                                                      Ну так не определяйте состояние, если оно вам не нужно. Зачем несколько разных способов объявлять компонент?

                                                                      • 0

                                                                        Тут я не о сторах, а о целевом состоянии DOM — оно чистая функция от состояния.


                                                                        Позволяет. Не работает, но позволяет, а ошибка в рантайме выводится.


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

                                                                        • +1

                                                                          Я тоже не о сторах, а о чистоте. Погоня за чистотой порождает чудовищные решения в духе "формируем новое состояние всего дома вместо обновления пары свойств у конкретных элементов".


                                                                          Позволяет ввести любую формулу. Но биндинга никакого по ней не создаёт.


                                                                          Что за "инфраструктура"? Объект со ссылкой на прототип? Ну офигеть инфраструктура.

                                                                          • 0

                                                                            Чем они чудовищные? Очень легко поддерживаются. И позволяют сделать реальное обновление парой свойств.


                                                                            Формулу сохраняет — значит биндинг создаётся. Просто он не работает. Хорошо, что ошибку выводит, а не в бесконечную рекурсию уходит.


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

                                                                            • +1

                                                                              Например, такие: https://habrahabr.ru/post/326484/#comment_10174502


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


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

                                      • +1

                                        Что за доменные объекты, которые знаю о dispatch в частности и о нуждах UI вообще?

                                        • 0
                                          про нужды UI знает reducer(в терминах cqrs+es это был бы проектор), доменный объект знает только о том, что что-то произошло
                                          • 0

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

                                            • 0
                                              Redux вообще не должен ничего «знать, чтобы диспетчеризовать». Его задача — только хранить состояние. У вас должен быть отдельный кусок программы, ответственный за перехват тех или иных action'ов и за то, чтобы на это как-то отреагировать (в том числе взять текущий стейт, проанализировать его и, вероятно, запустить какие то экшены).

                                              Ну нужно пытаться вешать на Redux то, что выходит за рамки его ответственности.
                                              • 0

                                                Его задача не только хранение состояния (обычный JS-объект с этим справится), но и управление им.

                                                • 0
                                                  Ну, скажем так, его задача наметить паттерн для этого (dispatch, actions, middlewares, reducers), но не обязательно предлагать непосредственный механизм для этого (вместо него — предлагается подключать что-то свое как middleware).

                                                  UPDATE: Не то, чтобы это хорошо или плохо — с одной стороны, это дает больше гибкости, но с другой — все делают кто во что горазд :) Особенно когда сложность приложения выходит за пределы разумности применения простых вещей вроде redux-thunk (который, вроде как стартовая точка и он же — первый middleware, с которым все сталкиваются).
                                              • 0
                                                очевидно объект сообщит redux'у :)
                                      • 0

                                        Мешает экпоненциальная костыльность "современной front end разработки" :-)

                                    • +2
                                      Почем нет ни слова про mobX? Мы же в 2017
                                      • 0
                                        Где relay?
                                        • +1

                                          Про reselect забыли!

                                          • +6

                                            Для себя как бэкенд-разработчика я не увидел ответа на один очень важный вопрос: зачем это всё? Почему вдруг стало нельзя просто рисовать HTML на бэкенде и отдавать его браузеру как есть (с AJAX или без)?


                                            (Не считая случаев, когда сайт таким образом написать в принципе невозможно и нужен интерактив на JS, но ведь такие сайты по пальцам можно пересчитать)

                                            • +5
                                              Как бывший фронтенд разработчик, который ныне сидит на бэке, отвечаю =)
                                              1. Рисовать HTML на бэкенде все еще можно, но такой подход не умеет решать ряд важных проблем. Например, если юзер взаимодействует с элементом, который меняет контент во многих разных частях приложения, скажем, отредактировал свое имя, которое должно успешно замениться повсюду на странице.
                                              2. Для отрисовки на бэке тратятся ресурсы networking'а, и это имело бы смысл, будь генерация HTML'а чем-то сложным и непроизводительным, но это совсем не так — любой современный шаблонизатор работает с ast, и на клиенте это все будет работать во многих случаях быстрее чем один только пинг до сервера.
                                              3. Отрисовка кусмана HTML'а на клиенте — это более трудозатратный процесс, чем использование DOM API, как раз потому, что снова выделяются ресурсы на парсинг и валидацию. Современные фреймворки/либы стараются использовать virtual dom, чтобы по возможности оптимизировать эти процессы.
                                              4. Еще, конечно, обилие мобильных клиентов требует уменьшения трафика. Готовый HTML практически всегда будет весить больше чем JSON API.

                                              Сайтов, которым нужны сложные взаимодействия на клиенте, точно не по пальцам пересчитать. Прикиньте, сколько в мире есть CRMок, облачных приложений, прибавьте сюда практически весь b2b-стек — почти каждая такая система выглядит и работает лучше, когда часть нагрузок передана на клиент.
                                              • 0
                                                должно успешно замениться повсюду на странице

                                                После изменения имени страница классического сайта просто перезагрузится :)


                                                на бэке тратятся ресурсы

                                                Ни разу не сталкивался с тем, чтобы это становилось узким местом. Да и рисование HTML на бэкенде многими крупнейшими сайтами тоже тому пример. А вот то, что производительности браузера не хватает джаваскрипту — это постоянно, особенно на мобилках. Ну и gzip никто не отменял


                                                Отрисовка кусмана HTML'а на клиенте — это более трудозатратный процесс

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


                                                Прикиньте, сколько в мире есть CRMок, облачных приложений

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

                                                • +5
                                                  После изменения имени страница классического сайта просто перезагрузится :)
                                                  Заставив нас ждать пинга сервер-клиента, рендера на сервере, рендера на клиенте, репейнта… А в нашем случае, может быть, надо было только два слова всего поменять. Конечно, раз на раз тут не приходится, но сценарий, когда действие пользователя меняет несколько достаточно удаленных друг от друга частей сайта, но незначительно — в моей практике встречался слишком часто.

                                                  на бэке тратятся ресурсы
                                                  Вы вырвали цитату из контекста. Привожу полную цитату:
                                                  Для отрисовки на бэке[ПАУЗА] тратятся ресурсы networking'а

                                                  Ресурсы бэка, конечно, не особо тратятся (зависит от). Но вот когда сервер в Лондоне, а юзер в Монголии…

                                                  видимой на глаз разницы в производительности [...] не замечал
                                                  Зависит от сайта, разумеется. Мне доводилось замечать такую разницу на всякого рода b2b-ресурсах.

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

                                                  Я лично считаю, что серебряной пули нет. Реакт не панацея (и мне лично вообще Vue больше нравится например), и толкать его повсюду — это та же мода на сложение чисел с помощью jquery только в профиль. Если ваш фронт, которому надо сделать лендинг для разовой акции, тянет туда реакт, возможно, он не очень компетентен. Если ваш бэкендер, который пилит условный trello, морщится при слове реакт и хочет все сделать на GWT/ASP.NET — про него можно сказать то же самое.
                                                  • +1
                                                    А в нашем случае, может быть, надо было только два слова всего поменять

                                                    Если ограничиваться только этим случаем, то получается что-то вроде document.querySelectorAll('.js-user-name').forEach(x => x.textContent = newName), и React тут как-то ни к чему)


                                                    в моей практике встречался слишком часто.

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


                                                    тратятся ресурсы networking'а

                                                    В веб-приложениях всю экономию с лихвой компенсируют громадный размер js-бандлов, а при частых обновлениях кода даже браузерный кэш становится бесполезен. Зажатый gzip'ом html-код неплохо передаётся даже по EDGE (а там пинг, напомню, приближается к секунде), так что не вижу смысла экономить на спичках

                                                    • +2
                                                      Ну, вы как-то даже и не хотите видеть каких-то преимуществ SPA-приложений (а люди с вами зачем-то спорят). Если вам хватает ваших инструментов — у вас же их никто не отбирает. У всех задачи опять же разные. Всякие чисто «хайповые» штуки со временем уходят или находят свою нишу, а вот полезные паттерны — остаются. Неужели вы думаете, что все и каждый вокруг вас — хипстер, которому нравятся модные словечки и использует такой подход только из за своей хипстеровости (есть такое слово?) :)

                                                      Возьмем тот же Реакт и его серверный рендеринг — и вот вам вполне себе альтернативный вариант написать чисто серверный UI (можете даже не подрубать React на клиенте). Кому-то удобнее, кому-то нет, но это еще один полезный паттерн.

                                                      document.querySelectorAll('.js-user-name').forEach(x => x.textContent = newName)
                                                      

                                                      Ну очень уж притянутый за уши пример. Вывести какой-то текст во все элементы с таким-то классом — элементарно. Во фронтенде реально сложно — хранить и управлять состоянием. Рендерить — чем угодно и как угодно — просто.
                                                      • 0

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

                                                        • 0
                                                          Ну, я именно это имел в виду под «хранением и управлением состояния». Надо было добавить «и корректному отображению всего этого на UI». И тут становится понятно, что и virtual DOM не просто так придумали и всякие другие штуки для этого — чтобы была возможна концепция «UI — это функция от состояния». Но автор комментариев выше настаивает на том, что все это не нужно, и я, наравне со многими другими, с этим несогласен :)
                                                      • 0
                                                        Если ограничиваться только этим случаем, то получается что-то вроде document.querySelectorAll('.js-user-name').forEach(x => x.textContent = newName), и React тут как-то ни к чему)

                                                        А где-то в сторонке у вас на некоторых страницах список пользователей отсортированный по имени. Ваши действия?

                                                        • 0
                                                          на некоторых страницах

                                                          А вот тут всплывает другая проблема: я как пользователь хочу, чтобы контент на страницах, которые я не трогаю, не менялся, и если что-то там пересортируется само по себе без явного нажатия мной F5, я сильно расстроюсь. А если на текущей странице нужно изменить что-то большее чем пару лейблов, то всё ещё нетрудно просто перезагрузить её (почти) целиком, «ресурсы networking'а» в 2017 году уже не проблема. Так что по моему личному мнению никаких действий предпринимать не нужно :)

                                                          • 0
                                                            Вы своим кодом как раз поменяли все имена пользователей в той таблице.
                                                            • 0

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

                                                              • 0

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

                                                                • 0
                                                                  Страница может быть какая угодно. Вопрос в том, какая именно, и какой уровень отзывчивости и интерактивности ваши пользователи ожидают. Оба подхода имеют право на жизнь, очень зависит от конкретного сайта/приложения и того, какие действия с его помощью производят ваши пользователи. Просто не надо все под одну гребенку и говорить, что перезагрузка страницы — единственный тру-способ работы с веб-приложениями.
                                                                  • 0

                                                                    Как я отметил ещё в скобках в своём самом первом комментарии, сайтов, на которых без интерактивщины с js вообще никак (тот же условный trello), я не касаюсь. А вот для большинства других ситуаций перезагрузка страницы таки является достаточно быстрым и достаточно интерактивным способом обновления контента. И я не понимаю, зачем жертвовать примитивностью и понятностью фронтенда ради экономии сотни миллисекунд и пары килобайт трафика. (И не забываем про PJAX.)

                                                                  • 0

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

                                                                    • 0

                                                                      Это дело всё ещё нетрудно перезагрузить. Всю страницу или только список подгрузить через AJAX — неважно, но я не вижу смысла как-то ещё усложнять фронтенд в данном случае. Даже если рассматривать экономию трафика и времени на парсинг html, она получается микроскопическая (даже если список большой — напомню, 2017 год на дворе) и, имхо, не стоит того. А анимации не нужны (если только так не скажет заказчик, с которым спорить себе невыгодно — но это уже совсем другая история)

                                                                      • 0

                                                                        Давайте усложнять бэкенд? :)

                                                                        • 0

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

                                                                          • 0
                                                                            Да вы просто о разных вещах говорите, мужики. andreymal говорит о документах, VolCh vintage и прочие — о приложении. Общего у них — только броузер и html. Ваш кэп.
                                                                            • +1

                                                                              Давно не видел в вебе документов.

                                                                              • –1
                                                                                А я вот утконосов давно не видел. Документация, статейники, форумы (да, интерактивные, но все же документы, так как предоставляют информацию, в отличие от приложений, который суть провайдеры сервиса)
                                                                                • +1

                                                                                  Приложения тоже по большей части предоставляют информацию. Всё отличие — в интерактивности.

                                                                        • 0

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


                                                                          Заказчик обычно говорит "хочу быстро, модно, молодёжно, чтобы всё летало туда-сюда без перезагрузки и не дорого".

                                                                    • 0

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

                                                          • 0
                                                            > Прикиньте, сколько в мире есть CRMок, облачных приложений, прибавьте сюда практически весь b2b-стек
                                                            Зачем здравомыслящему человеку перекладывать это всё с надёжных технологий на значительно менее надёжные?
                                                            • +1

                                                              Вы сейчас какую технологию ненадежной назвали?

                                                              • +4
                                                                этот их жабаскрипт вестимо
                                                                • 0

                                                                  И что у него не так с надёжностью?

                                                                  • +1
                                                                    Тут где-то меткую фразу видел: «у вас сарказм детектор барахлит» :)
                                                          • 0
                                                            Rest позволяет унифицировать апишку для любого типа клиентов.
                                                            • 0

                                                              Ну против API я ничего не имею

                                                            • 0
                                                              Тут достаточно просто, если вы пишите не простенький веб сайт а полноценное веб приложение с множеством динамики, то без клиентской шаблонизации не сделать многих вещей достаточно просто, ды и производительность.
                                                              • +1

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

                                                                • 0

                                                                  А новые фреймворки не для сайтов прежде всего, а для приложений.

                                                                  • +1

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

                                                                    • 0

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

                                                                      • 0

                                                                        Да ладно, любой сайт с богатым клиентом гораздо удобнее.

                                                              • 0
                                                                Мы научили фронтов верстать сразу в реакт-компоненты. Это избавляет от этапа интеграции с серверной шаблонизацией, например. Можно отдельно делать фронт, отдельно бек и не разворачивать у фронта бд и все что там еще нужно. Большие возможности интерактивного UI. Думаю, это основные плюсы.
                                                                • +3

                                                                  Современные веб-приложения (а не сайты) стремятся к тому, чтобы запросов к серверу было как можно меньше и при этом в ответах отдавалось только то, что ещё не отдавалось. Минимизация количество запросов и их объёма.


                                                                  Если знакомы с классическими клиент-серверными приложениями с десктопным клиентом, то у вас же не вызывает вопроса почему сервер не даёт клиенту сразу команды GDI (вроде так в винде это называется), рисующие данніе, а даёт только данные? Современные веб-приложения по сути являются полнофункциональным десктопным клиентом, не требующего установки и запускающегося с сетевого диска. Сначала ждём пока бинарник загрузится по сети, а потом общаемся с сервером только данными, делая всю работу UI на клиенте. А серверу всё равно, обычное десктоп приложение к нему стучится за данными, мобильное, веб, а может вообще другой сервер или CLI-утилита.

                                                                • +2
                                                                  Вот эти бородатые хипстеры, диалог и кнопка «Стать модным» — это ведь стёб, я правильно понимаю?
                                                                  • 0
                                                                    Как ни странно, TypeScript. При более детальном изучении оказалось, что не все так прекрасно. Во-первых TypeScript – это не полноценный язык со статической типизацией, а транспилер.

                                                                    Т.е. в случае с flow / babel / jsx вас это не смутило, а в случае с TypeScript — смутило?


                                                                    Это сильно ограничивает возможности использования шаблонов и мета-программирования.

                                                                    Не очень понял, какие? Можно хотя бы пару примеров?


                                                                    Во-вторых, далеко не все npm-пакеты идут в комплекте с d.ts-файлами.

                                                                    А аннотацию flow конечно же все npm-пакеты поддерживают?


                                                                    Короче, Flow показался проще в прикручивании.

                                                                    Для Flow надо прикрутить babel + babel-flow, а для TypeScript — только typescript… но это оказалось сложней?

                                                                    • +4
                                                                      Опять бедных бэкендеров мучают redux-ом. Бэкендеры, не слушайте их, берите mobx для управления состоянием, иначе со временем проклянёте тот день, когда решили разобраться с фронтом. И так проклянёте, конечно, но с mobx у вас хотя бы будет на руках работающее приложение и гораздо меньше седых волос.
                                                                      • –1
                                                                        А что именно не нравится в редаксе? И в чем принципиальное преимущество mobx? Я когда сравнивал просто не нашел киллер-фич каких-то.
                                                                        • 0

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

                                                                          • 0
                                                                            Вообще-то можно. Для этого как раз и используется combineReducers. Это вопрос компоновки приложения, а не redux'а.
                                                                            • 0

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

                                                                              • 0
                                                                                Эмм… зачем вам в редьюсере знать свой полный путь от корня стейта? Редьюсер должен оперировать только тем стейтом, который приходит в него первым аргументом.
                                                                                • 0

                                                                                  Чтобы понимать, что за стейт в него приходит.

                                                                                  • 0
                                                                                    Это не редьюсер решает, а как раз тот самый «код композиции».
                                                                                    • 0
                                                                                      Видимо, вы упускаете важную деталь. Редьюсер не должен знать об окружении, в котором он выполняется. Это чистая функция от стейта и экшена, приходящих в него через аргументы. Т.е. он не должен обращаться в какой-то глобальный стейт за данными.
                                                                                      Вот пример:
                                                                                      type TUser = {
                                                                                      	id: number,
                                                                                      	name: string
                                                                                      };
                                                                                      type TAuthState = {
                                                                                      	user?: TUser,
                                                                                      	loggedIn: boolean
                                                                                      };
                                                                                      const initial: TAuthState = {
                                                                                      	loggedIn: false
                                                                                      };
                                                                                      type TLoginAction = {
                                                                                      	type: 'AUTH_LOGIN',
                                                                                      	payload: {
                                                                                      		user: TUser
                                                                                      	}
                                                                                      };
                                                                                      type TLogoutAction = {
                                                                                      	type: 'AUTH_LOGOUT'
                                                                                      };
                                                                                      type TAction = TLoginAction | TLogoutAction;
                                                                                      
                                                                                      const auth = (state: TAuthState = initial, action: TAction): TAuthState => {
                                                                                      	switch (action.type) {
                                                                                      		case 'AUTH_LOGIN': {
                                                                                      			return {
                                                                                      				...state,
                                                                                      				user: action.payload.user,
                                                                                      				loggedIn: true
                                                                                      			};
                                                                                      		}
                                                                                      		case 'AUTH_LOGOUT': {
                                                                                      			return {
                                                                                      				...state,
                                                                                      				user: undefined,
                                                                                      				loggedIn: false
                                                                                      			};
                                                                                      		}
                                                                                      		default: {
                                                                                      			return state;
                                                                                      		}
                                                                                      	}
                                                                                      };
                                                                                      
                                                                                      //compose
                                                                                      type TAppState = {
                                                                                      	auth: TAuthState
                                                                                      };
                                                                                      const app: TAppState = combineReducers({
                                                                                      	auth
                                                                                      });
                                                                                      


                                                                                      Редьюсер auth не знает ровным счетом ничего о том, где он находится в глобальном стейте app, да и не должен.
                                                                                      • 0
                                                                                        Упс, с типами напутал:
                                                                                        const app = combineReducers<TAppState>({
                                                                                        	auth
                                                                                        });
                                                                                        

                                                                                        Но суть не меняется.
                                                                                        • 0

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

                                                                                          • +4
                                                                                            Значит вы не понимаете концепцию redux.

                                                                                            Вот пишете, недостаток. А какой в этом недостаток?
                                                                                            В редьюсер приходит не «какой-то стейт», а тот, с которым он умеет работать. И ему абсолютно по барабану, в корне этот стейт лежит, на 20 уровне вложенности, на полке или в холодильнике. Совершенно все-равно, все что он делает — это применяет нужный экшен к своему стейту.

                                                                                            Можете привести пример, когда вам нужно «знать, откуда пришел стейт»?
                                                                                            • 0

                                                                                              Он не знает его это стейт или не его. Вот в вашем примере придёт


                                                                                              {
                                                                                                  id: number,
                                                                                                  name: string
                                                                                              };

                                                                                              но это может оказаться не пользователь, а клиент.

                                                                                              • 0
                                                                                                Так а редьюсеру-то какая разница? Если вам нужна разная обработка в зависимости от типа — заводите либо разные экшены, либо разные редьюсеры, либо добавляйте в структуру ее тип.

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

                                                                                                Из этого органично следует вывод, что редьюсер не может принять «не свой стейт», он даже думать не должен, «его это стейт или нет». Он сам «определяет собой» этот стейт.
                                                                                                • 0

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

                                                                                                  • +1
                                                                                                    Ну так изменения должны вноситься на основании сущности, которая этим редьюсером обрабатывается. Все. Редьюсер — не зависит от окружения, какая разница, клиенты или пользователи лежат в стейте, если у них структура одинаковая?

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

                                                                                                    Когда разработчику нужно внести изменения в логику обработки тех же клиентов, он идет в редьюсер с клиентами и вносит изменения туда, основываясь только на том, что стейт в редьюсере может изменяться только этим редьюсером. Вы изолированы от окружения и реагируете строго на определенный список экшенов.
                                                                                                    • –1

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

                                                                                                      • 0
                                                                                                        взял и использовал один редьюсер в разных ветках стора
                                                                                                        Но зачем, если он обрабатывает одни и те же данные? Это обычная ошибка, и к redux не имеет никакого отношения.
                                                                                                        doSomthUser
                                                                                                        Видите, вы воспринимаете редьюсер, как какой-то метод, которые что-то делает со стейтом при его императивном вызове. Это не так, редьюсер — значение, которое вы можете прочитать, и все. Это значение может как-то меняться, в зависимости от описанной реакции на какие-то экшены. Ну руками снаружи вы его изменить не должны.
                                                                                                        Если уж на то пошло, то нужно было от хранения клиентов в стейте вообще избавиться, потому как ровно такая же обработка происходит в редьюсере с пользователями. Если бы эта ошибка не была допущена, то не случилось бы и следующей с нечаянным изменением обработки клиентов при изменении обработки пользователей. Это не имеет никакого отношения к redux.
                                                                                                        • 0

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


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

                                                                                                          • +1
                                                                                                            Почему у вас 10 редьюсеров на одну сущность вместо одного? Ну вот же ошибка. Представьте, что редьюсер — таблица в базе, вы же не заводите 10 таблиц под одну сущность?

                                                                                                            Я его воспринимаю редьюсер как функцию от старого стейта и данных экшена с побочным эффектом в виде создания нового объекта для замены им старого стейта.
                                                                                                            Это на основании анализа кода примеров на redux и его самого такое восприятие
                                                                                                            Вот и неверно. Все примеры redux, которые я видел, оперируют редьюсерами как объектами, сущностями, но никак не методами или функциями от старого стейта к новому. То, что это такая функция — детали реализации.
                                                                                          • 0
                                                                                            const app: TAppState = combineReducers({
                                                                                            	auth
                                                                                            });
                                                                                            

                                                                                            У меня есть значительные замечания, но на таких примерах они проявляются слабо. Можно пример реального приложения с десятками моделей, где многие из них используются несколько раз, иногда с отличиями — ну то есть реальное приложение, а не обучающий ХеллоуВорлд? К сожалению, в интернете я таких примеров не нашел. В ООП такое вполне решается наследованием и композицией.

                                                                                            Я просто не хочу писать свой псевдокод, чтобы потом не было аргументов, что я придумываю, а хочу показать на чем-то реальном.
                                                                                            • 0
                                                                                              import Module1 from './modules/Module1'
                                                                                              import Module2 from './modules/Module2'
                                                                                              
                                                                                              const combineModuleReducers = modules => {
                                                                                                const reducers = {}
                                                                                              
                                                                                                for (let i in modules) {
                                                                                                  const red = modules[i].reducer
                                                                                                  if (typeof(red) !== 'function') {
                                                                                                    throw new Error('Module ' + i + ' does not define reducer!')
                                                                                                  }
                                                                                              
                                                                                                  reducers[i] = red
                                                                                                }
                                                                                              
                                                                                                return reducers
                                                                                              }
                                                                                              
                                                                                              const modules = {
                                                                                                Module1,
                                                                                                Module2
                                                                                              }
                                                                                              
                                                                                              const store = createAppStore(combineReducers(combineModuleReducers(modules)))
                                                                                              
                                                                                              // код типового модуля
                                                                                              const mdl = {
                                                                                                 title: 'Мой модуль',
                                                                                                 reducer: (state = initialState) => {/* логика редюсера*/},
                                                                                                 path: '/module1'
                                                                                              }
                                                                                              
                                                                                              • 0
                                                                                                И что?
                                                                                                • 0
                                                                                                  Пример из реального приложения с сотнями моделей. Комбинируем редьюсеры так. Хочу услышать значительные замечания. Может быть что-то узнаю и исправлю.
                                                                                                  • 0
                                                                                                    Лучше покажите сотни редюсеров, а не код маленькой библиотеки)
                                                                                                    • 0
                                                                                                      export const fetchReducerFactory = (name, initialState, callback) => (state = initialState, action) => {
                                                                                                        const keys = Object.keys(initialState)
                                                                                                      
                                                                                                        for(var i = 0; i < keys.length; i++){
                                                                                                          const actionType = name + '/' + toUpperCamelCase(keys[i])+ '/Fetch'
                                                                                                      
                                                                                                          if(action.type == actionType) {
                                                                                                            const res = {...state}
                                                                                                            res[keys[i]] = Object.assign({}, res[keys[i]], {...action, isFetching: true})
                                                                                                            if(typeof(res['ui']) == 'object' && keys[i] == 'data') {
                                                                                                              res['ui'] = Object.assign(res['ui'], action.params)
                                                                                                            }
                                                                                                      
                                                                                                            return res
                                                                                                          }
                                                                                                      
                                                                                                          const actionTypeSucceeded =
                                                                                                            (name + '/' + toUpperCamelCase(keys[i])+ '/FetchSucceeded').replace('Data/', '')
                                                                                                      
                                                                                                          if(action.type == actionTypeSucceeded) {
                                                                                                            const res = {...state}
                                                                                                            res[keys[i]] = Object.assign({}, res[keys[i]], {...action, isInitialized: true, isFetching: false})
                                                                                                            return res
                                                                                                          }
                                                                                                      
                                                                                                        }
                                                                                                      
                                                                                                        return typeof(callback) == 'function' ? callback(state, action) : state;
                                                                                                      }
                                                                                                      


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

                                                                                                      Используется так:
                                                                                                      mdl.reducer = fetchReducerFactory(
                                                                                                        moduleId, {
                                                                                                          ...initialState,
                                                                                                          moduleId
                                                                                                       })
                                                                                                      
                                                                                              • 0
                                                                                                Ну в опенсорсе этого всего нет по понятным причинам. Но основная мысль, что доменный слой не лежит на уровне редьюсеров. Редьюсеры — это состояние, необходимое только для отрисовки UI и работы самого приложения (всякие там роутинги, состояние сессии и т.п.).
                                                                                                Модели же и процессы работы с ними (у нас на сагах все) лежат совершенно отдельно. А там хоть ООП, хоть ФП, хоть что угодно, главное уметь реагировать на экшены запросов (command) и выбрасывать их обратно на обработку редьюсерами (event). Чтение же из стора идет через селекторы (query). Получается этакий CQRS.

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

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

                                                                                                  Я понимаю, как работает Редакс, к сожалению я успел поработать с ним.

                                                                                                  Ну вы предупредили, так что аргументов таких не будет, смело приводите примеры.

                                                                                                  То есть я теперь могу написать любую ересь, а вы не можете привести аргументы, что я пишу ересь?)

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

                                                                                                  К сожалению, все примеры редакса — это что-то из разряда «я написал за пару десятков часов что-то в свое удовольствие и все, что не вписывалось в архитектуру — выбросил как ненужное».

                                                                                                  Я же хотел бы увидеть «мы командой уже полгода пишем, активно реагируя на запросы пользователей и получили такой результат».
                                                                                                  • 0
                                                                                                    Эмс. Ну на других технологиях довольно много чего лежит в опенсорсе почему-то. Так что причин реальных я не вижу.
                                                                                                    Под понятными причинами я имел в виду, что у нас все closed-source. Видимо, неудачно выразился.

                                                                                                    Ну… можно просто порассуждать о возможных проблемах. Если редьюсеры превращаются в кашу с кучей копипасты — с ними точно что-то не так.
                                                                                                    Мы используем редьюсеры исключительно как хранилище обычных данных (грубо говоря, кэш), состояния сессии, и некоторого состояния UI (но далеко не всего). Гипотетические сотни моделей, если они являются сущностями, могут уложиться в те же сотни редьюсеров с нормализованными данными. Каких-либо проблем я тут не вижу. Если они есть — укажите.

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

                                                                                                      Ах, я не понял, что вы именно про ВАШ код. Я же говорил впринципе про любой код.

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

                                                                                                      А то с редаксом всегда так. Из-за Стокгольмского синдрома те, кто его используют — защищают, а показать недостатки на практике — невозможно, потому-что «код закрыт, извините, но честно-честно, там все очень клево, он нас не очень сильно насилует».
                                                                                                      • 0
                                                                                                        Я же говорил впринципе про любой код.
                                                                                                        Вот видите, вы говорите, что мои рассуждения неправильные, потому что в реальном коде не так, как я и ожидал. Потому и хотел глянуть на реальный код и ткнуть пальцем, и мне не могли сказать: «но в реальном коде это не так».
                                                                                                        Ну тут, увы и ах, более менее серьезных примеров в открытом доступе действительно нет. С сагами так вообще все плохо, кроме их документации (кстати, достаточно неплохой) и статей на медиуме из серии «саги для чайников» вообще ничего нет. Приходится искать литературу по process managers и вот этому всему и как-то перекладывать на js.

                                                                                                        А то с редаксом всегда так. Из-за Стокгольмского синдрома те, кто его используют — защищают, а показать недостатки на практике — невозможно, потому-что «код закрыт, извините, но честно-честно, там все очень клево, он нас не очень сильно насилует».
                                                                                                        Так а вы покажите с какими недостатками столкнулись вы. А я попробую смоделировать их решение на нашем стэке.
                                                                                    • +2
                                                                                      Глядя на функцию нельзя однозначно сказать на работу с какой частью стора она заточена.
                                                                                      Ну так в этом то и смысл. Функция отвязана во всех смыслах от места компоновки. А типизация еще больше упрощает работу.
                                                                                      • 0

                                                                                        Ну вот это и не нравится. Мало того, что данные и функция, их мутирующая в разных местах, так она ещё и во всех смыслах полностью отвязана.


                                                                                        А про типизацию не понял: в сторе редакса же все объекты нетипизированные, по сути исключительно литералы, нельзя же сделать где-то в редьюсере return new User(username), а надо return {username}. Или можно?