ZarahioN Presents

Answering why

Base

Разбираемся с React(Webpack)-ом [Pt. 1 — Manual React Installation (and pain of doing so)]

Что вообще такое React(.js)?

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

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

Начало работы с React.js

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

Первым и уже практически ставшим стандартом, является использование утилиты create-react-app, которая создаст базовую установку для практически мгновенного начала работы со стандартным и привычным набором инструментов.

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

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

Что же нам нужно?

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

Node.js и npm, а для любителей котиков и чуть более упорных людей — еще и yarn

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

node -v
npm -v

И для вышеназванных любителей котиков:

yarn -v

Если все команды успешно выполнились и вывели ожидаемые версии — успех! Первый шаг сделан.

Для чего нам Node.js?

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

Следующим шагом нам нужно установить сам реакт и пару сопутствующих инструментов:

npm(yarn) init
Круглые скобки подсказывают взаимозаменяемость команд или флагов

Это создаст специальный файл package.json, в котором хранится описание, версия и набор необходимых для приложения библиотек. Во время инициализации скорее всего придется ответить на пару вопросов о приложении.

npm i[nstall] -S(--save) react react-dom либо yarn add react react-dom
Квадратные скобки подсказывают полное название команды или флага

Это должно установить библиотеки react и react-dom, обе необходимые для работы реакта в браузере. И технически мы могли бы начать работу (на некоторых версиях реакта) прямо сейчас, используя src="node_modules/react/dist/..." но это не очень удобно и совсем не то, как мы будем в будущем работать.

Следующим шагом будет установка специального инструмента — «переводчика» (transpiler) или же просто компилятора babel. Он нужен главным образом из-за использованного в реакте языка JSX, который представляет собой XML с вписанным в атрибуты обычным JavaScript-ом. О нем мы еще позже подробно поговорим, но просто знай, что он помогает упростить жизнь, переводя более современный и удобный вариант JS в поддерживаемый повсеместно (и в некоторых областях сильно устаревший) JS.

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

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

npm i -D(--save-dev) webpack@3.11.0 babel-loader babel-core babel-preset-react-app
либо
yarn add -D(--dev) webpack@3.11.0 babel-loader babel-core babel-preset-react-app
-D или --dev это флаг для записи библиотек в условный раздел devDependencies нашего package.json. Условный он потому, что для запуска приложения нам не нужны и зависимости из dependencies — они будут собраны в готовый файл в будущем. Однако это полезное разделение на относящиеся непосредственно к работе приложения модули и просто необходимые для сборки (и желательно тестирования) инструменты.

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

Что же заглянем на страницу документации настройки вебпака и.. да…

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

@webpack.config.js
var path = require('path');
var webpack = require('webpack');

module.exports = {
    entry: './index.js',
    output: {
        path: path.resolve(__dirname, 'public'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                loader: 'babel-loader',
                options: {
                    presets: ['react-app']
                }
            }
        ]
    },
    resolve: {
        modules: [path.resolve(__dirname, 'node_modules')],
        extensions: ['.js', '.jsx']
    },
};

Для начала что такое require и module.exports — это функционал по соответственно импорту и экспорту модулей и библиотек. Он начал свой путь с библиотек вроде RequireJS, потом перекочевал в Node.js и JS как расширенная часть языка и наконец был принят в стандарте ES6. (Или как-то так, я не слишком хорошо осведомлен о истории модулей).

Далее непосредственно конфиг:
entry — файл, с которого начинать сборку, с него по сути начинает работу приложение — загружает при необходимости библиотеки (реакт, к примеру) и что-то делает со страницей.
output — путь и название конечного файла (или файлов) в которые соберется весь необходимый код приложения.
module — «логика» (rules) вебпака — какие файлы обрабатывать, через какие загрузчики (loader) их пропускать, какие параметры передать этим загрузчикам и так далее.
— — test — регулярное выражение (как правило) по которому проверять, какие файлы должен обрабатывать этот загрузчик
— — loader — непосредственно название библиотеки загрузчика
— — options — набор параметров, который нужно передать данному загрузчику, каждый загрузчик принимает свой собственный набор параметров
resolve — какие пути (modules) проверять при импорте модулей в коде и какие расширения файлов считать импортируемыми (extensions) таким образом. Это немного своеобразный элемент, так как он (должен) работать только для импорта библиотек, но можно настроить подгрузку своих файлов, чтобы не нужно было прописывать глубоких путей наверх в некоторых случаях. Это своеобразный вопрос конфигурации и я, возможно, разберу его при необходимости в будущем.

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

Но, мы еще не закончили, последний шаг.

