ZarahioN Presents

Answering why

Author Archive ZN Dev-Ops Head Monkey

Welp, we’ve tried Reduh way of things [Pt. Fin]

So, я пытался рассказать о реакте, редуксе, и прочих прелестях.

Но, я встретил его, Vue, и.. все очень быстро поменялось.

Он настолько простой, приятный и «легкий», что возвращаться к громоздкому реакту слишком сложно.

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

So in so, fare thee well.

Возвращения тавтологии [Pt. 9.R — The Reduh deal]

Уф..

It’s been a long time, isn’t it?

Много всего произошло за.. месяц?..

Я искал работу..

Баловался нервишками..

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

А еще встретил Vue, но об этом интересном и увлекательном событии позжее.

А сейчас
*пуф-пуф пыльку с репо для редакса*

На чем мы там остановились? А-а, тудушковое приложение в классическом реакте. Ну, почти классическом, за исключением Immutable.js и редакс-оватого подхода к данным.

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

yarn add redux react-redux
или
npm i -S redux react-redux

Что добавит сам редакс и связующую библиотеку react-redux. Последняя нужна для упрощения подключения редакса к компонентам реакта и приложения, что технически можно и ручками, но не стоит.

Для начала

идет чутка всеми любимого и в меру единого конфигурационного кода в нашем src/index.js:

import { createStore, compose, combineReducers } from 'redux'
import { Provider } from 'react-redux'

import todoReducer from './TodoApp/reducer'
const composer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
    combineReducers({todoReducer}),
    composer()
)

ReactDOM.render(
<Provider store={store}>
    <App />
</Provider>
, document.getElementById('root'));

Как и всегда, идем по строкам:

1, 2
импорты, все привычно и знакомо.

4
мы импортируем редуктор (reducer) из созданного файла reducer.js, для простых приложений это как правило подходит, однако я расскажу про более привычный способ импорта позжее.

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

6-9
непосредственно запуск редакса который заключается в создании его «магазина» store или как я чаще его называю Состояния. Внимательно, вся активность непосредственно с состоянием осуществляется функционалом библиотеки redux-а, а не ее оберткой-коннектором react-redux.

Самое просто состояние можно инициализировать просто передав в createStore одну функцию-редуктор:

createStore(
    (state = { count: 0} , action) => action.type === 'increment' ?
        { count: state.count + 1} :
        state
)

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

const store = createStore(combineReducers({
    todoReducer,
    b: anotherImportedReducer
}))

Создаст такое дерево состояния редакса:

{
    todoReducer: <todoReducer's initial state>
    b: <anotherImportedReducer's initial state>
}

И наконец 11-15
Provider это специальный компонент подключающий переданное состояние редакса (store) к нашему приложению, втихоря залезая в context чтобы позже можно было отдать его [состояние] указанным нами компонентам.

На всякий случай повторим что произошло:
1. Мы импортировали редуктор todoReducer.
2. Мы создали состояние, передав скомбинированный через composeReducers редуктор (либо просто передав сам редуктор createStore(todoApp, ...))
3. Создавая состояние мы также передали наше особое дополнение для разработки полученное из расширения Redux Dev Tools посредством пустого вызова названной функции composer.
4. С помощью специального компонента Provider из react-redux мы незаметно передали состояние в контекст, позволяя в будущем получить его в любом компоненте приложения (App).

Страшно, но совсем несложно.

Однако, что же такое редуктор?

Уверен в прошлый раз я вкратце рассказывал про редакс и его место в нашем приложении, как он работает и что делает, и бла-бла-бла. Но! Свои пояснения я представлял с сугубо теоритической точки, сейчас же мы рассмотрим практику взглянув на самый просто редуктор который не может (практически) ничего:

@TodoApp/reducer.js

const initState = {
    hello: 'world'
};

export const types = {

};

export default (state = initState, action) => {
    switch(action.type){

        default: return state;
    }
};

Как можно заметить, стандартный экспорт у нас в самом низу (то что мы импортировали в index.js как import todoRecuer from ./TodoApp/reducer) и представляет он из себя стрелочную функцию, по двум достаточно условным причинам:

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

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

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

Но вернемся к сбежавшему коду — у нас осталась строка с пустым объектом types — сюда мы будем помещать типы действий редакса — строк-указателей, которые говорят редуктору, по какому пути switch-а нужно обработать пришедший объект действия (если его вообще нужно обрабатывать а не вернуть default ничего не трогая).

Что же, осталась последняя остановка для подключения нашего очень умного и полезного редуктора к приложению, но перед этим вопрос:

А что мы будем там хранить?

