Comments 32

Приятно читать такие статьи, все по делу, без нытья, что фронт это сложно, и js, как всегда, убог.
Рад, что у вас все получилось, так держать!

Спасибо. Говоря честно, все «нытье» и размышления о js при втором проходе по статье я категорически убрал. js с сахаром и таким набором фреймворков становится почти приятным)
Проверяем. И получаем нашу запись в истории подключения, но почему то с запятыми. Javascript, WTF?

Ну так понятное дело. history.map(..).reverse() — это массив, а значение textarea.value должно быть строкой. У массивов же метод toString реализован как, грубо говоря, return this.join(',').

А, вот оно в чем дело! Но я потратил лишний час жизни на стаке разбираясь, почему у меня возникает запятая на фактически ровном месте)
Статья хорошая, но как по мне, прежде всего бэкендеру, для таких задач Redux — это из пушки по воробьям. MobX для таких задач показывает себя лучше как по количеству кода, так и, и это главное, по понятности для бэкендера, впитавшего ООП и прочую MVC-like императивщину, с молоком матери. Сторы там обычные объекты, хоть на верхнем уровне, хоть где угодно, экшенами могут быть методы этих или других объектов (включая методы компонента), а могут быть глобальные функции.
Да, поддерживаю и рекомендую обратить на Mobx внимание.
Есть один момент: не смог найти «расширенный» бойлерплейт, так чтобы сразу в нем был более менее полный интерфейс. Есть подсказки?
Если под интерфейсом имеете в виду UI, то таковых не встречалось. По простой, по-моему, причине — можно подключить любую адаптированную под React UI либу. Как-то так исторически сложилось, что я использую для «админок» Material-UI, хотя всё больше в ней разочаровываюсь (прежде всего из-за инлайн стилей) и смотрю в сторону React-Bootstrap
Да, под интерфейсом имеется ввиду набор преднастроенных технологий и стартовый шаблон с менюшками и пр., чтобы вообще не заниматься этим. Либо заниматься на уровне конфигурирования готового «скелета». У нас же «черновик» интерфейса.
В примере, который взят за основу, используется React-Bootstrap.
Я правильно понимаю, что там генерируются CSS-файлы, а не инлайн стили у элементов используются?
Еще лучше, ничего не генерируется, там используются css-модули, которые позволяют использовать обычный css (с любым процессингом) с инкапсуляцией. Расширение темы компонента происходит через добавление кастомных класснеймов к его собственным по заданным в интерфейсе ключам.
За это отвечает react-css-themr, и, собственно, его можно использовать и без react-toolbox.
Пожалуй без знания основ было бы сложно понять «что тут происходит», но зная основу, практика заходит «на ура». Тут есть неплохой скринкаст (там и по Реакту есть).
Отличное дополнение, для более глубокого погружения и понимания, то что нужно
Вот тут базовом примере есть странная нестыковка, все предлагается писать в одном файле, не разделяя на файл экшенов и редюсеров, ну допустим.

Это фича

Это особенно ценно, занес в TODO.
Кстати читал же про уток, но как-то не придал значения.
Action Types всегда должны лежать в отдельном файле, так как, если у вас связанные сущности, например Tab -> Widget, и по редьюсеру на каждую сущность, файлы с редьюсерами будут циклично референсить друг друга.
Например, при экшене TAB_DELETE нужно зачистить табу и все виджеты, к ней привязанные.
Тема со структурой проекта как оказалось очень важная.
Я правильно понимаю, что под отдельным файлом подразумевается отдельный общий файл со всеми экшенами проекта?

Плюс, а можно ли проиллюстрировать суть замечания каким либо примером. Я просто не могу понять, почему
Action Types всегда должны лежать в отдельном файле

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

Пример:
//actions/types.js
export const TAB_DELETE = 'TAB_DELETE';

//actions/tab.js
import {TAB_DELETE} from './types';
export const tabDelete = id => ({
  type: TAB_DELETE,
  payload: {
    id
  }
});

//reducers/tab.js
import {TAB_DELETE} from '../actions/types';  //no crossreference between tab and widget
export const tab = (state = {}, action) => {
  const {type, payload} = action;
  switch (type) {
    case TAB_DELETE: {
      //delete tab with id === payload.id
      return state;
    }
    default: {
      return state;
    }
  }
};

//recuders/widget.js
import {TAB_DELETE} from '../actions/types'; //no crossreference between widget and tab
export const widget = (state = {}, action) => {
  const {type, payload} = action;
  switch (type) {
    case TAB_DELETE: {
      //delete all widgets with tabId === payload.id
      return state;
    }
    default: {
      return state;
    }
  }
};

Отлично! Спасибо большое. В TODO к статье занес поправить этот кусок.


Действительно у Эрика получается этого в явном виде не выделяется, хотя он пишет в своем походе https://github.com/erikras/ducks-modular-redux:


A module…
  1. MUST export default a function called reducer()
  2. MUST export its action creators as functions
  3. MUST have action types in the form npm-module-or-app/reducer/ACTION_TYPE
  4. MAY export its action types as UPPER_SNAKE_CASE, if an external reducer needs to listen for them, or if it is a published reusable library


These same guidelines are recommended for {actionType, action, reducer} bundles that are shared as reusable Redux libraries.

Вот что он понимает под "такие же правила рекомендованы для actionType, action, reducer" я не понимаю.


С другой стороны в его подходе реально не учитывается вложенность, а это значит что не может быть связанного Tab и Widget, но тогда будет 3 разных объекта Tab, Widget и TabWidget.

С другой стороны в его подходе реально не учитывается вложенность, а это значит что не может быть связанного Tab и Widget, но тогда будет 3 разных объекта Tab, Widget и TabWidget.
Ну это как-то странно совсем. Получается эта методология не помогает решать проблемы, а создает новые. Сущности-то — это табы и виджеты, ладно б у них связь многие ко многим была, я б еще понял, но простую связку хранить в трех модулях только для разрешения циркурлярных зависимостей — мне кажется, это перебор.

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

Про дефолтный экспорт согласен, но это опять вкусовщина.

Поправка, если (и только если следовать обозначенным принципам), то у нас получается один уровень вложенности для модулей, а это приведет нас к тому что будет либо Tab, либо Widget, либо монструозный TabWidget. Короче для всех разновидностей мы будем делать отдельные модули. (Ну это допустим следуя поговорке "Если нельзя, но очень хочется, то можно").


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

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

Ну, большинство приложений — все же CRUD-мордашки к бэкенду, а там без связности/вложенности никуда.

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

А если разные виджеты можно как-то настраивать и сохранять эти настройки в пресеты? А если, а если… Плоская модульная структура начинает трещать по швам.

Тут ключевой вопрос: зачем редюсер виджета отрабатывает экшен таба?


"Кто такой Студебеккер? Это ваш родственник Студебеккер? Папа ваш Студебеккер?" :)

Ну а как вы иначе удалите виджеты при удалении табы? В редьюсере табы полезете в глобальный стейт?
Примеров уйма, на самом деле. Вот лежат у пользователя в настройках айдишники его созданных табов. Жмет он на удаление табы, экшен должен попасть в оба редьюсера. Если, конечно, у вас данные нормализованы (с этого надо было начать, наверное).
Only those users with full accounts are able to leave comments. Log in, please.