Hello World with React.js (and bunch of not so fancy stuff)

Создадим два пустых файла index.html и index.js (который мы указали в entry конфига вебпака). В body html файла нужно добавить две строки:

<div id="root"></div>
<script src="bundle.js"></script>

Элемент div нужен как контейнер (или обертка) для указания реакту, куда вставлять (render) разметку его компонентов. Скрипт же очевидным образом загружает bundle.js, который мы сейчас соберем вебпаком из index.js. Переходя к которому:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

const StateLessComponent = ({text}) =>
<div>
    <span>{text}</span>
</div>;

class StateFullComponent extends Component {
    state = {
        text: 'Heya Earthlings!'
    }
    render(){
        return (
            <StateLessComponent text={this.state.text}/>
        );
    }
}

ReactDOM.render(<StateFullComponent/>, document.getElementById('root'));

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

ReactDOM.render(JSXComponent, DOMNode) — вставляет (монтирует) собранный реакт(jsx)-компонент в указанный элемент DOM-а страницы. То есть, мы находим какой-то элемент (контейнер) на реальной странице и вставляем в него наш компонент (или дерево вложенных компонентов, как в нашем скромном случае).

class StateFullComponent extends Component — это ES6 синтаксис-обертка для создания функции-конструктора и по совместительству в реалиях реакта — полноценный компонент.
Полноценный означает что он может, помимо простого отображения разметки (метод render), иметь некую логику (свои методы\функции), иметь состояние (state) и использовать некоторые «события» реакта. (lifecycle hooks, технически это не события, но тем не менее, практическая суть достаточно близка).
О всех этих несомненно интересных словах мы поговорим, уже разбирая непосредственно внутренности реакта.

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

const StateLessComponent = ({text}) => <div>...

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

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

Последние строки:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

Это уже названный выше функционал модулей в ES6 версии JS — он немного отличается от использованного в конфиге вебпака из-за того, что даже Node.js еще не начал полноценное использование этого стандарта.
(Насколько я знаю*. Достаточно скоро или уже Node.js может полностью перейти к этому стандарту, но до тех пор там используется CommonJS версия импорта и экспорта — require() и module.)

Помимо синтаксического отличия я использовал именованный импорт — он позволяет запросить только нужные части из модуля, без всего модуля.
И опять же требуется пометка — насколько я знаю, на данный момент для такого (реально) частичного импорта нужно использовать «обрезание» кода и дерева модулей — «dead code elimination» и «tree shaking», по этим словам ты скорее всего найдешь информацию, если тебя интересует вопрос оптимизации размеров твоего приложения. Я же, возможно, в будущем сам расскажу подробнее про эти возможности, но учитывая какой уровень дополнительной сложности вносит только базовый вебпак, оптимизация — это разговор на другой раз.