Одна из интересных «проблем» с первым знакомством с редаксом это желание и часто наставление запихнуть в него ВСЁ. Начиная от логики приложения, допустим CRUD-а наших тудушек, до состояния наведения каждой сколь важной кнопки. И если первое скорее всего получит N-ный набор преимуществ от переезда в состояние редакса, со вторым и всеми последующими нужно быть осторожными — излишек данных в едином дереве редакса усложняет его восприятие в куда большей степени, чем небольшие кусочки визуальной и не только логики в отдельных компонентах.

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

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

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

Сделать это умный (но точнее сложный)

Добавим небольшой компонент для вывода списка пользователей и заголовок в приложение:

@UsersSelect.jsx
const UsersSelect = ({users, selectedUser, onChange}) =>
<select
    value={selectedUser}
    onChange={evt => onChange(evt.target.value)}
>
    { users.map(
        (user, index) => <option key={index} value={index}>{user}</option>
    )}
</select>

@Header.js
const Header = ({users = ['Tom', 'Peter']}) =>
<div className="todos">
    <UsersSelect users={users}/>
</div>

@TodoApp/index.js
render() {
    return (
        <div>
            <Header/>
            <TodoList
                setKey="today"
                todosLabel="ToDo ToDay"
            />
        </div>
    );
}

Пока все просто-мирно и знакомо, мы создали два функциональных компонента выводящие данные.

@Header.js
import { connect } from 'react-redux'
...
export default connect(
    function(state) { return state.todoReducer; }
)(Header);

И тут мы встречаем небезизвестный connect.

Что же это такое?

По факту connect это фабрика оберток, или Higher order Component(ов) — HoC. Она принимает до 4 аргументов, первый из которых функция-разбборщик состояния редакса, как правило ее называют mapStateToProps, потому что она, как следует из говорящего названия — привязывает состояния редакса с свойствам компонента.

Очень часто ее можно встретить в одном из трех максимально простых видов:

connect(state => ({
    propA: state.reducerA.propA, // ein
    wholeReducer: state.reducerB, // zwei
    ...state.destructuredReducer // drei
}))

Первый случай это прямое взятие одного значения из состояния редуктора reducerA в свойство propA,
второй — это взятие всего состояния редуктора reducerB в свойство wholeReducer,
а третий это деструктуризация всех значений из редуктора destructuredReducer.
Важное замечание: такой разбор подразумевает комбинирование редукторов через combineReducers который создает отдельную ветку под каждый редуктор, если создавать состояние редакса из одной функции-редуктора, то он будет являться непосредственно state-ом, но такое использование редакса достаточно редко.

Имея полученое знание, что получит наш Header с таким коннектом? Правильным ответом будет весь набор todoReducer-а — возвращенное из mapStateToProps значение по ключу передается свойствами в компонент, в этом можно самостоятельно убедиться глянув как будет передана простая строка.
А мы же можем проверить что базовое состояние нашего редуктора успешно передалось выведя значение свойства helloHello {hello} — где-либо в компоненте (не забудь добавить его в деструктуризацию из props либо получение его оттуда через (this.)props.hello).

Все это хорошо, но нужно закругляться и сделать редуктор не настолько глупым — зададим адекватное начальное состояние и добавим наш первый создатель действий «action creator«:

@reducer.js
const initState = {
    users: ['Tom', 'Peter', 'Vanko', 'Monkeyman'],
    selectedUserId: 0,
};

export const types = {
    selectUser: 'SelectUser',
};
export const actions = {
    selectUser: (selectedUserId) => ({
        type: types.selectUser, selectedUserId
    })
};

export default (state = initState, action) => {
    switch(action.type){
        case types.selectUser:
        return {
            ...state,
            selectedUserId: action.selectedUserId,
        }
        default: return state;
    }
};

Как можно заметить наш редуктор пополнился, initState получил массив пользователей и переменную для выбранного пользователя. Помимо этого добавилось значение в объекте types, которое используется в самом редукторе, чтобы «словить» нужное действие и в новом объекте actions, чтобы это действие создать.

Объект actions является по сути списком методов, возвращающих объекты действий — поэтому их и называют action-creators. Каждый метод должен возвращать простой объект, который должен содержать по крайней мере type (который может быть пустым, но так делать не стоит, мы же хотим знать что нужно сделать редуктору и различать пришедшие действия).

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

@Header.js
import { actions } from './reducer';
...
export default connect(
    state => state.todoReducer,
    actions,
)(Header);

Как можно заметить, мы банально передали описанный в самом редукторе объект actions. При желании можно записать действия отдельным объектом или напрямую параметром в коннект:

const mapDispatchToProps = {
    selectUserId() {
        return { type: importedTypes.selectUser }
    }
}
connect(..., mapDispatchToProps)
// or
connect(..., {
    selectUser: () => ({ type: importedTypes.selectUser })
})

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

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

@Header.js
const Header = ({ users, selectedUserId, selectUser }) =>
<div className="todos">
    <UsersSelect
        users={users}
        selectedUserId={selectedUserId}
        onChange={selectUser}
    />
</div>

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

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

