Комментарии 38
Да, конечно будет кучу памяти отжирать. Но гигабайт данных отправлять в браузер одним куском, без пагинации? Тут у JS начнутся проблемы, не только у редакса. Это особенный случай, и решение понадобится тоже особое.
Если честно, не уверен, что это получится быстро даже на голом DOM. Обычно, когда работают с настолько большими датасетами, используют виртуализацию (react-virtualized, react-window).
> Т.е. памяти страница отожрет в 2 раза больше как минимум
Отожрёт если с прозрачностью ссылок что-то пойдёт не так, и только на отображаемую сущность.
> Еще наверняка есть какие-то обертки для каждой строки, которые слушают изменения в сторе
Нету. Стор модифицируется запуском редьюсера в ответ на action, который прокинули в систему (да, тут нюанс — запустятся все редьюсеры, если говорить о классических редьюсерах на switch/case). Дальше работает shallow comparison (проверяется ссылочная эквивалентность данных), чтобы избежать лишних ре-рендеров.
> Плюс журналируются изменные стэйты
Журналируются только action (обычный JS объект со строковым свойством type и вашей информацией), и только когда был подключён Redux Dev Tools или схожий middleware.
UPD: redux, кстати, только хранит данные, а уж как их отображать — не его задача. Вы можете работать хоть с VanillaJS и использовать redux, главное — подписаться на стор и адекватно реагировать на его изменения.
гигантской инвалидной каляской
интересно, чем обусловлено такое мнение?
1) Необходимость держать стейт в нормализованном виде из-за иммутабельности. Иммутабельное обновление данных подразумевает, что все вложенные объекты тоже должны быть скопированы, что будет триггерить перерисовку компонентов, данные в которых остались по факту теми же. Об этом написано в документации. Проблема решается нормализацией данных, что только добавляет головной боли — придётся нормализовывать данные с бекенда перед вставкой в стор и денормализовывать обратно перед отправкой на сервер. Получается ORM на фронте, с Mobx это не нужно.
2) Для мемоизации нужно писать селекторы с ручным указанием зависимостей. Допустим у вас есть страница с товарами и кнопка «Load more», которая запрашивает товары с сервера пачками по N штук. Кнопка должна пропасть, если страниц с товарами больше нет. Это произойдёт когда количество загруженных товаров на странице станет равно общему количеству товаров на бекенде.
Код на reselect будет выглядеть так:
export const getProductPage = (state: RootState) => state.productPage;
export const isLastPage = createSelector(
createSelector(getProductPage, page => page.products),
createSelector(getProductPage, page => page.totalCount),
(products, totalCount) => products.length === totalCount
);
Код на Mobx будет выглядеть так:
@computed get isLastPage() {
return this.products.length === this.totalCount;
}
Не нравятся декораторы — используйте функции. Декораторы inject и observer больше не нужны с выходом хуков. Mobx будет автоматически пересчитывать значение isLastPage когда хотя бы одна из зависимостей (products или totalCount) изменится.
Вычисления в селекторах на практике оказываются более сложными — например таблица с множественными аггрегациями по определённым полям и строкам. В таких случаях пересчитывания на каждый рендер компонента могут ухудшить UX. Для этих целей придумали мемоизацию и в Redux это делается очень многословно.
Декораторы inject и observer больше не нужны с выходом хуков.
Страница, на которую вы дали ссылку, рассказывает только про inject, который в mobx-react изначально был пятым колесом в телеге. Декоратор observer всё ещё нужен.
Хук useObserver лучше не использовать в качестве основного решения. Даже там, куда вы дали ссылку, про него написано так:
Low level implementation used internally by observer HOC and Observer component.
И ещё вот так:
Despite using useObserver hook in the middle of the component, it will re-render a whole component on change of the observable. If you want to micro-manage renders, feel free to use <Observer />
Despite using useObserver hook in the middle of the component, it will re-render a whole component on change of the observable.
Классовый компонент с декоратором observer будет работать так же — компонент будет полностью перерисовываться когда хотя бы одно из observable значений в render методе поменяется. Если хотим меньше перерисовок — либо разбиваем компонент на компоненты поменьше (тогда перерисовываться будут только дочерние), либо используем <Observer />. На практике мне всегда хватало первого варианта.
Декоратор observer всё ещё нужен.
Я не точно выразился. Имел в виду, что не обязательно прописывать experimentalDecorators в tsconfig, Mobx может использоваться и без декораторов:
export const Counter = observer(() => {
return (
<div>
<span>{counter.count}</span>
<button onClick={counter.inc}>Increment</button>
</div>
)
})
Работает так же, как и старый пример с декоратором:
@observer
export class Counter {
render() {
<div>
<span>{counter.count}</span>
<button onClick={counter.inc}>Increment</button>
</div>
}
}
Хуки же позволяют полностью переключиться с классовых компонентов на функциональные. Уже давно перестал использовать декораторы observer и inject, проблем в продакшене не было.
Если использовать хуки Redux получится лаконичнее:
import { useSelector } from 'react-redux';
const isLastPage = useSelector(
({ page }) => page.products.length === page.totalCount
);
const getVisibilityFilter = state => state.visibilityFilter;
const getTodos = state => state.todos;
export const getVisibleTodos = createSelector(
[getVisibilityFilter, getTodos],
(visibilityFilter, todos) => {
switch (visibilityFilter) {
case 'SHOW_ALL':
return todos;
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed);
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed);
}
}
);
const getKeyword = state => state.keyword;
const getVisibleTodosFilteredByKeyword = createSelector(
[getVisibleTodos, getKeyword],
(visibleTodos, keyword) =>
visibleTodos.filter(todo => todo.text.includes(keyword))
);
На Mobx это будет выглядеть так:
@computed get visibleTodos() {
switch (this.visibilityFilter) {
case 'SHOW_ALL':
return this.todos;
case 'SHOW_COMPLETED':
return this.todos.filter(t => t.completed);
case 'SHOW_ACTIVE':
return this.todos.filter(t => !t.completed);
}
}
@computed get visibleTodosFilteredByKeyword() {
return this.visibleTodos.filter(todo => todo.text.includes(this.keyword));
}
Мало того, что пример на Mobx объективно меньше и проще, так он ещё удобнее для рефакторинга — зависимости селектора не нужно прописывать вручную, а значит если нужно добавить ещё одну переменную на основе которой вычисляется значение, то мы добавим её в одном месте, а не в двух.
Господа минусующие, потрудитесь объяснить в чём я не прав. Ставить минусы исподтишка много ума не надо.
Да всё проще же. Просто кто-то с комментарием несогласен. Такое бывает с любыми комментариями. Но это не повод обвинять других пользователей в глупости.
Зачем приводить пример из mobx если статья рассказывает про инструмент для redux? Я вообще не увидел в статье использования селекторов. Есть упоминание что toolkit содержит реселект, но каждый может подключить любую другую библиотеку для создания селекторов, которая больше нравится.
Не вижу смысла в коментариях выше и в своих тоже. :)
Чтобы было с чем сравнить, разве нет?
Если бы статья называлась "Сравниваем mobx и redux", то да.
Если бы статья называлась "Сравниваем mobx и redux", то комментарий как раз был бы не нужен, сравнение было бы уже в статье.
То есть в статье автор рассматривает инструмент, который помогает разработчику redux, а комментатор приводит пример кода на mobx в котором без всяких инструментов можно получить хороший результат. Логично стремление автора коментария показать, что он умеет лучше. Но не логично что он делает это под статьей про инструмент, а не библиотеку полностью или же что-то похожее. За это он получил минус, это мое мнение, ваше мнение, что он получил минус потому что кто-то не согласен с ним, что вполне может быть. Предлагаю на этом закончить. С Новым Годом!
В react/redux стеке очень не хватает единообразия. Буквально на каждом уровне (компоненты/общий стор) по-своему организовывается кэширование и отслеживание изменений, а также хранение состояния и вычислимых свойств на его основе. Часто с помощью инородных сторонних библиотек типа reselect. Аналогично и для изменений — синхронные мутации встроены, асинхронные — через сторонние библиотеки вроде Thunk или Saga. Столько всего нагородили, а способа увидеть состояние приложения в целом — нет.
Такое понятие “мутаторы” к Redux не относится.
Низкая популярность прежде всего связана с тем, что релиз библиотеки версии 1.0 состоялся всего 2 месяца назад.
Redux toolkit как раз и призван решить проблемы выбора подходящих средств из всего разнообразия в типичных случаях использования.
В большинстве случаев необходимо использовать прямую выборку из стэйта. Предпочтительное использование мемоизации заключается не в получении сложных выборок, а кэшировании комплексных, часто запрашиваемых данных в случае, если начинает провисать производительность. По поводу производительности и ее преждевременной оптимизации можно ознакомиться со следующей статьей: optimization guide.
Стор хранит нормализованные данные, данные можно нормализовывать как угодно до их попадания в стор. А после пользоваться ими как удобно. Селекторы модифицируют изъятые данные в конкретном месте, а Redux DevTools может это отображать как цельное состояние, это и имелось ввиду.
Стор хранит нормализованные данные, допустим, авторов и их книги с связями. Допустим, нам в разных компонентах надо считать число книг по авторам или выводить число книг, помеченных как новинка. Логично же сделать селектор, чтобы не повторять себя в каждом компоненте. Логично желание видеть значение этого селектора для отладки безотносительно того, в каком компоненте мы его используем. Это же свойство стора, одно из его расширенных представлений. Как view и процедуры в базе данных. Редьюсеры — как хранимые процедуры, меняющие стор. На данный момент не нашёл нормальных инструментов для решения этой задачи. Есть полузаброшенный проект для reselect, не позволяющий работать с большим числом селекторов, не выводящий их названия.
Логично желание видеть значение этого селектора для отладки безотносительно того, в каком компоненте мы его используем.
Последние версии Redux Devtools (2.15 точно) это позволяют.
Привожу пример:
Селектор -
import { createSelector } from 'reselect';
const getNodes = (state) => state.nodes.data;
const treeNodesSelector = createSelector(
getNodes,
(nodes) => { ... Строится дерево из массива (добавляется поле children, к элементам)}
);
export default treeNodesSelector;
MapStateToProps —
nodes: treeNodesSelector(state),
Хранится в store в виде массива — data: []
.
Результат в DevTools —
nodes: {
data: [
{
id: '1',
ip: '127.0.0.1',
name: 'What is Love?',
port: 1,
parent_id: null,
children: [
{
id: '6',
ip: '11.1.1.1',
name: '11',
port: 1,
parent_id: '1'
}
]
},...
Из статьи по вашей ссылке: you are not required to use selector functions in a Redux app, а также сказано “Similarly, you don't have to use the Reselect library to create selectors — you can just write plain functions if you want”
Ох блин, наконец дошло до людей что action+reducer+action creator это просто функция. Пройдет еще годик и дойдет, что можно дропнуть все эти велосипеды и писать старое доброе
export class ProductReleases extends Store {
productReleasesFetching(state) {
state.fetchingState = 'requesting';
}
productReleasesFetched(state, action) {
state.productReleases = action.payload.productReleases;
state.fetchingState = 'success';
}
productReleasesFetchingError(state, action) {
state.fetchingState = 'failed';
state.error = action.payload.error;
}
…
};
с ровно тем же результатом
Подскажите пожалуйста касательно Redux Toolkit.
Мое приложение выводит список постов из группы vk.com. Сами данные получаю без пагинации, данных 30к+. Данные храню в редаксе в виде массива. Без использования toolkit при фетчинге наблюдались небольшие лаги, так как я в целом не делал никакой оптимизации.
Но после внедрения toolkit по данной статье начались сильные лаги при манипуляции со стором. В чем может быть причина и как это исправить?
Спасибо :)
Redux Toolkit как средство эффективной Redux-разработки