Поэтому мы наконец завершаем наше затянувшееся путешествие, во время которого мы успели
1. [Поставить Node.js, npm и, опционально, но мягко рекомендуемо, yarn.](#Что же нам нужно?)
2. Создать пустой проект через yarn init и добавить в него реакт через yarn add
3. Немного разобрать что есть babel и webpack и зачем они нужны
4. [И даже написать первые строки приложения на реакте](#Hello World with React.js (and bunch of not so fancy stuff))

На этом я ненадолго оставляю тебя с, надеюсь, немного подкипевшей головой и списком материала на вечернее чтение:
npm, yarn, webpack, babel и сам реакт
Это сайты документации или помощи инструментов, которыми мы будем очень часто пользоваться на пути создания реакт приложений и я, как бы не хотел, не смогу ответить на все вопросы — иногда придется расчехлить навыки поиска и покопаться в официальных источниках (ну или заглянуть на stackoverflow\подобные) в поисках нужного ответа.

Благость под названием create-react-app [Pt.2 – Automatic React Installation (and how to break it)]

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

create-react-app

Что же это? По факту cli — command line interface для создания базового шаблона стандартного приложения на реакте.

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

Почему не сразу? Все просто — чтобы научиться что-то чинить, нужно или это что-то сломать, или построить свой вариант. Ломать это, конечно, весело, но без хоть какого-либо понимания, процесс обратной починки может затянуться, а то и вовсе по факту не начаться. Если у тебя что-то сломается при использовании шаблона созданного c-r-a — ты хотя бы примерно сможешь ориентироваться в каком из узлов всей цепочки сборки и компиляции это произошло и где искать помощи.

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

Впрочем, завершим затянувшееся лирическое отступление и приступим к делу:

Работа с create-react-app

Есть два пути: старый-добрый-глобальный, если у тебя npm ниже версии 5.2, и новый-модный npx, если выше. Отличаются они в одну строчку и кучку деталей (про которые можно почитать здесь(англ) или здесь(рус)), поэтому посмотрим оба (, а я таки обновлю свою стареющую ноду 8.5.0 версии (хотя это делать оказалось необязательно)).

Классический способ это установка cli в глобальную директорию npm:

npm i -g create-react-app

После чего можно использовать: (если это первая глобальная установка, возможно придется добавить путь в PATH на Win или в /bin (или куда нужно) на *nix системах)

create-react-app app-folder-name

Это создаст проект в «app-folder-name» по текущему пути консоли.

Модный способ это использование вышеназванного npx, который поставляется с 5.2 версий npm:

npm install npm@latest -g

Это обновит npm, на случай, если ты хочешь использовать npx, но npm уже старенький. Напоминаю, что проверить версии можно с помощью npm(node) -v

npx create-react-app app-folder-name

Это опять же создаст проект в «app-folder-name» по текущему пути консоли.

Удобство npx состоит в том, что он позволяет как запускать локально установленные версии инструментов (модулей), так и использовать «одноразовые» инструменты вроде create-react-app без их установки в глобальную директорию. Что в последнем случае очень удобно — не приходится следить за версией названных инструментов.

И.. собственно все, в этот мы справились куда быстрее, не так ли? Мы можем запустить вебпак через yarn start и начать создавать!

Но это скучно.

Поэтому, приготовься покинуть кабину, мы катапультируемся!

(Я знаю, что eject это не совсем катапультироваться, но тем не менее)

create-react-app это очень базовый, стандартизированный и проверенный способ начинать работу с реактом, поэтому в нем нет всех тех прикольных штук, которые тебе возможно захочется использовать. К примеру последний писк моды и ES8 с компиляцией от бейбла, или немногословный и немножко страшный, но такой удобный stylus, или тебе просто, как и мне, интересно поковыряться и узнать как они все это настроили.

Поэтому без промедления (и находясь в папке с созданным проектом):

yarn eject

Ах, сколько новых папочек и файлов

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

Что же, разберемся по-подробнее с появившимся разнообразием конфигурационных файлов. В особенности меня сейчас интересует configs/webpack.config.dev.js — он содержит конфигурацию для — yarn start — процесса разработки, а ближайшее время мы будем заниматься только им.
К слову, в нем достаточно много подробных комментариев, так что приглашаю заглянуть в него вместе, не дожидаясь моих пояснений.
Также помимо самого файла конфигурации, может быть полезно заглянуть в configs/paths.js — он содержит распознаватель путей и непосредственно их самих — полезно примерно прикидывать что куда указывает.

module.exports = {
  entry: [
    require.resolve('react-dev-utils/webpackHotDevClient'),
    paths.appIndexJs,
  ],
  output: {
    pathinfo: true,
    filename: 'static/js/bundle.js',
    chunkFilename: 'static/js/[name].chunk.js',
    publicPath: '/',
  },
  resolve: {
    modules: ['node_modules', paths.appNodeModules].concat(
      process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
    ),
    extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'],
  },
  module: {
    strictExportPresence: true,
    rules: [
      {
        test: /\.(js|jsx|mjs)$/,
        enforce: 'pre',
        use: [
          {
            options: {
              formatter: eslintFormatter,
              eslintPath: require.resolve('eslint'),
            },
            loader: require.resolve('eslint-loader'),
          },
        ],
        include: paths.appSrc,
      },
      {
        oneOf: [
          {
            test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
            loader: require.resolve('url-loader'),
            options: {
              limit: 10000,
              name: 'static/media/[name].[hash:8].[ext]',
            },
          },
          {
            test: /\.(js|jsx|mjs)$/,
            include: paths.appSrc,
            loader: require.resolve('babel-loader'),
            options: {
              cacheDirectory: true,
            },
          },
          {
            test: /\.css$/,
            use: [
              require.resolve('style-loader'),
              {
                loader: require.resolve('css-loader'),
                options: {
                  importLoaders: 1,
                },
              },
              {
                loader: require.resolve('postcss-loader'),
                options: {
                  ident: 'postcss',
                  plugins: () => [
                    require('postcss-flexbugs-fixes'),
                    autoprefixer({ ... }),
                  ],
                },
              },
            ],
          },
          {
            exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
            loader: require.resolve('file-loader'),
            options: {
              name: 'static/media/[name].[hash:8].[ext]',
            },
          },
        ],
      },
    ],
  },
};

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

Как можно сразу заметить, entry и output слегка отличаются от нашего самопала, однако большей частью это лишь косметические изменения:

entry может принимать не единственную строку, как в нашем случае, а массив или даже объект строк (и массивов, вложенность, ура), в данном случае помимо нашего приложения вебпак просто обрабатывает сервер для разработки и набор заполнителей выбранный c-r-a; output же использует несколько доп опций:
pathinfo — включает в конечную сборку данные о путях, модулях и прочем,
publicPath — позволяет указать корень выхода — папку, в которую помещаются все сгенерированные файлы, мы не использовали его, так как наш проект содержал всего один файл
chunkFilename — указывает как называть чанки — куски кода оптимизированные для отдельной загрузки и кеширования.

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

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

С загрузчиками, впрочем, все достаточно просто и знакомо (спасибо вебпаку за стандартизацию конфигурации):
Первым идет eslint — линтер для JS. Линтер — это «инструмент статического анализа» — или говоря по-человечьи, подсветка ошибок и несоответствий выбранному набору правил написания кода. Очень полезный инструмент, который, что особенно приятно, как правило доступен в виде расширения для редактора.
При этом загрузчик линтера использует специальный модификатор enforce — он нужен, чтобы линтер всегда обрабатывал исходный код до его преобразования бейблом. Модификатор (параметр) enforce задает категорию порядка применения загрузчика, всего доступно 4 опции (pre, inline, normal, post), о которых можно почитать здесь.

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

На чем наш расковыряный файл конфигурации заканчивается. Я, опять же, мягко указываю тебе покопаться в конфигурации самостоятельно и, к примеру, добавить поддержку любимого css препроцессора или пресета для бейбла. Процесс достаточно простой, и я буду разбирать его в будущем, когда приложению понадобится стилизация (а делать ее на css или тем более встроенных стилях я отказываюсь), но всегда полезно попробовать самостоятельно (имея бекап), прежде чем идти читать руководство.
Также должен заметить, что учитывая базовость и частое нежелание катапультироваться из c-r-a, создали кучку способов изменения или расширения конфигурации, оставаясь технически с c-r-a. Но я сам больше склоняюсь к eject-у, нежели использованию таких инструментов.

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

Первые серьезные строки на React.js [Pt.3 – Finally starting for real]

В прошлых монотонных рассказах мы занимались долгой и утомительной настройкой вебпака и вообще «среды» для создания React приложений.

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

Впрочем, начнем с азов, затронутых еще в первом рассказе. Заглянем в файл index.js, он скорее всего находится в папке src, если ты продолжаешь с пути c-r-a, либо прямо в корне проекта, если решил в лоб принять удар тяжелой и героической самостоятельной настройки вебпака.

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

Для простоты подхода (и соблюдения традиций) мы начнем с глупых — stateless — компонентов.
На данный момент существует 3 способа их создания, впрочем последний (и исторически первый) через React.createClass мы не будем разбирать как технически устаревающий (и не зря же мы проходили через все страдания вебпака и бейбла)

class X extends React.Component

import React, { Component } from 'react'; 
import ReactDOM from 'react-dom'; 

class ColoredDiv extends React.Component {
    render() {
        const {color, fontSize: fz} = this.props;
        return <div style={{background: this.props.bgColor, color, fontSize: fz}}>Me colored, yay!</div>
    }
}

ReactDOM.render(<ColoredDiv bgColor="green" color="red" fontSize="14px"/>, document.getElementById('root'));

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

Разберем же наш простенький код по полочкам:
import X from 'x'; — механизм загрузки и сборки модулей, для которого мы и используем вебпак, так как это до сих пор достаточно далекий от браузерного стандарта функционал. Как можно правильно догадаться, import React from 'react' дает доступ к функционалу реакта посредством константы React (также любой импорт говорит вебпаку загрузить указанную библиотеку, что бывает важно в некоторых случаях).
class X extends React.Component — синтаксис для создания «класса» в ES6, при этом мы наследуем от базового класса реакта (потому что надо).
render() это метод нашего класса, который будет использовать реакт для создания разметки компонента — ее рендера.

this.props это специальный неизменяемый объект (свойство\параметр), в который реакт помещает переданные компоненту свойства (prop(ertie)s).

Непосредственно процесс передачи можно увидеть глянув на последнюю строчку — ReactDOM.render. Первым аргументом для него идет JSX разметка (технически возврат React.createElement, но это ненужные детали). Мы использовали имя созданного компонента (класса) и вставили несколько атрибутов: bgColor, color, fontSize эти атрибуты и попадают в объект props компонента.

Как можно заметить, в рендере самого компонента мы возвращаем JSX разметку подобную той, что передавали в ReactDOM.render (наблюдаешь связь?). И как можно заметить, фигурные скобки в атрибутах служат для передачи JS значений, включая объекты как в style (отчего там получились по две фигурные скобки, внешние для обозначения компилятору что идет JS код, внутренние уже для самого объекта), так и просто строки или числа.

Также я использовал сразу три способа использования переменных из свойств (props): доступ напрямую this.props.bgColor, и деструктуризации {color} = this.props и {fontSize: fz} = this.props, третья отличается от второй лишь тем, что мы переименовали переменную перед использованием. Как правило используется второй и третий тип, так как это позволяет сразу в начале рендера указать, какие свойства используются в компоненте.

Как можно догадаться, фигурные скобки позволяют использовать JS не только в атрибутах:

render() {
    const {color, fontSize: fz } = this.props;
    return (<div style={{background: this.props.bgColor, color, fontSize: fz}}>
        {Math.random() >= 0.5 ? 'Me colored, yay!' : 'Me no random!'}
    </div>)
}

Помимо скромных забав со случайными числами таким образом можно использовать любой JS код, который возвращает значения (строки, числа, элементы реакта (JSX) или массивы перечисленных типов), к повсеместно используемому примеру, Array.map:

class ColoredDiv extends React.Component{
    render() {
        const {color, fontSize: fz, rows } = this.props;
        return (<div style={{background: this.props.bgColor, color, fontSize: fz}}>
            {rows.map(row => <li>{row}</li>)}
        </div>);
    }
}

ReactDOM.render(<ColoredDiv 
    color="#ab00ff" 
    bgColor="white" 
    fontSize="14px" 
    rows={[
        'Me colored, yay!', 
        'Me colored, nope second try!', 
        'No random is it!'
    ]}
/>, document.getElementById('root'));

Как можно ожидать (или нет, с непривычки) — ColoredDiv выведет уже знакомый стилизованный div и li со строками из массива переданного через свойство rows (ReactDOM.render).
Если ты еще не ознакомился с функциональными методами массивов, постарайся разобраться хотя бы с минимальным набором map, filter, reduce, и опционально forEach, последний технически идентичен map, просто не возвращает значений. Но особенно непредсказуемо полезным может оказаться reduce. А первые два ты скорее всего будешь использовать повсеместно, работая с реактом (и очень вероятно без него тоже).

Помимо бездумного разглядывания кода, я предлагаю тебе самостоятельно побаловаться с нашим скромным примером, использовав все три перечисленных типа данных доступных для вывода, и в целом привыкнуть к подобию JSX на HTML и его отличительным особенностям, которые позволяют творить (иногда к сожалению) что угодно за счет вставки чистого JS кода в разметку. Особенно интересно становится, когда ты заканчиваешь играть со стандартными HTML елементами и начинаешь компоновать разметку компонентами реакта.

Итак, что мы успели сделать:
1. Создали класс-компонент ColoredDiv через class ColoredDiv extends React.Component { ... },
2. Добавили в него метод render() { ... } который возвращает JSX разметку (<div style={{ ... }}>...</div>),
3. Отрендерили (или правильнее сказать монтировали (mount)) созданный компонент посредством ReactDOM.render(<ColoredDiv ... />, realDOMElem),
4. Передали свойства (props) компоненту ColoredDiv через ReactDOM.render и использовали их.

Базовое знакомство с работой реакта? Завершено.

Хоть это и скучно, но на сейчас почти хватит, осталось только глянуть второй способ записи глупых компонентов, о котором я говорил ранее:

const X = (props) => …

Как можно заметить, имея опыт с ES6, это просто «стрелочная» (arrow) функция. Такой тип записи компонентов называется функциональным (duh!) и является более простой и удобной заменой относительно громоздкому классу. К слову, использовать стандартные функции теоретически тоже можно, но лично я такое не практикую.

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

const ColoredDiv = ({color, fontSize: fz, rows, ...props }) => 
<div style={{background: props.bgColor, color, fontSize: fz}}>
    {rows.map(row => <li>{row}</li>)}
</div>

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

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

Почему вообще Реакт? [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. 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. Надеюсь, потискали получившуюся форму)

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

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

Реакт, Флукс, Состояние, весело [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. 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..

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

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

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

Первое настоящее приложение с 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-ое-приложение.

Следующая остановка, 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), который получает созданные нами Действия и согласно нашим указаниям возвращает новое состояние (в том числе возвращая исходное состояние, если изменений не требуется).

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

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