Однако отвечая на вопрос — для будущего.

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

А пока, наше любимое повторение:
— В Header.js мы ввели состояние редуктора todoApp с помощью connect(state => state.todoApp, ...)
— В Header.js мы ввели создатели действий для редуктора todoApp с помощью connect(..., actions)
— В Header.js мы привязали полученые свойства и метод для обновления выбранного пользователя в состоянии редакса

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

И на этом на сегодня все, прощаюсь не прощаясь.
See ya!

Следующая остановка, Redux [Pt. 8.R — Hello Redux, my old friend]

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

Начнем со сложного, зачем вообще редакс?

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

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

Вторая и как правило более серьезная причина это набор преимуществ подхода редакса: тестируемоесть, предсказуемость и доступная надежность Состояниия.

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

Что есть Redux?

Как я много раз уже говорил — Redux это библиотека для управления состоянием. Она основывается на взаимосвязи единого дерева состояния (store), которое может быть изменено только за счет чистых (pure) функций-редукторов (reducer), получающих действия (action) описывающие какие изменения необходимы.

Сразу сноска:

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

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

Можно с легкостью проследить подобие такой стратегии подходу Flux — Действия (action) отправляются в Диспетчер (reducer?), который создает новое Состояние (store).
(Создатели редакса отрицают использование Диспетчера, и технически это правда, но размышлять полным набором элементов модели и их сутью удобнее и проще)

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

Поэтому перед продолжением повторю еще раз:
У нас есть одно состояние на все приложение — store,
Оно изменяется только через чистые функции — reducers,
Эти функции получают простые объекты-действия с указаниями какие изменения нужно внести — action.

Теоретически можно провести аналогию с базовым setState реакта:

// redux:
reduce((oldStore, action) => newStore ) -> newStore
// almost the same (on surface), react:
event => (action => this.setState(reduce(action))) -> newState

Только модель редакса предлагает куда более широкий доступ к самому процессу установки нового состояния.

Как?

Я уже много раз повторял, что в модели флукса, которой практически следует редакс, идет строгий порядок действий — (из Представления) создается Действие, оно попадает в Диспетчер который решает каким получится новое Состояние, из которого собирается Представление и цикл замыкается.

Как мы уже договорились — реакт является нашим Представлением — он собирает разметку из свойств (props) и опционально Состояния (state) компонента.

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

При этом редакс, являясь полной реализацией Состояния (как реакт являеется реализацией Представления), имеет также базовые задатки Действий и кхем Диспетчера (так же, как реакт имеет задатки для полной модели). Можно догадаться, что я веду к тому, что делая реализацию модели более устойчивой с полноценной библиотекой владеющей Состоянием, мы в будущем можем сделать тоже и со связкой Диспетчера+Действия.
(Намек на будущее это redux-saga)

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

Наконец, демонстрация

Допустим у нас есть приложение, все так же ссылаясь на мой креативный пример про заголовок, где мы создали компонент Profile для отображения непосредственно профиля пользователя. Добавить к нему редакс можно подобным образом:

@reducer.js
const types = {
    newAvatarSet: 'NewAvatarSet',
}
export const actions = {
    setNewAvatar: (avatarUrl) => ({
        type: types.newAvatarSet,
        avatarUrl,
    })
}
export const reducer = (state, action) {
    switch(action.type) {
        case types.newAvatarSet:
        const { avatarUrl } = action;
        return {
            ...state,
            avatar: avatarUrl,
        }
        default: return state;
    }
}

@App.jsx
import {actions} from './reducer';

const Profile = ({name, avatar, bio}) =>
<div>
    <div>
        <span>{name}</span>
        <img
            src={avatar}
            alt={name + "'s pretty face'"}
            onClick={
                actions.setNewAvatar(/* url returned from modal for example*/)
            }
        />
    </div>
    <p>{bio}</p>
</div>;

export default MagicallyConnectReduxToReactComponentAndReturnsComponentWithRequiredConnections(
    transferReduxStateToReactProfile,
    transferAvailableReduxActionsToReactProfile // как правило импортированный объект actions
)(Profile);

@index.js
const store = ReduxMagicStoreCreator(...);
ReactDOM.render(
<ReduxMagicWrapper store={store}>
    <App />
</MagicWrapper>
, DOMnode)

