- PVSM.RU - https://www.pvsm.ru -
С тех пор, как в React появились хуки, возникает много вопросов о том, способны ли они заменить Redux.
Я полагаю, что хуки и Redux имеют мало общего между собой. Хуки не дают нам неких новых удивительных возможностей по работе с состоянием. Они, вместо этого, расширяют API, позволяющие делать в React то, что в нём уже было возможным. Однако API хуков сделал гораздо удобнее работу со встроенными возможностями React по управлению состоянием. Оказалось, что новыми возможностями по работе с состоянием пользоваться легче, чем старыми, которые имелись в компонентах, основанных на классах. Теперь я гораздо чаще, чем раньше, использую средства по работе с состоянием компонентов. Естественно, поступаю я так лишь тогда, когда это уместно.
Для того чтобы объяснить моё отношение к хукам React и к Redux, мне хотелось бы сначала рассказать о том, в каких ситуациях обычно прибегают к использованию Redux.
Redux — это библиотека, реализующая предсказуемое хранилище состояния приложений. Это, кроме того, архитектура, которая легко интегрируется с React.
Вот основные сильные стороны Redux:
Другими словами, Redux позволяет хорошо организовывать код и позволяет удобно его отлаживать. Redux помогает разрабатывать приложения, которые легко поддерживать. Благодаря использованию этой библиотеки упрощается поиск истоков проблем, возникающих в программах.
Хуки React позволяют, при работе с функциональными компонентами, пользоваться аналогом состояния компонентов, основанных на классах, и аналогами методов их жизненного цикла. Хуки появились в React 16.8.
Среди основных сильных сторон хуков можно отметить следующие:
Обратите внимание на то, что эти замечательные возможности, на самом деле, не перекрывают возможности Redux. Хуки React можно и нужно использовать для того, чтобы выполнять детерминированные обновления состояния, но это всегда было одной из возможностей React, и детерминированная модель состояния Redux хорошо совмещается с этой возможностью. Именно так React добивается детерминированности в выводе визуальных элементов, и это, без преувеличений, один из движущих мотивов создания React.
Если пользоваться инструментами наподобие API react-redux с поддержкой хуков [3], или хуком React useReducer [4], окажется, что нет причины задавать вопрос о том, что выбрать — хуки или Redux. Можно пользоваться и тем и другим, сочетая и комбинируя эти технологии.
После появления API хуков я перестал использовать следующие технологии:
Я всё ещё часто пользуюсь следующими технологиями:
Нет нужды стремиться к тому, чтобы использовать Redux в каждом приложении и в каждом компоненте. Если ваш проект состоит из одного визуального компонента, если он не сохраняет данные в состояние и не загружает их оттуда, если в нём не выполняются асинхронные операции ввода-вывода, то я не могу придумать достойной причины усложнять этот проект за счёт использования в нём Redux.
То же самое можно сказать и о компонентах, обладающих следующими особенностями:
У вас могут быть веские основания для использования в определённых ситуациях стандартной модели состояния компонентов React. В подобных ситуациях хорошую службу вам сослужат хуки React. Например, форма, код которой приведён ниже, использует локальное состояние компонента с помощью хука React useState
.
import React, { useState } from 'react';
import t from 'prop-types';
import TextField, { Input } from '@material/react-text-field';
const noop = () => {};
const Holder = ({
itemPrice = 175,
name = '',
email = '',
id = '',
removeHolder = noop,
showRemoveButton = false,
}) => {
const [nameInput, setName] = useState(name);
const [emailInput, setEmail] = useState(email);
const setter = set => e => {
const { target } = e;
const { value } = target;
set(value);
};
return (
<div className="row">
<div className="holder">
<div className="holder-name">
<TextField label="Name">
<Input value={nameInput} onChange={setter(setName)} required />
</TextField>
</div>
<div className="holder-email">
<TextField label="Email">
<Input
value={emailInput}
onChange={setter(setEmail)}
type="email"
required
/>
</TextField>
</div>
{showRemoveButton && (
<button
className="remove-holder"
aria-label="Remove membership"
onClick={e => {
e.preventDefault();
removeHolder(id);
}}
>
×
</button>
)}
</div>
<div className="line-item-price">${itemPrice}</div>
<style jsx>{cssHere}</style>
</div>
);
};
Holder.propTypes = {
name: t.string,
email: t.string,
itemPrice: t.number,
id: t.string,
removeHolder: t.func,
showRemoveButton: t.bool,
};
export default Holder;
Здесь useState
используется для управления кратковременно используемым состоянием полей ввода name
и email
:
const [nameInput, setName] = useState(name);
const [emailInput, setEmail] = useState(email);
Вы можете заметить, что здесь ещё имеется создатель действия removeHolder
, поступающий в свойства из Redux. Как уже было сказано, сочетание и комбинирование технологий — это совершенно нормально.
Использование локального состояния компонента для решения подобных задач всегда выглядело хорошо, но до появления хуков React у меня бы, в любом случае, возникло бы желание сохранять данные компонента в Redux-хранилище и получать состояние из свойств.
Раньше работа с состоянием компонента предусматривала использование компонентов, основанных на классах, запись начальных данных в состояние с применением механизмов объявления свойств класса (или в конструкторе класса) и так далее. В результате оказывалось, что для того, чтобы избежать использования Redux, компонент приходилось слишком сильно усложнять. В пользу Redux говорило и существование удобных инструментов для управления состоянием форм с помощью Redux. В результате раньше я не беспокоился бы о том, что временное состояние формы хранится там же, где и данные, имеющие более длительный срок существования.
Так как я уже использовал Redux во всех моих мало-мальски сложных приложениях, выбор технологии для хранения состояния компонентов особых раздумий у меня не вызывал. Я просто пользовался Redux практически во всех случаях.
В современных условиях делать выбор тоже несложно: работа с состоянием компонента организуется с помощью стандартных механизмов React, а управление состоянием приложения — с помощью Redux.
Ещё один распространённый вопрос, касающийся управления состоянием, звучит так: «Нужно ли мне помещать абсолютно всё в хранилище Redux? Если я этого не сделаю, не нарушит ли это возможности по отладке приложений с использованием механизмов TTD?».
Нет нужды размещать абсолютно всё в хранилище Redux. Дело в том, что в приложениях используется много временных данных, которые слишком сильно по нему разбросаны для того, чтобы дать какую-то информацию, которая, будучи записанной в журнал или применённой при отладке, может оказать разработчику заметную помощь в поиске проблем. Вероятно, вам, если только вы не пишете приложение-редактор, работающее в реальном времени, нет нужды записывать в состояние каждое движение мыши или каждое нажатие на клавишу клавиатуры. Когда вы помещаете нечто в состояние Redux, вы добавляете в приложение дополнительный уровень абстракции, а также — сопутствующий ему дополнительный уровень сложности.
Другими словами, вы можете спокойно пользоваться Redux, но на это у вас должны быть некие причины. Применение возможностей Redux в компонентах может оказаться оправданным в том случае, если компоненты отличаются следующими особенностями:
Вот ещё один пример, взятый из приложения TDDDay [5]:
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { compose } from 'ramda';
import page from '../../hocs/page.js';
import Purchase from './purchase-component.js';
import { addHolder, removeHolder, getHolders } from './purchase-reducer.js';
const PurchasePage = () => {
// Это можно использовать вместо
// mapStateToProps и mapDispatchToProps
const dispatch = useDispatch();
const holders = useSelector(getHolders);
const props = {
// Воспользуемся композицией функций для конструирования создателей
// действий с помощью dispatch.
addHolder: compose(
dispatch,
addHolder
),
removeHolder: compose(
dispatch,
removeHolder
),
holders,
};
return <Purchase {...props} />;
};
// `page` - это компонент высшего порядка, созданный из множества
// других компонентов высшего порядка с помощью композиции функций.
export default page(PurchasePage);
Этот документ не занимается работой с DOM. Это — презентационный компонент. Он подключён к Redux с использованием API react-redux с поддержкой хуков [3].
Redux здесь используется из-за того, что нам нужно, чтобы данные, которыми занимается эта форма, можно было бы использовать и в других частях пользовательского интерфейса. А после того, как операция покупки будет завершена, нам надо сохранить соответствующую информацию в базе данных.
Фрагменты состояния, с которыми работает этот код, используются различными компонентами, они не обрабатываются лишь одним компонентом. Это — не данные, существующие лишь небольшой отрезок времени. Эти данные можно считать постоянными, они вполне могут использоваться на разных экранах приложения и в нескольких сессиях. Всё это — сценарии, в которых состояния компонента для хранения данных применить не удастся. Это, правда, всё же возможно, но только в том случае, если создатель приложения напишет, на базе API React, собственную библиотеку для управления состоянием. Подобное сделать гораздо сложнее, чем просто воспользоваться Redux.
API React Suspense [6], в будущем, может пригодиться при выполнении сохранения данных в состоянии и загрузки их из него. Нам нужно подождать его выхода и посмотреть — сможет ли оно заменить шаблоны сохранения и загрузки данных Redux. Redux позволяет нам чётко отделять побочные эффекты от остальной логики компонента, при этом нам не нужно особым образом работать со службами ввода-вывода. (Причиной того, что я предпочитаю библиотеку redux-saga [7] промежуточному ПО redux-thunk [8], является изоляция эффектов). Для того чтобы соревноваться в этом сценарии с Redux, API React понадобится обеспечить изоляцию эффектов.
Redux — это гораздо больше (а часто — и гораздо меньше), чем библиотека для управления состоянием. Это ещё и подмножество архитектуры Flux [9], которая гораздо более жёстко определяет то, как выполняются изменения состояния. Подробности об архитектуре Redux можно почитать здесь [10].
Я часто использую редьюсеры, созданные в стиле Redux, в тех случаях, когда мне нужно поддерживать сложное состояние компонента, но не нужно пользоваться библиотекой Redux. Я, кроме того, пользуюсь действиями, созданными в духе Redux (и даже Redux-инструментами вроде Autodux [11] и redux-saga [7]) для отправки действий в Node.js-приложениях. При этом я даже не импортирую в подобные приложения Redux.
Проект Redux всегда был больше архитектурой и набором добровольно соблюдаемых соглашений, чем библиотекой. На самом деле, базовую реализацию Redux можно уложить буквально в пару десятков строк кода.
Это окажется хорошей новостью для тех, кому хочется чаще использовать локальные состояния компонентов с помощью хуков и при этом не привязывать всё к Redux.
React поддерживает хук useReducer
, который может работать с редьюсерами, написанными в стиле Redux. Это хорошо для реализации нетривиальной логики работы с состоянием, для работы с зависимыми фрагментами состояния, и так далее. Если вы встретились с задачей, для решения которой подойдёт временное состояние отдельного компонента, то для работы с этим состоянием вы можете воспользоваться архитектурой Redux, но вместо библиотеки Redux для управления состоянием можете применить хук useReducer
.
Если позже вам понадобится наладить постоянное хранение данных, которые раньше вы хранили лишь временно, то вы будете на 90% готовы к подобному изменению. Всё, что вам останется сделать — это подключить компонент к хранилищу Redux и добавить туда соответствующий редьюсер.
Нет, не нарушается. На самом деле, использование Redux не делает проект детерминированным. А вот соглашения — делают. Если вы хотите, чтобы ваше Redux-состояние было бы детерминированным — используйте чистые функции [12]. То же самое касается и ситуаций, в которых нужно, чтобы детерминированным бы было временное состояние локальных компонентов.
Принцип единого источника достоверных данных не указывает на то, что нужно, чтобы все данные, входящие в состояние приложения, хранились бы в одном месте. Смысл этого принципа заключается в том, что у каждого фрагмента состояния должен быть лишь один источник достоверных данных. В результате у нас может быть множество фрагментов состояния, у каждого из которых есть собственный источник достоверных данных.
Это означает, что программист может принять решение о том, что передаётся в Redux, и о том, что передаётся в состояние компонентов. Данные, определяющие состояние, можно брать и из других источников. Например — из браузерного API, позволяющего работать со сведениями об адресе просматриваемой страницы.
Redux — это отличный инструмент для поддержки единого источника достоверных данных для состояния приложения. Но если состояние компонента находится и используется исключительно в пределах этого компонента, то, по определению, у этого состояния уже имеется единственный источник достоверных данных — состояние компонента React.
Если вы помещаете некие данные в состояние Redux, вы всегда должны выполнять чтение этих данных из состояния Redux. Для всего, что находится в хранилище Redux, это хранилище должно выступать единственным источником достоверных данных.
Если нужно, то помещать всё в состояние Redux — это совершенно нормально. Возможно, это повлияет на производительность в случае использования фрагментов состояния, которые нужно часто обновлять, или в том случае, если речь идёт о хранении состояния компонента, в котором интенсивно используются зависимые фрагменты состояния. О производительности не стоит беспокоиться до тех пор, пока с производительностью не возникнут проблемы. Но если вас вопрос производительности беспокоит — попробуйте оба способа работы с состоянием и оцените их воздействие на производительность. Выполните профилирование проекта и помните о модели производительности RAIL.
Это зависит от многого. Функция connect
создаёт компонент высшего порядка, подходящий для многократного использования, а хуки оптимизированы для интеграции с отдельным компонентом.
Нужно ли подключать одни и те же свойства к разным компонентам? Если это так — пользуйтесь connect
. В противном случае я предпочёл бы выбрать хуки. Например, представьте, что у вас имеется компонент, который отвечает за авторизацию разрешений для действий пользователя:
import { connect } from 'react-redux';
import RequiresPermission from './requires-permission-component';
import { userHasPermission } from '../../features/user-profile/user-profile-reducer';
import curry from 'lodash/fp/curry';
const requiresPermission = curry(
(NotPermittedComponent, { permission }, PermittedComponent) => {
const mapStateToProps = state => ({
NotPermittedComponent,
PermittedComponent,
isPermitted: userHasPermission(state, permission),
});
return connect(mapStateToProps)(RequiresPermission);
},
);
export default requiresPermission;
Теперь, если с приложением интенсивно работает администратор, все действия которого нуждаются в особом разрешении, вы можете создать компонент высшего порядка, который сочетает в себе все эти разрешения со всей необходимой сквозной функциональностью:
import NextError from 'next/error';
import compose from 'lodash/fp/compose';
import React from 'react';
import requiresPermission from '../requires-permission';
import withFeatures from '../with-features';
import withAuth from '../with-auth';
import withEnv from '../with-env';
import withLoader from '../with-loader';
import withLayout from '../with-layout';
export default compose(
withEnv,
withAuth,
withLoader,
withLayout(),
withFeatures,
requiresPermission(() => <NextError statusCode={404} />, {
permission: 'admin',
}),
);
Вот как это использовать:
import compose from 'lodash/fp/compose';
import adminPage from '../HOCs/admin-page';
import AdminIndex from '../features/admin-index/admin-index-component.js';
export default adminPage(AdminIndex);
API компонентов высшего порядка оказывается удобным для решения этой задачи. Оно позволяет решить её более лаконично, с использованием меньшего объёма кода, чем применение хуков. А вот для того чтобы задействовать функцию connect
, нужно помнить о том, что она, в качестве первого аргумента, принимает mapStateToProps
, а в качестве второго — mapDispatchToProps
. Нужно не забывать о том, что эта функция может принимать функции или объектные литералы. Нужно знать о том, чем отличаются разные варианты использования connect
, и о том, что это — каррированная функция, но её каррирование не выполняется автоматически.
Другими словами можно сказать, что я полагаю, что при разработке connect
была проведена большая работа в направлении лаконичности кода, но получающийся код не оказывается ни особенно хорошо читаемым, ни особенно удобным. Если мне не нужно работать с несколькими компонентами, то я с удовольствием предпочту неудобному API connect
гораздо более удобный API хуков, даже учитывая то, что это приведёт к росту объёма кода.
Нет, не значит. Использование в коде паттерна синглтон намекает на сомнительное качество этого кода, указывая на наличие в нём совместно используемого изменяемого состояния. Вот это — настоящий анти-паттерн. Redux же предотвращает мутацию разделяемого состояния через инкапсуляцию (не следует менять состояние приложения напрямую, за пределами редьюсеров; задачи по изменению состояния решает Redux) и через передачу сообщений (изменение состояния может вызвать лишь отправленный объект события).
Заменяют ли Redux хуки React? Хуки — это замечательно, но Redux они не заменяют.
Надеемся, что этот материал поможет вам в деле выбора модели управления состоянием для ваших React-проектов.
Уважаемые читатели! Встречались ли вы с ситуациями, в которых хуки React способны заменить Redux?
Автор: ru_vds
Источник [13]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/328587
Ссылки в тексте:
[1] Image: https://habr.com/ru/company/ruvds/blog/464293/
[2] render prop: https://reactjs.org/docs/render-props.html
[3] API react-redux с поддержкой хуков: https://react-redux.js.org/next/api/hooks
[4] хуком React useReducer: https://reactjs.org/docs/hooks-reference.html#usereducer
[5] TDDDay: https://tddday.com/
[6] Suspense: https://reactjs.org/docs/react-api.html#suspense
[7] redux-saga: https://github.com/redux-saga/redux-saga
[8] redux-thunk: https://github.com/reduxjs/redux-thunk
[9] Flux: https://facebook.github.io/flux/
[10] здесь: https://medium.com/javascript-scene/10-tips-for-better-redux-architecture-69250425af44
[11] Autodux: https://github.com/ericelliott/autodux
[12] чистые функции: https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-pure-function-d1c076bec976
[13] Источник: https://habr.com/ru/post/464293/?utm_source=habrahabr&utm_medium=rss&utm_campaign=464293
Нажмите здесь для печати.