Comments 182
>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
У меня все.
IEEE 754
У меня всё.
А статья — очередное «вступление в мир моды на быдлокод в JS», которых, как сам автор сказал, уже очень много и потому не нужна
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
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.1 + 0.2 == 0.3f
writeln(0.1*0.2 == 0.02f); //false
Тем не менее:
writeln( 0.1 * 0.2 ); //0.02
writeln( 0.1 * 0.2 == 0.02.to!double ); //true
writefln("%1.18e", 0.1*0.2); //2.000000000000000042e-02
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
Поэтому в любом случае стоит делать проверку на диапазон или любой другой хак.
react-router-redux?
(Более чем) реальная альтернатива react-router-у — junctions.js.
Немного критики. К сожалению, ряд моментов режет глаз.
Сравним с бекендом: MVC-фреймворк, ORM, Data Mapper, IOC-контейнер...Почему MVC? Почему фреймворк? Почему ORM? И т.д. Это вовсе не необходимые компоненты бэкенда. Я бы назвал их «модными», но бэкенд может быть построен на совершенно других принципах. Кмк, такое сравнение тут не совсем к месту.
Самым монструозным из всех был конечно Smarty. Мне казалось, что люди сошли с ума. Как иначе можно было объяснить желание написать шаблонизатор… для шаблонизатора Perl?Конечно же автор имеет в виду php, а не perl. Фраза о «шаблонизаторе для шаблонизатора» тоже модная, но слишком часто используется не в тему.
первая версия PHP была написана на PerlЭто шутка что ли или я туплю? Насколько мне помнится, php/fi сразу был написан на C.
Исправление… Понял о чем вы, о первом наборе скриптов на perl.
Почему MVC? Почему фреймворк? Почему ORM? И т.д. Это вовсе не необходимые компоненты бэкенда. Я бы назвал их «модными», но бэкенд может быть построен на совершенно других принципах. Кмк, такое сравнение тут не совсем к месту.
Потому что примерно 90% кода из мира веб-разработки, который я видел или поддерживал — это MVC-фрейворк + ORM и не более. На PHP — это чаще всего Active Record, на Java — Hibernate, на .NET — Entity Framework. Да, бывают другие стеки. Но мейнстрим — именно ORM + MVC = love
Как по мне, то для разработчика MV* ООП веб-бэкендов, не стоит начинать свой путь во фронте с React+Redux, достаточно одного React, или React+MobX, если практикуешь DDD. Redux — это полное разделение состояния и поведения, а не их инкапсуляция в объектах.
DomainEventSucceeded / Failed
и уже их обрабатывать в редюсерах?Доменные объекты инкапсулируют данные и поведение. Redux же их разделят по крайней мере на уровне рекомендуемых практик. Может и можно технически хранить в store полноценные объекты с методами, реализующими доменное поведение, а потом дергать их в редюсерах, но точно это будет шоком для подавляющего большинства активно использующих Redux. А скорее всего эта возможность либо просто заблокирована (может только в дев-режиме), либо ожидаемой реакции на вызов методов объекта не будет, поскольку redux+react не заметят, что изменилось состояние объекта в редьюсере, поскольку ожидает либо возврат того же объекта без изменений, либо нового объекта. В общем не прокатит что-то вроде (state, value) => state.domainObject.setValue(value).
Можно отдельно вынести мутабельную модель и проецировать её состояние на redux-стор в миддлварах, но что нам это даст, кроме "потому что можем"?
P.S. На практике немного сложнее, в сторе обычно находятся не только данные «domain model», а куча разного рода другого состояния приложения.
По-моему сложно будет говорить о выделенном слое бизнес-логики, всё будет в одной куче и бизнес, и инфраструктура, и UI.
Тут не нужно придумывать отдельный паттерн для «бизнес-логики», чтобы как-то технически отделить ее от логики приложения. Просто не надо в кучу мешать (разные «саги», разные ключи в хранилище стейта, все такое).
DOM — это готовое отоброажение мира для UI. А state у вас — это промежуточная модель.
А зачем? Может лучше state заменить на DOM?
Вот именно. Зачем дому редукс?
Он (или любая другая система хранения и управления состоянием приложения) нужен реакту, поскольку имеющаяся у него довольно примитивна.
А зачем дому реакт? :-D
Дому реакт не нужен, реакт (или что-то подобное) нужны разработчику, чтобы управлять домом. Можно и другими средствами, но лично мне реакт кажется лучшим на сегодняшний день по балансу плюсов и минусов.
Это какие плюсы и минусы вы имеете ввиду?
Плюсы, навскидку:
- декларативное описание целевого состояния DOM без учёта текущего на языке очень близком к HTML с возможностью использовать все средства JS
- средства автоматического обновления DOM при изменении состояния
- компонентная модель с высоким уровне изоляции
- исключительно односторонний биндинг
- довольно заметные оптимизации ререндеринга DOM
- минимум три способа создавать компоненты (обычные функции, наследники Component, наследники PureComponent)
Минусы: - нестандартный синтаксис, требующий компиляции в JS и изучения
- невозможность создавать компоненты, представимые в DOM типами, отличными от элементов (прежде всего напрягает отсутствие возможности представить компонент списком элементов)
- местами многословный синтаксис
- неопределенность в плане лучших практик
- доминирование отдельных не самых лучших в общем случае решений
декларативное описание… все средства JS
Что-то тут не так.
исключительно односторонний биндинг
Что плохого в двустороннем?
минимум три способа создавать компоненты
То есть чем больше способов создавать компоненты — тем лучше?
Что-то тут не так.
Всё так. Интерфейс сугубо декларативный, а логика вполне может быть императивной. Собственно все (ну или почти все, всякие ПЛИС не рассматриваем) так называемые декларативные средства современного ИТ сводятся к императивным командам тьюринг-подобных процессоров.
Что плохого в двустороннем?
Конфликты и циклы. Да даже без них — усложнение логики в виду отсутствия единого источника правды.
То есть чем больше способов создавать компоненты — тем лучше?
В разумных пределах. Для разных ситуаций редко подходит один универсальный способ, а даже если подходит, то вносит лишний оверхид.
Всё так. Интерфейс сугубо декларативный, а логика вполне может быть императивной.
setState — какой-то совсем не декларативный интерфейс.
Конфликты и циклы. Да даже без них — усложнение логики в виду отсутствия единого источника правды.
Это всё особенности Ангуляра к двусторонним биндингам, в общем случае, отношения не имеющие.
Для разных ситуаций редко подходит один универсальный способ, а даже если подходит, то вносит лишний оверхид.
Например? Что за условия могут потребовать создавать компоненты по разному?
setState — какой-то совсем не декларативный интерфейс.Ну вот зачем придираетесь? Под «всеми средствами» JS, очевидно, имелся полный доступ к отрисовываемым объектам, фильры, сортировки, мерджи и т.п. при непосредственном рендеринге.
Это всё особенности Ангуляра к двусторонним биндингам, в общем случае, отношения не имеющие.WPF и компания, видимо, в общем случае тоже к двусторонним бингингам отношения не имеют? Там это давно наболевшая проблема.
Например? Что за условия могут потребовать создавать компоненты по разному?stateless vs stateful
Под «всеми средствами» JS, очевидно, имелся полный доступ к отрисовываемым объектам, фильры, сортировки, мерджи и т.п. при непосредственном рендеринге.
Например, взять и добавить атрибут в уже отрисованный дом?
Там это давно наболевшая проблема.
В $mol такой проблемы нет, а двусторонние биндинги есть. Что я делаю не так?
stateless vs stateful
И зачем их по разному создавать?
setState — какой-то совсем не декларативный интерфейс.
Он не относится к описанию целевого состояния DOM. Целевой DOM является чистой функцией от props и state.
Это всё особенности Ангуляра к двусторонним биндингам, в общем случае, отношения не имеющие.
Это общая проблема двусторонних биндингов. В Excel/Calc попробуйте двумя ячейкам друг на друга сослаться. А лучше через десяток промежуточных.
Например? Что за условия могут потребовать создавать компоненты по разному?
- stateless компонент (чистая функция от свойств)
- stateful компонент с "нативным" setState
- stateful компонент со сторонним хранением и управлением состояния (redux, mobx, rxjs и т. п.)
Он не относится к описанию целевого состояния DOM. Целевой DOM является чистой функцией от props и state.
Зачем тут чистота, если достаточно идемпотентности?
Это общая проблема двусторонних биндингов.
Нет, это проблема кривой их реализации. В нормальной реализации таких проблем нет. Типичная ошибка при их реализации — делать двусторонний биндинг через пару противоположных реактивных зависимости. Не надо так.
В Excel/Calc попробуйте двумя ячейкам друг на друга сослаться. А лучше через десяток промежуточных.
Эксель ругается на циклические зависимости.
stateless компонент (чистая функция от свойств)
stateful компонент с "нативным" setState
stateful компонент со сторонним хранением и управлением состояния (redux, mobx, rxjs и т. п.)
И зачем тут 3 разных способа создания компонента?
Чистота чище :)
Именно, ругается, но позволяет их создавать.
Чтобы уменьшить накладные расходы. Не нужно состояние — не нужно его и вводить в компонент. Нужная частичная поддержка состояния — минимальное количество поддерживающего кода, полное — максимальное.
Чистота приводит лишь к ребусам в коде вида "чтобы измеить значение переменной в сторе нужно вызвать левую функцию и передать ей функцию, которая принимает стор, а возвращает копию стора с другим значением этой переменной".
Не позволяет. Формулы с циклическими зависимостями не работоают.
Ну так не определяйте состояние, если оно вам не нужно. Зачем несколько разных способов объявлять компонент?
Тут я не о сторах, а о целевом состоянии DOM — оно чистая функция от состояния.
Позволяет. Не работает, но позволяет, а ошибка в рантайме выводится.
Не определять состояние в компоненте не значит что для него не будет создана инфраструктура его хранения и управления. Она будет создана просто будет простаивать. А хочется гибкости, чтобы ресурсы тратились сообразно реальному использованию, а не всем гипотетическим сценариям.
Я тоже не о сторах, а о чистоте. Погоня за чистотой порождает чудовищные решения в духе "формируем новое состояние всего дома вместо обновления пары свойств у конкретных элементов".
Позволяет ввести любую формулу. Но биндинга никакого по ней не создаёт.
Что за "инфраструктура"? Объект со ссылкой на прототип? Ну офигеть инфраструктура.
Чем они чудовищные? Очень легко поддерживаются. И позволяют сделать реальное обновление парой свойств.
Формулу сохраняет — значит биндинг создаётся. Просто он не работает. Хорошо, что ошибку выводит, а не в бесконечную рекурсию уходит.
Объект с ненужными свойствами. Ну и да, разрешение методов происходит, что небесплатно.
Например, такие: https://habrahabr.ru/post/326484/#comment_10174502
Формула — это просто текст. Она сохраняется, если соответствует формальной грамматике выражений. Следующий шаг — вычисление и трекинг зависимостей — уже не проходит и падает с ошибкой.
Любое замыкание — это объект, хранящий ссылку на функцию и контекст её создания, хранящий "ненужные переменные". Проще говоря — разницы нет от слова "вообще".
Что за доменные объекты, которые знаю о dispatch в частности и о нуждах UI вообще?
Объект знает, что с ним произошло и даже может сообщить кому-то если спросят. Как об этом должен узнать redux, чтобы вычитать изменения в графе домена, создать соответствующие action и диспетчеризировать их?
Ну нужно пытаться вешать на Redux то, что выходит за рамки его ответственности.
Его задача не только хранение состояния (обычный JS-объект с этим справится), но и управление им.
UPDATE: Не то, чтобы это хорошо или плохо — с одной стороны, это дает больше гибкости, но с другой — все делают кто во что горазд :) Особенно когда сложность приложения выходит за пределы разумности применения простых вещей вроде redux-thunk (который, вроде как стартовая точка и он же — первый middleware, с которым все сталкиваются).
Мешает экпоненциальная костыльность "современной front end разработки" :-)
Про reselect забыли!
Для себя как бэкенд-разработчика я не увидел ответа на один очень важный вопрос: зачем это всё? Почему вдруг стало нельзя просто рисовать HTML на бэкенде и отдавать его браузеру как есть (с AJAX или без)?
(Не считая случаев, когда сайт таким образом написать в принципе невозможно и нужен интерактив на JS, но ведь такие сайты по пальцам можно пересчитать)
1. Рисовать HTML на бэкенде все еще можно, но такой подход не умеет решать ряд важных проблем. Например, если юзер взаимодействует с элементом, который меняет контент во многих разных частях приложения, скажем, отредактировал свое имя, которое должно успешно замениться повсюду на странице.
2. Для отрисовки на бэке тратятся ресурсы networking'а, и это имело бы смысл, будь генерация HTML'а чем-то сложным и непроизводительным, но это совсем не так — любой современный шаблонизатор работает с ast, и на клиенте это все будет работать во многих случаях быстрее чем один только пинг до сервера.
3. Отрисовка кусмана HTML'а на клиенте — это более трудозатратный процесс, чем использование DOM API, как раз потому, что снова выделяются ресурсы на парсинг и валидацию. Современные фреймворки/либы стараются использовать virtual dom, чтобы по возможности оптимизировать эти процессы.
4. Еще, конечно, обилие мобильных клиентов требует уменьшения трафика. Готовый HTML практически всегда будет весить больше чем JSON API.
Сайтов, которым нужны сложные взаимодействия на клиенте, точно не по пальцам пересчитать. Прикиньте, сколько в мире есть CRMок, облачных приложений, прибавьте сюда практически весь b2b-стек — почти каждая такая система выглядит и работает лучше, когда часть нагрузок передана на клиент.
должно успешно замениться повсюду на странице
После изменения имени страница классического сайта просто перезагрузится :)
на бэке тратятся ресурсы
Ни разу не сталкивался с тем, чтобы это становилось узким местом. Да и рисование HTML на бэкенде многими крупнейшими сайтами тоже тому пример. А вот то, что производительности браузера не хватает джаваскрипту — это постоянно, особенно на мобилках. Ну и gzip никто не отменял
Отрисовка кусмана HTML'а на клиенте — это более трудозатратный процесс
Видимо, разница в пределах погрешности, потому что видимой на глаз разницы в производительности между сайтами, активно юзающими innerHTML (PJAX/Turbolinks какие-нибудь), и сайтами на React не замечал (и на мобилках тормозят примерно одинаково).
Прикиньте, сколько в мире есть CRMок, облачных приложений
Прикиньте, сколько в мире есть сайтов-визиток, блогов, форумов, которые кроме гифок статичны чуть более чем полностью — но при этом в них в последнее время зачем-то пихают реакты и прочие ангуляры с js-бандлами в десяток-другой мегабайт :)
После изменения имени страница классического сайта просто перезагрузится :)Заставив нас ждать пинга сервер-клиента, рендера на сервере, рендера на клиенте, репейнта… А в нашем случае, может быть, надо было только два слова всего поменять. Конечно, раз на раз тут не приходится, но сценарий, когда действие пользователя меняет несколько достаточно удаленных друг от друга частей сайта, но незначительно — в моей практике встречался слишком часто.
на бэке тратятся ресурсыВы вырвали цитату из контекста. Привожу полную цитату:
Для отрисовки на бэке[ПАУЗА] тратятся ресурсы networking'а
Ресурсы бэка, конечно, не особо тратятся (зависит от). Но вот когда сервер в Лондоне, а юзер в Монголии…
видимой на глаз разницы в производительности [...] не замечалЗависит от сайта, разумеется. Мне доводилось замечать такую разницу на всякого рода b2b-ресурсах.
(Не считая случаев, когда сайт таким образом написать в принципе невозможно и нужен интерактив на JS, но ведь такие сайты по пальцам можно пересчитать)
Прикиньте, сколько в мире есть сайтов-визиток, блогов, форумов, которые кроме гифок статичны чуть более чем полностьюТо, что в мире много сайтов-визиток с морем статики, никак не подтверждает аргумент, что других сайтов — по пальцам пересчитать.
Я лично считаю, что серебряной пули нет. Реакт не панацея (и мне лично вообще Vue больше нравится например), и толкать его повсюду — это та же мода на сложение чисел с помощью jquery только в профиль. Если ваш фронт, которому надо сделать лендинг для разовой акции, тянет туда реакт, возможно, он не очень компетентен. Если ваш бэкендер, который пилит условный trello, морщится при слове реакт и хочет все сделать на GWT/ASP.NET — про него можно сказать то же самое.
А в нашем случае, может быть, надо было только два слова всего поменять
Если ограничиваться только этим случаем, то получается что-то вроде document.querySelectorAll('.js-user-name').forEach(x => x.textContent = newName)
, и React тут как-то ни к чему)
в моей практике встречался слишком часто.
Видимо, мне чертовски повезло с тем, что в моей практике это встречалось всего один раз, и это был не сайт, а сложное веб-приложение. Впрочем, и там я без реакта успешно обошёлся, но это уже другая история)
тратятся ресурсы networking'а
В веб-приложениях всю экономию с лихвой компенсируют громадный размер js-бандлов, а при частых обновлениях кода даже браузерный кэш становится бесполезен. Зажатый gzip'ом html-код неплохо передаётся даже по EDGE (а там пинг, напомню, приближается к секунде), так что не вижу смысла экономить на спичках
Возьмем тот же Реакт и его серверный рендеринг — и вот вам вполне себе альтернативный вариант написать чисто серверный UI (можете даже не подрубать React на клиенте). Кому-то удобнее, кому-то нет, но это еще один полезный паттерн.
document.querySelectorAll('.js-user-name').forEach(x => x.textContent = newName)
Ну очень уж притянутый за уши пример. Вывести какой-то текст во все элементы с таким-то классом — элементарно. Во фронтенде реально сложно — хранить и управлять состоянием. Рендерить — чем угодно и как угодно — просто.
Хранить и управлять состоянием, имхо, не так уж сложно. Сложно перерендерить именно то, что нужно, когда состояние изменилось или, проще говоря, связать состояние с DOM, крайне желательно без полной очистки контейнера для рендеринга, а точечными операциями по его изменению.
Если ограничиваться только этим случаем, то получается что-то вроде document.querySelectorAll('.js-user-name').forEach(x => x.textContent = newName), и React тут как-то ни к чему)
А где-то в сторонке у вас на некоторых страницах список пользователей отсортированный по имени. Ваши действия?
на некоторых страницах
А вот тут всплывает другая проблема: я как пользователь хочу, чтобы контент на страницах, которые я не трогаю, не менялся, и если что-то там пересортируется само по себе без явного нажатия мной F5, я сильно расстроюсь. А если на текущей странице нужно изменить что-то большее чем пару лейблов, то всё ещё нетрудно просто перезагрузить её (почти) целиком, «ресурсы networking'а» в 2017 году уже не проблема. Так что по моему личному мнению никаких действий предпринимать не нужно :)
Речь про одну и ту же страницу, на которую сейчас смотрит пользователь. Список пользователей на ней может быть, а может и не быть и если вы поменяли имя одного пользователя, то этот элемент списка должен перелететь в другое место, желательно с анимацией.
Во-первых, её нетрудно перезагрузить. Во-вторых, что это вообще за страница такая странная, на которой есть одновременно и список пользователей, и функция изменения имени?
Как я отметил ещё в скобках в своём самом первом комментарии, сайтов, на которых без интерактивщины с js вообще никак (тот же условный trello), я не касаюсь. А вот для большинства других ситуаций перезагрузка страницы таки является достаточно быстрым и достаточно интерактивным способом обновления контента. И я не понимаю, зачем жертвовать примитивностью и понятностью фронтенда ради экономии сотни миллисекунд и пары килобайт трафика. (И не забываем про PJAX.)
В шапке на всех страницах сайта отображается имя пользователя. Тут же можно его поменять (почему бы и нет?). В теле страницы "обсуждаем завтрашнюю стрелку" выводится список список "ребят с вашего двора", отсортированный по заданному пользователем критерию (имя, возраст, репутация, сила). В случае, если пользователь изменил имя, и на экран сейчас выводится список пользователей (а он может быть скрыт) и этот список отсортирован по имени пользователей, то в этом и только в этом случае нужно найти строку с текущим пользователем и переместить её в правильное место.
Это дело всё ещё нетрудно перезагрузить. Всю страницу или только список подгрузить через AJAX — неважно, но я не вижу смысла как-то ещё усложнять фронтенд в данном случае. Даже если рассматривать экономию трафика и времени на парсинг html, она получается микроскопическая (даже если список большой — напомню, 2017 год на дворе) и, имхо, не стоит того. А анимации не нужны (если только так не скажет заказчик, с которым спорить себе невыгодно — но это уже совсем другая история)
Давайте усложнять бэкенд? :)
Здесь я отвечаю «давайте, потому что хороший сайт должен уметь работать без js» и меня закидывают помидорами и яйцами.)
Давно не видел в вебе документов.
Перезагрузить-то может и не сложно, да только вот вот вы не уследите за всеми такими моментами, которые нужно предусмотреть. И будут вас долго и упорно преследовать багрепорты.
Заказчик обычно говорит "хочу быстро, модно, молодёжно, чтобы всё летало туда-сюда без перезагрузки и не дорого".
А перелёт с анимацией очень спорная фича, сильно не люблю когда что-то куда-то скачет (и ещё больше не люблю, когда перед выполнением какого-то действия нужно дождаться, пока это что-то перестанет скакать). А если в обсуждаемом списке пользователей будет ещё и пагинация, то вообще караул тотальное неюзерфрендли :\
Зачем здравомыслящему человеку перекладывать это всё с надёжных технологий на значительно менее надёжные?
Как я намекнул в скобках, у большинства сайтов динамики просто нет (у хабра, кстати, тоже, за исключением подгрузки новых комментариев)
Современные веб-приложения (а не сайты) стремятся к тому, чтобы запросов к серверу было как можно меньше и при этом в ответах отдавалось только то, что ещё не отдавалось. Минимизация количество запросов и их объёма.
Если знакомы с классическими клиент-серверными приложениями с десктопным клиентом, то у вас же не вызывает вопроса почему сервер не даёт клиенту сразу команды GDI (вроде так в винде это называется), рисующие данніе, а даёт только данные? Современные веб-приложения по сути являются полнофункциональным десктопным клиентом, не требующего установки и запускающегося с сетевого диска. Сначала ждём пока бинарник загрузится по сети, а потом общаемся с сервером только данными, делая всю работу UI на клиенте. А серверу всё равно, обычное десктоп приложение к нему стучится за данными, мобильное, веб, а может вообще другой сервер или CLI-утилита.
Как ни странно, TypeScript. При более детальном изучении оказалось, что не все так прекрасно. Во-первых TypeScript – это не полноценный язык со статической типизацией, а транспилер.
Т.е. в случае с flow / babel / jsx вас это не смутило, а в случае с TypeScript — смутило?
Это сильно ограничивает возможности использования шаблонов и мета-программирования.
Не очень понял, какие? Можно хотя бы пару примеров?
Во-вторых, далеко не все npm-пакеты идут в комплекте с d.ts-файлами.
А аннотацию flow конечно же все npm-пакеты поддерживают?
Короче, Flow показался проще в прикручивании.
Для Flow надо прикрутить babel + babel-flow, а для TypeScript — только typescript… но это оказалось сложней?
Как минимум, MobX нормально оперирует привычными многим бэкендерам полноценными объектами, инкапсулирующими данные и поведение. В Redux же, по сути, есть только структуры данных типа сишных структур или паскалевских записей и функции, связанные со структурами только в голове у разработчика. Глядя на функцию нельзя однозначно сказать на работу с какой частью стора она заточена.
combineReducers
. Это вопрос компоновки приложения, а не redux'а.Вообще-то именно она и сбивает со следа, "анонимизируя" редьюсеру его часть стора так, что его коду нельзя понять полный путь от корня стора.
Чтобы понимать, что за стейт в него приходит.
Вот пример:
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, да и не должен.
const app = combineReducers<TAppState>({
auth
});
Но суть не меняется.
Я не упускаю, я считаю недостатком, что в редьюсер приходит какой-то стейт, а он не знает откуда он пришёл, что редьюсер и обрабатываемый им стейт связаны через корень приложения.
Вот пишете, недостаток. А какой в этом недостаток?
В редьюсер приходит не «какой-то стейт», а тот, с которым он умеет работать. И ему абсолютно по барабану, в корне этот стейт лежит, на 20 уровне вложенности, на полке или в холодильнике. Совершенно все-равно, все что он делает — это применяет нужный экшен к своему стейту.
Можете привести пример, когда вам нужно «знать, откуда пришел стейт»?
Он не знает его это стейт или не его. Вот в вашем примере придёт
{
id: number,
name: string
};
но это может оказаться не пользователь, а клиент.
Тут есть небольшой трюк — представьте, что редьюсер — это не функция, которую где-то кто-то как-то вызывает, и может вызвать неправильно. Представьте, что это значение, которое меняется со временем при обработке приходящих экшенов. Ну, вы прямом смысле значение, объект. И из этих объектов вы собираете конечный объект стейта. Вот она функциональная композиция, вам не нужно вызывать редьюсер императивно (в большинстве случаев), достаточно обычной композиции в объект через combineReducers.
Из этого органично следует вывод, что редьюсер не может принять «не свой стейт», он даже думать не должен, «его это стейт или нет». Он сам «определяет собой» этот стейт.
Не редьюсеру есть разница, а разработчику, которому нужно внести изменение в логику его работы.
Если вам нужно один и тот же редьюсер использовать для хранения и пользователей, и клиентов (что, хм, странно), вы при композиции кладете его под разными ключами. Если вам нужна разная обработка, вы делаете 2 разных редьюсера (что уже ближе к правде), которые реагируют на разные и/или пересекающиеся экшены по-разному и работают с разными структурами.
Когда разработчику нужно внести изменения в логику обработки тех же клиентов, он идет в редьюсер с клиентами и вносит изменения туда, основываясь только на том, что стейт в редьюсере может изменяться только этим редьюсером. Вы изолированы от окружения и реагируете строго на определенный список экшенов.
Ну вот кто-то, исходя из благих побуждений типа переиспользования кода, взял и использовал один редьюсер в разных ветках стора, когда пришла задача сделать в какой-то части поведение пользователя и клиента одинаковым, но имя оставил типа doSomthUser и в папочку положил User. А потом кому-то другому приходит задача изменить поведение для пользователей и он меняет поведение пользователя, не подозревая что меняет поведение клиентов. Когда стейт и данные, его изменяющие всегда лежат вместе, то таких проблем просто не возникает.
взял и использовал один редьюсер в разных ветках стораНо зачем, если он обрабатывает одни и те же данные? Это обычная ошибка, и к redux не имеет никакого отношения.
doSomthUserВидите, вы воспринимаете редьюсер, как какой-то метод, которые что-то делает со стейтом при его императивном вызове. Это не так, редьюсер — значение, которое вы можете прочитать, и все. Это значение может как-то меняться, в зависимости от описанной реакции на какие-то экшены. Ну руками снаружи вы его изменить не должны.
Если уж на то пошло, то нужно было от хранения клиентов в стейте вообще избавиться, потому как ровно такая же обработка происходит в редьюсере с пользователями. Если бы эта ошибка не была допущена, то не случилось бы и следующей с нечаянным изменением обработки клиентов при изменении обработки пользователей. Это не имеет никакого отношения к redux.
И у клиентов, и у пользователей есть, например, ФИО, но остальные данные различаются. И редьюсеров у каждого штук по 10 минимум, но разные, кроме вот этого проблемного.
Я его воспринимаю редьюсер как функцию от старого стейта и данных экшена с побочным эффектом в виде создания нового объекта для замены им старого стейта. Что замена происходит не непосредственно в нём — дань упрощения детектирования изменений — объекты гораздо проще сравнивать по ссылке, а не по значению.Это на основании анализа кода примеров на redux и его самого такое восприятие.
Я его воспринимаю редьюсер как функцию от старого стейта и данных экшена с побочным эффектом в виде создания нового объекта для замены им старого стейта.
Это на основании анализа кода примеров на redux и его самого такое восприятиеВот и неверно. Все примеры redux, которые я видел, оперируют редьюсерами как объектами, сущностями, но никак не методами или функциями от старого стейта к новому. То, что это такая функция — детали реализации.
const app: TAppState = combineReducers({ auth });
У меня есть значительные замечания, но на таких примерах они проявляются слабо. Можно пример реального приложения с десятками моделей, где многие из них используются несколько раз, иногда с отличиями — ну то есть реальное приложение, а не обучающий ХеллоуВорлд? К сожалению, в интернете я таких примеров не нашел. В ООП такое вполне решается наследованием и композицией.
Я просто не хочу писать свой псевдокод, чтобы потом не было аргументов, что я придумываю, а хочу показать на чем-то реальном.
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'
}
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
})
Модели же и процессы работы с ними (у нас на сагах все) лежат совершенно отдельно. А там хоть ООП, хоть ФП, хоть что угодно, главное уметь реагировать на экшены запросов (command) и выбрасывать их обратно на обработку редьюсерами (event). Чтение же из стора идет через селекторы (query). Получается этакий CQRS.
Я просто не хочу писать свой псевдокод, чтобы потом не было аргументов, что я придумываю, а хочу показать на чем-то реальном.Ну вы предупредили, так что аргументов таких не будет, смело приводите примеры.
Ну в опенсорсе этого всего нет по понятным причинам
Эмс. Ну на других технологиях довольно много чего лежит в опенсорсе почему-то. Так что причин реальных я не вижу.
Я понимаю, как работает Редакс, к сожалению я успел поработать с ним.
Ну вы предупредили, так что аргументов таких не будет, смело приводите примеры.
То есть я теперь могу написать любую ересь, а вы не можете привести аргументы, что я пишу ересь?)
Я хочу показать, что в реальном коде Редакс приводит к крайне запутаному коду и куче копи-пасты. К сожалению, если я покажу пример — вы скажете, что это не так. Потому я и хочу увидеть реальный код, на котором я мог бы это доказать.
К сожалению, все примеры редакса — это что-то из разряда «я написал за пару десятков часов что-то в свое удовольствие и все, что не вписывалось в архитектуру — выбросил как ненужное».
Я же хотел бы увидеть «мы командой уже полгода пишем, активно реагируя на запросы пользователей и получили такой результат».
Эмс. Ну на других технологиях довольно много чего лежит в опенсорсе почему-то. Так что причин реальных я не вижу.Под понятными причинами я имел в виду, что у нас все closed-source. Видимо, неудачно выразился.
Ну… можно просто порассуждать о возможных проблемах. Если редьюсеры превращаются в кашу с кучей копипасты — с ними точно что-то не так.
Мы используем редьюсеры исключительно как хранилище обычных данных (грубо говоря, кэш), состояния сессии, и некоторого состояния UI (но далеко не всего). Гипотетические сотни моделей, если они являются сущностями, могут уложиться в те же сотни редьюсеров с нормализованными данными. Каких-либо проблем я тут не вижу. Если они есть — укажите.
Возможно, а у меня есть подозрение, что это так, вы имеете в виду сложный процессинг этих данных, кочующий из редьюсера в редьюсер в виде слабосвязанных функций или той же копипасты. В общем, особого процессинга или какой-либо нетривиальной логики, связанной с ним, у нас в редьюсерах нет. Их задача достать из пэйлоада экшена ненормализованные данные, нормализовать и сохранить. Вся обработка вместе с логикой лежит в сагах (процессах), и там это все очень хорошо упорядочивается и группируется.
Под понятными причинами я имел в виду, что у нас все closed-source. Видимо, неудачно выразился.
Ах, я не понял, что вы именно про ВАШ код. Я же говорил впринципе про любой код.
Вот видите, вы говорите, что мои рассуждения неправильные, потому что в реальном коде не так, как я и ожидал. Потому и хотел глянуть на реальный код и ткнуть пальцем, и мне не могли сказать: «но в реальном коде это не так».
А то с редаксом всегда так. Из-за Стокгольмского синдрома те, кто его используют — защищают, а показать недостатки на практике — невозможно, потому-что «код закрыт, извините, но честно-честно, там все очень клево, он нас не очень сильно насилует».
Я же говорил впринципе про любой код.Ну тут, увы и ах, более менее серьезных примеров в открытом доступе действительно нет. С сагами так вообще все плохо, кроме их документации (кстати, достаточно неплохой) и статей на медиуме из серии «саги для чайников» вообще ничего нет. Приходится искать литературу по process managers и вот этому всему и как-то перекладывать на js.
Вот видите, вы говорите, что мои рассуждения неправильные, потому что в реальном коде не так, как я и ожидал. Потому и хотел глянуть на реальный код и ткнуть пальцем, и мне не могли сказать: «но в реальном коде это не так».
А то с редаксом всегда так. Из-за Стокгольмского синдрома те, кто его используют — защищают, а показать недостатки на практике — невозможно, потому-что «код закрыт, извините, но честно-честно, там все очень клево, он нас не очень сильно насилует».Так а вы покажите с какими недостатками столкнулись вы. А я попробую смоделировать их решение на нашем стэке.
Глядя на функцию нельзя однозначно сказать на работу с какой частью стора она заточена.Ну так в этом то и смысл. Функция отвязана во всех смыслах от места компоновки. А типизация еще больше упрощает работу.
Ну вот это и не нравится. Мало того, что данные и функция, их мутирующая в разных местах, так она ещё и во всех смыслах полностью отвязана.
А про типизацию не понял: в сторе редакса же все объекты нетипизированные, по сути исключительно литералы, нельзя же сделать где-то в редьюсере return new User(username), а надо return {username}. Или можно?
Как у вас выстроено дерево редьюсеров, что он у вас работает с данными в разных местах? Не очень понимаю.
{...state}
не удобно будет делать. А без этого не поменяется ссылка и Redux не запустит перерендер компонентов. Вам просто не нравится, что Redux функциональный.Мне не нравится прежде всего, что связка Redux+React предполагает, что нужное состояние DOM является чистой функцией от одного параметра — глобального стора. В теории можно создавать хоть в каждом компоненте сторы как замену this.state и редьюсеры как замену this.setState(), но на практике это не просто.
P.S. Совершенно верно, редьюсеры можно использовать на любом уровне для любого стейта.
Никогда не доверял глобальным переменным :), а по сути стор таковой и является.
Отчасти я наоборот хочу, чтобы знали :)
А вообще, глобальный стор редакса со всеми экшенами, которые могут его изменять, у меня чётко ассоциируются с ООП-антипатерном GodObject, а то и с процедурным программированием с большим применением глобальных переменных.
Отчасти я наоборот хочу, чтобы знали :)Вы про контейнеры, надеюсь? Селекторы добавляют дополнительный слой чтения, и позволяют избежать излишнего связывания.
А вообще, глобальный стор редакса со всеми экшенами, которые могут его изменять, у меня чётко ассоциируются с ООП-антипатерном GodObject, а то и с процедурным программированием с большим применением глобальных переменных.Вот и зря, так как стор — результат функциональной композиции, а не какая-то глобальная ерунда, к которой все имеют доступ и что-то в ней меняют, как в случае с god-object'ом. То, что стор — javascript объект — это детали реализации.
Знание "пути" в сторе позволяет быстрее определить, а что, собственно, редьюсер делает в терминах домена.
Можно жёстко, одним файлом, например, ограничить редьюсеры, которые могут менять участок стора? Насколько я знаю нет.
ограничить редьюсеры, которые могут менять участок стора?Я выше вам написал уже, редьюсеры не меняют участки стора, они этот стор создают при композиции.
Исследование чужого проекта: есть стор, который можно посмотреть в рантайме, и есть куча редьюсеров. Открываешь код редьюсера и видишь, что он, например, какое-то поле очищает, но с чем это поле связано, где находится, на что в сторе не понятно.
Формально, да, не менют, а создают новый на основе старого. По факту для большинства приложений — это лишь деталь реализации, поскольку новое значение стора просто заменяет старое.
Открываешь код редьюсера и видишь, что он, например, какое-то поле очищает, но с чем это поле связано, где находится, на что в сторе не понятно.Ну так вы ищите, где этот редьюсер компонуется, и сразу становится понятно, какой кусок стейта к этому редьюсеру относится.
К тому же, ну и что что очищает? Это его стейт, и завязок на это поле в других кусках стейта быть не должно. Можете привести пример такого поля, чтобы понятней было?
Разве у редакса есть ограничение, что изменять один участок стейта может один и только один редьюсер? Если есть, то мне надо многое переосмыслить :(
«Как минимум, MobX нормально оперирует привычными многим бэкендерам полноценными объектами, инкапсулирующими данные и поведение.»
А кто сказал, что всем нужно именно это? Это просто другой подход. Просто mobx как решение и хранит состояние, и имеет весь инструментарий для его управления. Плюс реактивность MobX, но это немного другая тема.
Redux же очень прост, но, по сути, действительно, умеет только хранить состояние, да и все. Ему нужно что-то, что будет поддерживать весь его (потенциально) навороченный стейт в порядке и согласованности. Причем, с ростом проекта, таких сценариев становится все больше, а они сами становятся все сложнее. Нужна асинхронность, опять же. Отсюда и появляются redux-saga, redux-observable (реактивные «саги»), сам я предпочитаю вообще Cycle.js использовать для подобного рода логики.
Так что, тут просто разница подходов и, по сути, дело вкуса. Но почему то мне кажется, что Redux придется по душе многим, или даже большинству, именно из за своей прямолинейности и простоты.
MVC-фреймворк, ORM, Data Mapper, IOC-контейнер, логер, профайлер, очереди, управление конфигурациями, сборка и выкладка
а кто сказал что все это обязательно надо?
Не надо явскриптовое безумие переносить обратно на бэкенд.
для новичка будет плохо, особенно, когда начинается описание того, что такое компонент, рендер, state, props. Автор преподносит автору салат из терминов философии реакта) Я то понимаю, но для новичка это будет кошмар. Ещё и сравнения вначале ненужные и интро затянутое.
Введение в React и Redux для бекенд-разработчиков