Итак, что у нас тут интересного:
— Новый файл reducer.js — по сути тут живет редакс, обычно название файла соответствует названию компонента либо ветви дерева Состояния, но на этот раз я для простоты оставил просто reducer.
— «Неведомый» store переданный «неведомому» ReduxMagicWrapper-у — это детали связи между реактом и редаксом, которые я обозрю капельку позже.
const actions = { action: (ourParameters) => ({type: ourActionType, ourParameters})}; — непосредственно объект действий. Как правило это будет объект с функциями, которые возвращают объекты Действий, иногда можно сделать «сложнее» и «круче» через функцию возвращающую функции, но это пока не наш случай.
const reducer = (currentState, recievedAction) => (newState) — «редуктор-присваиватель» — функция, которая получает текущее состояние приложения и придуманное нами действие и по придуманной нами логике создает новый лучший объект состояния. Я не зря выделил тот факт, что мы решаем как выглядят действия и что с ними делает редуктор, этот момент долго путал меня, когда я только начинал разбираться с редаксом.
— Кривой и непродуманный onClick у аватарки — когда я думал на примере чего показать редакс, я забыл что мне нужно будет как-то создавать Действие, считай что там крутая схемка вызывающая модальное окошко, в которое bla-bla-bla.

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

@types.js
export default {
    SET_NEW_AVATAR: 'SET_NEW_AVATAR',
}

@actions.js
import types from './types';
export default {
    setNewAvatar: (avatarUrl) => ({ type: types.SET_NEW_AVATAR, avatarUrl}),
}
// и редуктор в котором так же импортируются типы действий

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

Вернемся к интересному, а точнее к деталям работы.

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

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

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

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

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

Первое настоящее приложение с React.js [Pt. 7 — Todo App!]

Мы подобрались к важной составляющей любого приложения — Состоянию — очень быстро, и я уже думал начать разбирать редакс, однако вспомнил, что хотел показать как можно написать приложение в «трех стилях»: классическом реакте, без библиотек Состояния, с редаксом, как классикой решения проблемы, и современным подходом в виде MobX и его наблюдаемыми коллекциями (observable).

Плюс перед выходом к редаксу или моби (да-да, бедный MobX), не помешает нарядить и прихорошить свой скромный опыт работы с реактом.

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

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

Для начала, я решил предоставить краткое описание важных элементов получившегося монстра приложения, для тех кому ленно зайти в репо и глянуть код:
— У нас есть TodoApp(T)/index.js — базовый импорт всего приложения, который (по уму) должен создавать списки тудушек и подключать стили, но изначально бодяжил вообще все, о чем подробнее в графе ошибок,
T/TodoList.js(x) — изначально глупое Представление списка тудушек, которое благоразумно развилось до умного компонента, держущего список тудушек и следящего за ним, помимо прочего,
T/Todo.jsx — классически глупое Представление отдельной тудушки, которое раскидывает обработчики и данные,
T/AddTodoField.jsx — мое (спорно) глупое Представление поле добавление новой тудушки, о нем тоже подробнее ниже,
— и «мелочи» вроде стилей, моих неказистых CSS иконок и простенького заголовка.

Ожидаемо основной вес, чуть ли не чрезмерный, имеет TodoList — поумневший компонент, который отвечает за список тудушек и все CRUD действия с ними.
CRUD — это часто используемая с БД аббревиатура означающая стандартный набор действий с данными: Создание, Чтение, Изменение и Удаление (Create, Read, Update, Delete).

Интересным образом я заметил у себя полный редакс привычек — даже не используя его, я пытаюсь следовать подобной методике — набор действий и редукторов, которые предсказуемо изменяют состояние в TodoList:

const handlers = {
    onDoneClick: (index, done) => {
        const todoSet = this.state.todos;
        const newTodoSet = todoSet.set(
            index, todoSet.get(index).set('done', done)
        );
        return this.setState({todos: newTodoSet});
    },
    onTodoAdd: (text = '') => {
        if (text === '') return false;
        const todos = this.state.todos;
        this.setState({todos: todos.push(TodoRecord({text}))});
        return true;
    },
    onTodoDelete: (deleting) => {
        if (!isNaN(deleting) && this.state.deletingTodo === deleting){
            handlers.onTodoDeleted(true);
            this.setState({deletingTodo: null});
        }
        this.setState({deletingTodo: isNaN(deleting) ? null : deleting})
    },
    onTodoDeleteCancel: (index) => {
        handlers.onTodoDelete(null);
    },
    ...
};

Ко всему прочему я решил закинуть часто идущий с редаксом «в комплекте» Immutable.js — библиотеку, предоставляющую надежные неизменяеные коллекции и типы данных. Они нужны для поверхностной проверки равенста (shallow equality check) — операций сверения двух коллекций, которая надежно в данном случае определяет является ли это одной и той же коллекцией. Вторым важным преимуществом являтся невозможность такого казуса:

let a = { key: 'var' };
let b = a;
b.key = 37;
// a -> { key: 37 }

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

Почему Immutable, если у нас особо нечего сравнивать и ссылками мы вроде баловаться не собираемся? Все достаточно просто — она позволяет надежно и легко изменить обновлять запись по индексу в массиве-списке (List). Без него редактирование массива превращается в мутации либо в танцы со сплайсом/слайсом (splice, slice) и индексами, что или надоедает, или требует ваяния самодельных костылей.

