Комментарии 32
Приятно читать такие статьи, все по делу, без нытья, что фронт это сложно, и js, как всегда, убог.
Рад, что у вас все получилось, так держать!
Проверяем. И получаем нашу запись в истории подключения, но почему то с запятыми. Javascript, WTF?
Ну так понятное дело. history.map(..).reverse()
— это массив, а значение textarea.value
должно быть строкой. У массивов же метод toString
реализован как, грубо говоря, return this.join(',')
.
Есть один момент: не смог найти «расширенный» бойлерплейт, так чтобы сразу в нем был более менее полный интерфейс. Есть подсказки?
В примере, который взят за основу, используется React-Bootstrap.
За это отвечает react-css-themr, и, собственно, его можно использовать и без
react-toolbox
.базовое знание javascript (тут нужно поискать в интернете справочник по крайней версии js стандартов ES-2015)
Лучшее, что я видел — Обзор базовых возможностей ES6
Кстати читал же про уток, но как-то не придал значения.
Например, при экшене 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…
- MUST export default a function called reducer()
- MUST export its action creators as functions
- MUST have action types in the form npm-module-or-app/reducer/ACTION_TYPE
- 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 только хардкор.
Возьмем те же табы, всегда можно пойти дальше. Допустим, табы можно добавлять, удалять, переименовывать и вообще творить с ними всякое безобразие, да хоть между вкладками перетаскивать. То есть присутствует некий лэйаут или воркспейс, который хранится у пользователя в настройках.
Вот и вложенность нарисовалась.
А если разные виджеты можно как-то настраивать и сохранять эти настройки в пресеты? А если, а если… Плоская модульная структура начинает трещать по швам.
Тут ключевой вопрос: зачем редюсер виджета отрабатывает экшен таба?
"Кто такой Студебеккер? Это ваш родственник Студебеккер? Папа ваш Студебеккер?" :)
Примеров уйма, на самом деле. Вот лежат у пользователя в настройках айдишники его созданных табов. Жмет он на удаление табы, экшен должен попасть в оба редьюсера. Если, конечно, у вас данные нормализованы (с этого надо было начать, наверное).
Быстрый курс Redux + websockets для бэкендера