ZarahioN Presents

Answering why

About Flux

Почему вообще Реакт? [Pt.4 – Why bother]

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

Я как-то наскоком погнал ставить вебпак, ваять реакт и даже не поудосожился предложить один очень интересный вопрос: а зачем он вообще?

Для ответа на него, думаю, стоит вернуться к «реальному программированию» на ПК — «прикладному программированию оконных приложений» (desktop applications), это технически все пользовательские программы на Windows и macOS. С Linux все немного интереснее, но даже там «оболочку» приобретают все больше приложений с ростом базы обычных пользователей.

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

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

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

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

И тут на сцену выходит MVC — Model-View-Controller, ставший классикой и бичом начинающих познавать мир подход, архитектура и шаблон создания (относительно) сложных приложений. Как всегда, почитай о нем на досуге, только осторожно, его постигли излишняя популярность и возраст, и это породило кучу мнений, рассуждений и взглядов на то, каким должен быть «правильный» MVC.

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

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

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

Flux на спасение (глуповатых мартышек)!

Удивительно, но не я один страдал от неочевидности и можно сказать искусственной сложности привычного подхода, особенно того хаоса, что часто царил в веб-разработке. Поэтому компания добра ™ Facebook представила (и скорее всего втихоря у себя использовала некоторое время) Flux-архитектуру. Из своего скромного опыта я бы сказал, что она похожа на доработанный и функциональный MVVM, но не буду, так как они все достаточно схожи.

Что же из себя представляет флакс (флукс? флякс? фляжка.. Flux!)? Это однонаправленная архитектура построения приложений, включающая Представление (Вид, View), Состояние (Модель, Store) и связку Действий и Диспетчера (Action+Dispatcher, не совсем, но в некотором роде Контроллер).
В отличии от MVC, где, в некоторых трактовках и подходах, Представление могло общаться как с Моделью так и с Контроллером, иногда еще и напрямую обновляя первую за счет действий пользователя, во Flux Представление получается за счет декларативного преобразования Состояния, Состояние же образуется за счет изменений созданных Диспетчером, которые в свою очередь вызывают Действия, которые могут быть созданы Представлением (пользователем) либо самой системой.

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

]

Должен признаться, я скорее всего был плохим программистом, когда только столкнулся с необходимостью создавать интерфейсы на C#, я частенько путался и не был уверен, что, куда, зачем и почему. Не то чтобы я стал сильно лучше сейчас (я бы назвал себя смышленой мартышкой, пожалуй) но модель флукса я понял и принял мгновенно. И.. думаю это именно то, чего мне всегда так не хватало при попытке создать прикладное приложение (и декларативного реакта в виде представления, но это уже очень приятные мелочи).

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

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

Реакт, Флукс, Состояние, весело [Pt. 6 – Why states]

In previous chapter…

Что же, не так давно, в прошлой статье мы познакомились с Store — Состоянием (я о модели Flux) в базовом исполнении, который нам предлагает сам реакт. И появляется закономерный вопрос — а что с ним можно делать?

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

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

class PseudoGame extends React.Component {
    state = { rotation: 0 }
    componentDidMount() {
        this.loop = window.setTimeout(this.loopFunction.bind(this), 16)
        // или requestAnimationFrame и подобные
    }
    loopFunction() {
        const { rotation } = this.state;
        this.setState({rotation: rotation + Math.random()})
        window.setTimeout(this.loopFunction.bind(this), 16);
    }
    render() {
        const { rotation } = this.state;
        return (
            <div style={{margin: '100px auto', width: 300, height: 300, border: '1px solid #ab00ff', transform: `rotate(${rotation}deg)`}} />)
        );
    }
}

Или реальная версия:

See the Pen React Rotating circle by Anton (@Askadar) on CodePen.

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

Естественным образом вытекает уже данный ответ — мы вольны ваять из состояния все, что только пожелаем. Ведь как я уже не раз повторял — классическое состояние реакта это просто JS переменная, за которой реакт «следит» только за счет использования setState или вскользь упомянутого forceUpdate (, в практическом смысле, заглянуть под капот я пока не пытался).

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

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

Однако, состояние в реакте хоть и является часто достаточным, но очень быстро становится неудобным и громоздким в некоторых критических случаях:
1. Вложенность\иерархия
2. Связи, особенно уровня братьев (siblings) или дети->родитель (children->parent)
Помимо того одним иногда неудобным недостатком состояния является императивный и нерасширяемый интерфейс setState.

Первая проблема — вложенность или сложная иерархия.

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

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

Для этой проблемы было два способа решения, просто передача всех не взятых свойств через ...rest и контекст, который не особо рекомендуют использовать сами разработчики реакта.
Рекомендация вполне простая — это апи не предназначено для обычной публики из пользователей библиотек — то есть по сути нас, оно предназначено для создателей библиотек. Собственно создатели и привнесли третий (или второй с половиной) способ решения проблемы, предоставив в начале Redux а позже и MobX с еще парочкой альтернатив — полноценных представителей состояния (Store) и в некоторой мере связки Диспетчера и Действий. Но о них чуть позже, перед этим:

Вторая проблема — связи между компонентами.

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

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

- Root
- \ Header
- - \ Profile
- \ Content
- - \ Menu
- - \ Page
- - - \ Title
- - - \ HelloUser
- - - \ Article

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

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

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

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

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

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

Проблема первая, вложенность или передача каких-то параметров вглубь дерева компонентов. Редакс предоставляет единое дерево состояния, части которого можно брать в любой компонент независимо от его положения в дереве компонентов (сколько деревьев). Таким образом можно создать объект User в дереве редакса и подключить его к компонентам Profile, HelloUser и любым другим, где нужны данные о пользователе.

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

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

Впрочем, возвращаемся на землю, несмотря на многочисленные плюсы, у редакса есть интересный неоднозначный минус — он очень многословный и «странный». Вкратце, чтобы подключить редакс «правильно» нужно добавить конфигурацию (как правило в index.js), нужно добавить файлы обработчика, создателя действий и типов действий. На каждый компонент или обработчик. Получается где-то 3 файла на каждый контейнер (это термин выданный редаксом для компонентов с логикой и состоянием). Очень быстро ориентироваться во всем этом великолепии становится мягко скажем страшно.

Однако, как часто бывает — правильно не совсем хорошо, многословность редакса можно (и нужно) обращать в пользу — она предоставляет удивительный простор для семантического описания творящегося в приложение — создатель действий showProfile создает действие profileShowed забирая которое обработчик «понимает» что нужно изменить флажок в состоянии и получив новое состояние реакт отрисовывает стиль модалки в нужном состоянии по обновленному флажку.
И предоставляя простор для семантики он дает возможность ее и уничтожить запутывая других разработчиков приложения странными и страшными названиями — любые стилистические выборы кода должны быть понятны и согласованы между всей командой. Я не раз видел запутавшихся или не желавших разбираться в редаксе разработчиков только из-за его странной и часто неприятно использованной многословности, с таким мощным инструментов нужно обращаться очень внимательно и осторожно.

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