Помимо держания и упраления списком тудушек, TodoList занимается отображением дополнительных элементов — заголовка и поля для добавления тудушки.
Должен заметить, что по недосмотру так было не всегда — изначально всем этим занимался компонент приложения тудушек — TodoApp/index.js, но он был благополучно унижен до уровня глупого сдав полномочия списку.

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

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

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

{
    editing ? 
    <form className="input-field--wrap" onSubmit={(e) => e.preventDefault() || onTodoEdited(index, editingText)}>
        <input
            value={editingText}
            onChange={ e => oneditingTextChange(index, e.target.value) }
            className="input-field todo-edit"
            type="text"
        />
    </form> :
    [<span key="checkbox-wrap" className="todo-done">
        <input type="checkbox" 
            onChange={
                (e => onDoneClick(index, e.target.checked))
            }/>
        <CheckMarkToggle done={done}/>
    </span>,
    <span
        key="text-wrap"
        className="todo-text"
        onDoubleClick={() => onTodoEdit(index, text)}
    >{text}</span>]
}

Это до глупого элегантный и простой способ выбрать между двумя (и более, если ты любитель лесенок и головоломок) вариантами отображения, для, как у нас, варианта когда что-то редактируется или нет. Суть заключается в том, что {} в JSX позволяют писать любой JS код, возвращающий значения, в том числе и другую JSX разметку.
Таким образом:

const AB = ({isA}) => isA ? <A/> : </B>;

Отобразит компонент A если isA правдиво и компонент B иначе.
Альтернативным и более громоздким (и на удивление менее читаемым и удобным) вариантом будет задать переменную содержащую нужный компонент до рендера с помощью обычного if, но как и многими извращениями, я таким не занимаюсь.

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

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

А теперь об ошибках и Post Scriptum-е

1. index.js-казус

По глупости и под приятную музыку, я зачем-то решил наделить излишним умом самый верх приложения, непосредственно index.js, даже зная о том, что планирую в будущем создать несколько списков и перемещать данные между ними. Предполагаю сыграла уже названная привычка редакса — я думал о том, чтобы начинать дерево состояния в корне в виде нескольких ключей-веток под каждый список, и, как говорится «shot myself in da foot» — сел меж двух библиотек, так и не решив идти с «одно Состояние на компонент» в классическом реакте, или с «одно Состояние на приложение с контекстом» в редаксе.

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

2. «Слишком умный» AddTodoField.jsx

Это не столько ошибка, сколько очень близкое приближение к границам понятий — насколько жестко трактовать запись в Состояние значение ввода, если обработкой этого значения занимается родитель?

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

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

Либо просто расслабиться и принять этот случай как корректное использование краевых случаев терминологии и продолжать считать этот компонент глупым.

Почему возникла эта проблема? Все то же влияние редакса и его жесткое разделение компонентов на Контейнеры и Компоненты — первые имеют Состояние и куда важнее возможность создавать Действия, вторые просто отображают данные. И если следовать терминологии редакса, то под любое изменение состояния нужно писать связку Действие-Диспетчер-Новое Состояние, за счет чего и получается порой излишняя его многословность — две строчки, одна на переменную состояния, другая на обновления состояния в классике против пяти-семи строчек в редаксе разбросанные по любимым трем файлам.

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

О разделении на .js и ,jsx

На данный момент разработчики c-r-a и сообщество реакта не очень рекомендует использовать .jsx расширение по ряду причин, которые мне лень искать. Но два расширения фасилитируют мое разделение глупых и умных компонентов — .js это полноценные умные компоненты, .jsx это глупые, как правило функциональные, компоненты. Такое физическое разделение чисто семантическое и при необходимости можно просто пакетно переименовать все файлы — система импорта вебпака проверяет все сконфигурированные расширения тем самым позволяя опускать его при импорте — import Todo from './Todo'.

By the way, I may create simple «magic app loader» который позволит загружать ваши реализации Todo-приложения из репо, если предложений придет достаточно много — тогда любой сможет посмотреть на все вариации на единой странице, а я покажу как сделать выделенный загрузчик реакт компонентов и дополнительно буду вынужден показать чанкование и использование внешних библиотек.

На сим я бы предложил откланиться и бежать допиливать свое Todo-100500-ое-приложение.

[Pt. 6.42 — Scary hooks]

Крепко задумавшись в прошлый раз о состоянии я вспомнил что забыл о одном очень важном элементе буквальной жизнедеятельности компонентов реакта — lifecycle hooks.

Думаю, если ты знаком с WordPress-ом (или другими представителями систем расширяемых «событийными реакциями») то примерно знаешь или хотя бы слышал такой термин как hook. По сути крючок (hook) — это крючок, как ни странно. Он позволяет «зацепиться» за какую-то часть программы и построить свой небольшой замок в песочнице, или добавить башенку замку большого и доброго старшего товарища.
Если ты немного не понял мою креативную аллегорию — крючки позволяют добавлять свое или изменять «чужое» поведение в программе.

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

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

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

