Комментарии 36
теперь при помощи count.increment мы можем создать генератор действий напрямую из редьюсера.
Идея прямо скажем противоположная тому что предлагает redux. Скорее всего даже вредная.
На мой взгляд основная проблема redux имеет в основе основную проблему react а именно синхронность. В результате кому не знакома такая ситуация. Мы рендерим компонент. При этом в событии жизненного цикла запрашиваем данные с сервера. Далее компонент рендерится с пустым стором (для того чтобы это не вылетало в ошибку нужно дополнительно всяких условий накидать или синхронно инициализировать стор пустыми значениями. Если кто как делает по-другому то прошу поделиться опытом). Потом по получении данных с сервера происходит повторный рендер.
Я честно говоря нашел один хук куда внедрил асинхронность — это как ни странно в компонент Link. Это избавило меня от необходимости в этих неестественных проверках стора на непустоту и псевдо-инициализаций стора, а пользователя в наблюдении прелоадеров. См. например вариант приложения real-world realworld-react-universal-hot-iltreezyct.now.sh/author/Apa%20Pacy
Но в этом году все должно измениться. См. доклад Дэна Абрамова habrahabr.ru/post/350816 с русскими субтитрами. Вобщем-то значение redux (в современном виде) после этого немного становится туманным.
Идея прямо скажем противоположная тому что предлагает redux. Скорее всего даже вредная.
Мне вот всегда было интересно. Допустим, можно реально генерировать AC из редюсеров (если нам правда хватает этой информации в редюсерах), то в чем вредность такой генерации? Зачем писать дополнительный код, который пишется копипастой, если уже написано достаточно кода и на его базе можно сгенерировать всё необходимое? Так почему писать меньше ненужного кода — вредно?
Генерировать «действия» в редьюсере мне кажется нелогичным.
Давайте посмотрим что из себя представляет редьюсер. Я честно говоря не в восторге от терминологии redux возможно в этом причина, что многим эта технология не по душе. (Мне тоже кстати была пока я не почитал курс Максима Пацианского см. habrahabr.ru/post/351046 )
Редьюсер это собственно функция которая вызывается при выполнении каждого действия.
Она может вернуть тот же самый объект и тогда ни один компонент не будет перерисован. Либо может вернуть новый объект. Тогда все компоненты которые слушают изменение состояния будут обновлены. Таких редьюсеров обычно несколько в одном приложении.
Предназначение и функция редьюсера это просто вернуть старое или обновленное состояние объекта. Вызов каждого действия вызывает все функции-редьюсеры но «срабатывает» только нужная из них. При этом гарантируется что сналачало инициируется действие. Потом вызываются редбюсеры. Потом опять действие. потом опять редьюсеры и т.д. То есть образуется своеобразная цепь переходов их одного состояние в другое.
Что если из редьюсера начать вызывать действия? Синхронные или асинхронные не так уж важно. Это же еще не пробежалась система по всем редьюсерам. Это все компоненты которые слушают редьюсеры не обработали обновленное состояние стора. И уже пошел вызов нового действия. Фактически мы входим в рекурсию при этом. И все может стать настолько заполненным действиями, что до рендеринга обновленного состояния стора и дело не дойдет.
Не понял что такое АС если можно расшифруйте.
ActionCreator
Что если из редьюсера начать вызывать действия? [...] Фактически мы входим в рекурсию при этом
Простите, не путайте «вызывать действия из редьюсера» и «генерировать ActionCreator на базе редьюсера». Так как вы спутали эти две вещи — остальные ваши слова не имеют смысла. Вот у вас есть типичный экшн:
function apologize(fromPerson, toPerson, error) {
return {
type: 'APOLOGIZE',
fromPerson,
toPerson,
error
};
}
Какой в нем смысл? Такие функции должны стать рутинной работой генератора, а не копипаститься.
dispatch({
type: 'APOLOGIZE',
fromPerson,
toPerson,
error
};)
Один вопрос. Что делать, если, скажем, в эффекте необходимо менять не один стор (не знаю, как еще такой блок назвать), а несколько?
Кстати, я вот подумал. Можно пойти дальше и убрать редюсеры (они ведь тут остались как ФП ради ФП), изменять стейт в effects, а так как они остались одни — вынести на уровень выше. Получится, внезапно, MobX!
export class Count {
state: 0, // initial state
@action increment (delta) {
this.state += delta
}
async incrementAsync(delta) {
await new Promise(resolve => setTimeout(resolve, 1000))
this.increment(delta);
}
}
Условно:
const CALC_ACTION = 'CALC_ACTION';
const calcActionCreator = (a, b, c) => { type: CALC_ACTION, payload :{a, b, c }};
const calc = (arg, a, b, c) => { x: arg.x +a, y: arg.y * b, z: c}
const INITIAL_STATE = {x: 3, y: 4: z: 5 };
const reducer = (state = INITIAL_STATE, action) => {
// ...
case SOME_ACTION:
return {...state, ...calc(state, ...action.payload))};
// ...
};
dispatch(calcActionCreator({a: 6, b: 9, c: 10});
Как по мне, то достаточно много копипасты, и это всё в одном файле, без импортов — с ними ещё больше.
Например redux-restate может взять два стора на вход, и выдать один стор на выход (только для react «части»), а redux-loop может по некому redux action вызвать что-то из context текущего компонента, что может в итоге вызвать dispatch в сторе родительсткого приложения.
Мы сейчас используем и первое и второе чтобы как-то связать вроде бы два независимых приложения с независимыми сторами, вложенные одно в другое.
Каждый раз когда кто-то начинает говорить о недостатках redux, у меня возникает вопрос — почему не использовать уже mobx, черт возьми.
Мы занимаемся разработкой и поддержкой крупных проектов. В своё время в ужасе убежали с Redux на MobX + наша библиотека github.com/wearevolt/mobx-model которая позволяет держать в памяти модели-сторы с перекрёстными связями. Это позволяет легко загружать и обрабатывать сложные графы десятков видов моделей, и при этом не сходить с ума.
Прелесть MobX, что не надо знать. граф зависимостей под капотом вычисляется и перевычисляется автоматически.
Вроде как нигде не нужно знать, и везде всё вычисляется автоматически. Вопросы возникают когда "что-то пошло не так", стектрейс прямиком из ада, в проекте цепочки синхронно-асинхронных-отложенных зависимых computed-значений (этакая матрёшка в много разнородных слоёв). Последовательность вычислений начинает сильно зависеть от конкретных кликов мыши и др. нюансов. И в итоге клубок собирается по-разному. И даже воспроизведя проблему ты не знаешь, как это повторить заново. У меня был такой опыт (KnockoutJS), неоднократно.
Я пытаясь уйти от копипасты пришёл к такой схеме:
const A = actionFactory('PREFIX');
export const aExpandTreeItem = A.create('EXPAND', 'id');
export const aSomeAnotherAction = A.create('SOME', 'arg1, arg2, arg3');
// === (arg1, arg2, arg3) => ({ type: `PREFIX_SOME`, arg1, arg2, arg3 });
const map =
{
[aExpandTreeItem]: (state, { id }) => { /* ... */ },
[aSomeAnotherAction]: (state, { arg1, arg2, arg3 }) => { /* ... */ },
};
Но дубляжа осталось всё равно много, просто он стал компактнее. Дублируются поля в action-ах дважды, дублируются имена action-ов четырежды (export, import, A.create, map). Меньше помощи от IDE за счёт строк (правда мне ST3 итак не помогает)
Потенциально можно убрать actionCreator-ы совсем, генерируя их из map-ы. Но это терпит крах когда нам нужны actionCreator-ы, которые являются композицией над другими и когда нужны async-actionCreator-ы. Да и вообще не всегда хочется держать список доступных action-ов там же, где и их reducer.
Сложилось впечатление, что пока мы держим action-ы и reducer-ы отдельно, и хотим какой-то надёжности на уровне разруливания зависимостей в import-export — копипасты будет много.
Круто и удобно для клиентсайда.
Круто и удобно для старого доброго синхронного серверсайда.
Вообще никак не работает с новым кленовым асинхронным SSR. Потому что стор не паралелиться.
Выход тут только один, и многие по нему идут – один рендер == 1 процесс.
Прощай nodejs, привет Apache prefork!
— старый добрый MVC, где все будет готово до «реакта», он сам будет renderToString
— микс, где мы используем Реакт чтобы инициализировать данные, а потом renderToString
— «новый», с renderToStream и «suspence» под капотом.
Асинхронность на сервер-сайде, и использованием lifecycle Реакта — нычне стало легко и удобно. Но любой глобал это дело пресекает на корню.
Асинхронность на сервер-сайде, и использованием lifecycle Реакта — нычне стало легко и удобно. Но любой глобал это дело пресекает на корню.
Почему пресекает?
«Обычный» redux поддерживает это из коробки. Rematch, react-copy-write и другие — этот принцип ломают.
Потому что две разные страницы будут шарить один глобальных «store», в то время как должны быть полностью изолированы друг от друга.
Так смысл редакса и состоит в том, чтобы был один глобальный стор, который шарится. Как вы в редаксе обеспечите изоляцию, если отсутствие расшаренного стора = не-редакс?
И в чем таки проблемы с асинхронностью при наличии глобального расшаренного стора? Смысл асинхронности же как раз и состоит в том, что ей глобальные объекты не могут мешать.
Еще есть 100500 различных redux обвязок, которые берут этот «стор», что-то с ним делают, и кладут обратно. Простейщий пример — habr.com/post/346116, и таких десятки, на все-все случаи в жизни.
Тот «стор» с которым работать можно, лежит в контексте и для каждого инстанса свой.
То есть, это уже не редакс? И как вы решаете задачи синхронизации разных сторов? Их же надо в итоге потом в один свести, а там будут противоречивые варианты одних и тех же данных. Как выбираете корректные данные?
И так и непонятно в чем проблемы с асинхронностью-то. Ну есть у вас один глобальный стор, и?
Совершенствуем Redux