Однако для начала — что (технически) есмь хук?

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

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

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

class DemoLoader extends React.Component {
    state = {
        rows: []
    }
    componentDidMount() {
        fetch(apiUrl)
        .then(resp => resp.ok ? resp.json() : new Error(resp))
        .then(data => this.setState({ rows: data }))
        .catch(err => console.warn(err));
    }
    render() {
        return (
            <table>
                {this.state.rows.map(row => <tr><td>{row.text}</td></tr>)}
            </table>
        );
    }
}

Как можно заметить, я инициализирую состояние с одной пустой переменной rows, которая в последующем заполняется через fetch при монтировании компонента. «Реальную» демонстрацию можно посмотреть на codepen.

Что же происходит?

Реакт, или точнее его компоненты, имеют так называемый жизненный цикл (life cycle) — набор действий или состояний, через который проходит любой используемый компонент. Он начинается в constructor()-е компонента и заканчивается при размнотировании componentWillUnmount(), проходя между ними «в районе» десятка шагов которые нам доступны, пара из которых нам бывает полезна очень часто, componentDidMount() — один из таких шагов.

Какие они есть?

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

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

Как ни странно, но следующим идет непосредственно render(), в частности повторяющийся при каждом изменении компонента. Это, как уже думаю понятно, освной метод любого компонента — именно он задает Представление.

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

Это нужно в случаях, когда у нас есть, к примеру, таблица в пару тысяч строк — по умолчанию если у нас измениться хоть одно свойство у родительского компонента (table) то реакт будет вынужден заново провести рендер каждого вложенного ряда (tr) — что естественно не имеет смысла в большинстве случаев. Поэтому мы можем сами решить, стоит ли ререндериться компоненту — как правило сравнивая переданные в shouldComponentUpdate(newProps) новый набор свойств с текущим набором в компоненте this.props.

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

Отдельно стоит исключительный метод componentDidCatch, для поимки исключений.
I’ll exit myself out..

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

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

На сим я завершаю наше короткое ознакомление с жизненным циклом компонентов реакта.

Small notification

Если ты немного интересовался нашей (ZN Group) работой, то, возможно, заметил наши скромные благотворительные потуги, которые на данный момент освящаются на сайте издательства.

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

На сим, пожалуй, все. See ya!

Read More

Реакт, Флукс, Состояние, весело [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 забирая которое обработчик «понимает» что нужно изменить флажок в состоянии и получив новое состояние реакт отрисовывает стиль модалки в нужном состоянии по обновленному флажку.
И предоставляя простор для семантики он дает возможность ее и уничтожить запутывая других разработчиков приложения странными и страшными названиями — любые стилистические выборы кода должны быть понятны и согласованы между всей командой. Я не раз видел запутавшихся или не желавших разбираться в редаксе разработчиков только из-за его странной и часто неприятно использованной многословности, с таким мощным инструментов нужно обращаться очень внимательно и осторожно.

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

Пора умнеть [Pt. 5 — React Statefull Components, variation Uno]

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

Небольшие правки были совершены в трактовке и тавтологии понятий 4 Апреля 2018

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

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

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

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

Таким образом терминология получается несколько сложнее, чем просто разделение есть состояние/нету, или есть функции/нету, однако такой набор правил позволяет чуть строже и надёжнее разграничить области влияния глупых и умных компонентов.
И как всегда — это лишь то разделение, к которому я пришел со временем, поглядывая на Flux, Redux и прочих влиятельных игроков на поле деятельности. Каждый волен использовать свои понятия, до тех пор, пока их ясно осознают все члены команды.

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

@index.js
import ...
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));

@App.js
import React, { Component } from 'react';
import './App.css';

const LabeledInput = ({children, name, value, onChange, type="text"}) =>
<div className="input-group">
    <label htmlFor={name}>{children} </label>
    <input type={type} id={name} name={name} value={value} onChange={onChange}/>
</div>

class App extends Component {
    // constructor(p) {
    //     super(p);
    //     this.state = {
    //         demo: '',
    //         name: '',
    //         password: ''
    //     }
    //  this.handleInput = this.handleInput.bind(this) // привязка при использовании методов класса
    // }
    // handleInput(e) {
    //  this.setState({[e.target.name]: e.target.value})
    // }
    state = {
        demo: '',
        name: '',
        password: ''
    }
    render() {
        const handleInput = (e) => this.setState({
            [e.target.name]: e.target.value
        });
        const {
            demo, name, password
        } = this.state;
        return (<div className="form-container App">
            <form action="" onSubmit={(e) => e.preventDefault()}>
                <LabeledInput value={demo} name="demo" onChange={handleInput}>
                    First input label
                </LabeledInput>
                <LabeledInput value={name} name="name" onChange={handleInput}>
                    Namu?
                </LabeledInput>
                <LabeledInput value={password} name="password"onChange={handleInput} type="password">
                    Passwort
                </LabeledInput>
            </form>
            <div class="output">
                {
                    Object.entries(this.state).map(arr =>
                        arr[1] !== '' && <div>{arr[0]}: {arr[1]}</div>
                    )
                }
            </div>
        </div>);
    }
}

export default App;

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

В самом приложении (App.js) находится сразу два компонента — глупый для отображения отдельного поля в форме и умный для самой формы — LabeledInput и App соответственно. Помимо уже знакомых и вручную переданных через JSX атрибуты свойств (props), в LabeledInput присутствует еще одна деструктуризированная переменная children.

Children это специальное свойство (элемент объекта this.props), в которое реакт передает всех потомков JSX элемента:

<LabeledInput value={demo} name="demo" onChange={handleInput}>
    First input label
</LabeledInput>

В данном случае это просто текст «First input label» между открывающим и закрывающим тегом.
Сюда можно передавать любой JSX элемент — простой текст, будь то прописанный нами литерал или переменная, стандартные HTML элементы и другие JSX компоненты. При этом компонент получивший таких потомков сам решает что с ними сделать — в нашем случае мы просто вывели их в label, однако при желании и должной сноровке можно, к примеру, обработать, отфильтровать, или использовать их для каких-либо проверок внутри компонента.

Следующей примечательной остановкой является переменная\поле класса — state. Согласен, удивительно, но это именно то, что отличает statefull компоненты от stateless.
По сути state это объект (как правило; технически это просто переменная JS) представляющий «состояние» компонента — все данные нужные для его внутренней работы. Это чисто наш объект — реакт никак не вмешивается в его работу и узнает о его изменениях только в случае предназначенного обновления через специальный метод компонента setState. Альтернативно можно использовать forceUpdate() и прочие непотребства, но естественно это не рекомендуется в обычной практике.

Должен заметить что, насколько я помню, переменные класса не вошли в стандарт ES6 и могут быть недоступны в зависимости от настроек бейбла, впрочем, я могу ошибаться. Однако, если я не ошибся, то тебе возможно придется использовать классический синтаксис и инициализировать состояние через конструктор, я включил примерный код в виде комментария. Учитывая что наш класс расширяет компонент реакта, в самом начале конструктора нужно вызывать super передав в него полученные аргументом свойства (props) p. (Признаюсь, меня никогда не интересовало почему нужно так делать, поэтому объяснить это я не смогу)

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

const handleInput = (e) => this.setState({ [e.target.name]: e.target.value });

Это все та же любимая стрелочная функция, она получает исскуственный объект события и обновляет состояние установив по ключу в виде названия инпута (name) его значение (value). Я опять же использую один из ES трюков — переменную в виде ключа объекта.

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

Еще один важный момент связанный со стрелочными функциями: я упоминал, что их почти всегда можно напрямую заменить обычными. В данном случае это не так — мы используем ключевое слово this в обработчике, чтобы ссылаться на отображенный компонент (или экземпляр класса), а классические функции в JS всегда создают свое поле деятельности this(, или используют предоставленное им поле объекта, если это методы, а еще они могут… в классическом JS с this все достаточно запутано и сложно, если вкратце).

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

  1. Классика и создание связывающей ссылки вроде переменной self, принцип простой — перед определением функции мы создаем «переменную» к которой привязываем нужный нам вариант this и используем уже эту переменную вместо прямого обращения к this, который был «перезаписан» функцией.
  2. Чуть более элегантный и признанный — создания «присвоенной» копии функции через специальный метод bind. Он позволяет указать что использовать функции в качестве this (или точнее просто привязать ссылку this внутри функции на то, что нужно нам). Помимо этого bind позволяет (и то зачем я куда чаще его использую) добавить аргументы в функцию, к примеру:

start="1.">
const reducingFunction = (incrementing , acc, elem) => (incrementing ? acc + elem : acc - elem)
let a = [1,2,3,4,5].reduce( reducingFunction.bind(null, true), 0 );
// a -> 15

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

Однако, возвращаемся после нашего краткого экскурса в страшные детали работы JS к доброму-мирному реакту.

Перед возвратом разметки можно заметить деструктуризацию значений для наших полей из состояния:

const { demo, name, password } = this.state;

Ничего особо нового, просто источник данных сменился со свойств (props) на состояние (state).

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

Небольшим бонусом в самом конце я добавил вывод состояния через (очередную технически не полностью поддерживаемую) функцию (точнее статический метод, что не суть) Object.entries, обладающую двумя младшими братьями — Object.keys и Object.values, у них поддержка, кажется, чутка лучше. Суть у нее проста — взять объект и вернуть массив «кортежей» (но на самом деле массивов, мы же не развлекаем змеек) в виде [ключ, значение].
На тему поддержки — мы в царстве бейбла и технически можем о ней не волноваться, ибо все что нужно будет скомпилировано в простонародные варианты либо подкрашено заполнителями (polyfill), однако учитывая приятность огромного количества (человеческих, кхем) нововведений ES6 и выше, легко можно привыкнуть и писануть чего-нибудь ломающего браузер непросвещенного пользователя. Поэтому я стараюсь по ходу делать рассказывать о всем том, что это ломание любит вызывать и с чем я сам радостно сталкивался (тратя часы на отладку с пользователем устаревающего сафари, который при этом просто так не поставить себе для испытаний, я всегда недолюбливал яблоки).

Отставив стоны и стенания, запускаем
yarn start
И можем любоваться на нашу скромнейшую форму.
Но тыкать куда интереснее, поэтому не забудь ввести что-нибудь в каждое поле и порадоваться «магическому» обновлению небольшого отладочного вывода.

К слову о отладке, есть небольшой трюк для логгирования (и не только) в JS:

const LabeledInput = ({children, name, value, onChange, type="text"}) =>
console.log(children) || <div className="input-group">
    <label htmlFor={name}>{children} </label>
    <input type={type} id={name} name={name} value={value} onChange={onChange}/>
</div>

Так как console.log не возвращает значений он приравнивается к false и из-за поведения логического присваивания в JS функция возвращает не undefined а правильное значение после || — JSX разметку нашего компонента.

Итак, мое любимое повторение. Чего мы наваяли:
1. Создали полноценный компонент (класс) с помощью class X extends Component
2. Создали функциональный компонент для каждого именованного ввода с помощью стрелочной функции const Y = ({...props}) =>
3. В умном компоненте инициализировали состояние через поле класса state
4. В отображении (рендере) умного компонента создали обработчик полей ввода и деструктуризовали значения из его состояния
5. Вернули в умном компоненте разметку, в которой использовали функциональный компонент, передав его экземплярам данные и обработчик.
(6. Надеюсь, потискали получившуюся форму)

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

Ну а пока — до встречи, надеюсь скорой.

SymLinks to the rescue! Использование одной темы на нескольких WordPress сайтах

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

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

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

Но немного не получилось, WP по глупости своей пытается подставить полный физический путь в возврат get_stylesheet_directory_uri или get_template_directory_uri вместо стандартного wp-content/themes/theme-name.

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

Что же делать?

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

Убираем register_theme_directory и идем в терминал, пора потрудиться.

Для справки, на момент написания статьи у нас стандартная установка apache на виртуальной ubuntu настроенная через sites-available файлы-конфиги виртуальных хостов, каждый сайт имеет свою директорию под /var/www/<site-name>, общая директория имеет структуру /var/www/shared/themes/<theme-name>.

Думаю, имевшие дело с Linux и пару извращенцев старательно чистившие чрезмерно маленький системный раздел на Win знают о таком интересном инструменте как Symbolic (или Hard) Link. Это по сути возможность создать ссылку, которую система и практически все современные программы будут «считать» реальным файлом или директорией — смогут пройти по ней и читать-редактировать-удалять файл или директорию на которую ссылается ссылка.

Итак, какой план? Вместо калупания конфигурации WP и изменений темы, (http-)ссылок и прочего, мы используем доступный функционал системы (благо апач успешно путешествует по ссылкам, разве что надо будет глянуть, какие угрозы безопасности это может сулить) и сделаем небольшие ссылки на нужную (каждую) нам тему:

ln -d -s /var/www/shared/themes/rambo-child /var/www/dev/wp-content/themes/rambo-child

Что создаст ln символьную -s ссылку dev/wp-content/themes/rambo-child, которая указывает на директорию -d shared/themes/rambo-child.

Проверяем, успех!

Паттерн комманды ln получается таким:

ln <target> <from>

(Мне казалось цель и откуда должны быть наборот, ну да ладно).

Полную справку можно почитать через man ln, а мы, пожалуй, завершаем наше небольшое путешествие.

Recap, to say. Мы (после долгого введения) вынесли необходимую тему в отдельную директорию, доступную апачу, убрали оригинал этой директории из wp-content/themes и создали вместо нее символьную ссылку на директорию, ссылающуюся на созданную нами копию темы.

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

Но на этом, пора прощаться.

Почему вообще Реакт? [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 компоненты), возможность его менять — Диспетчер (функционал по обновлению Состояния) — и в конце-концов Действия, которыми можно представить простые объекты переданные в Диспетчер. Однако, это хоть и удобный порой способ, при практически любой сколь немного сложной компоновке (или не дай Ишвар работы в команде), он быстро перестает быть хоть сколь эффективным.
Тем не менее, скоро мы его разберем, как базис для пробы пород флукса и поиск проблем, почему реакт сам по себе таки не выход.

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