Comments 44
Мне кажется, что я использую связку React+Redux вполне стандартным образом. Просто я ввел некоторые ограничения, которые гарантируют, что все плюсы, которые дает нам Redux не будут сведены на нет. Про возможное убийство плюсов я писал.
Насчет "если нет возможности использовать Flux то зачем использовать редукс" не могу согласиться. Redux — всё-таки это не Flux (про отличия от предшественников можно почитать тут https://github.com/reactjs/redux/blob/master/docs/introduction/PriorArt.md )
Зачем строить MVC? Потому что плюсов масса, и потому что это легко сделать — посмотрите код всего приложения, там никаких кастомных решений нет — всё беру из коробки.
Вы говорите, что модели здесь вообще нет.
Описание модели из русской википедии:
Модель (англ. Model):
Предоставляет знания: данные и методы работы с этими данными;
Реагирует на запросы, изменяя своё состояние;
Не содержит информации, как эти знания можно визуализировать;
Экземпляр Store из Redux:
Предоставляет знания: данные и методы работы с этими данными — YES
Реагирует на запросы, изменяя своё состояние — YES
Не содержит информации, как эти знания можно визуализировать — YES
Не вижу противоречий.
Controller связывавает View и Model. Здесь всё стандартно
Дело в том, что React-компонент может выполнять все роли из MVC — хранить данные, обрабытывать их и рулить другими компонентами, может даже дергать функции дочерних компонентов через Refs. Из-за таких широких возможностей многие лепят архитектуру как попало. Я же предлагаю вариант наведения в коде порядка, спроецировав старые добрые принципы в новую реальность
Дело в том, что React-компонент может выполнять все роли из MVCВот поэтому тут не стоит даже упоминать MVC, чтобы избежать любой путаницы.
Если лезть в детали — Store не меняет свое состояние сам, и не содержит сам по себе никаких методов работы с этими данными. Более того, там могут храниться как данные приложения (domain data), так и различные аспекты состояния UI. Redux — это «state container», а не модель. И не пытается быть последней. Редьюсеры меняют любое состояние, и отнюдь не обязательно связанное с бизнес-логикой приложения. С очень большой натяжкой можно назвать всю эту часть «моделью», и то только для тех, кто вот без MVC вообще не может понять что за что отвечает. Но, как по мне, это изначально неверный способ думать об архитектуре Redux-приложений.
Если лезть в детали — Store не меняет свое состояние сам, и не содержит сам по себе никаких методов работы с этими данными
Если уж совсем грубо, то это ужасная «тонкая модель» из мира MVC (не академического, а того, который часто реализуют на практике). А компоненты на реакте — вьюшка и толстый контроллер.
Я понимаю, что хочется казаться модным и утверждать, что здесь нет ничего из поганого мира ооп, но то, что вы старательно даете старым вещам новые имена не делает эти вещи новыми.
Да не, модным никто и не пытается казаться, и кто-то говорил что-то про «поганый мир ООП» (я так не считаю)? Я только о том, что не нужно все под одну гребенку MVC, потому что так вроде бы проще. Элементов больше, стыки между M, V и C без натяжки не сделать, да и незачем.
Насчет middlewares: я использую redux-thunk. Без проблем прикрутится redux-devtools, какой-нибудь логгер и т.д… По-идее middlewares не должны мешать
Пример использования redux-thunk:
function submit() {
return function(dispatch, getState) {
const state = getState();
dispatch(reset());
request('/auth/', {send: {
login : state.login,
password : state.password
}}).then(function() {
router.push('/');
}).catch(function() {
window.alert("Auth failed")
});
}
}
То что подход многими реализуется через одно место, вовсе не означает, что это подразумевается подходом.
разделение данных и представления
Очень грубое упрощение. MVC про разделение бизнес-логики (с данными) и логики представления (может быть тоже со своими данными).
Тогда MVVM — тоже MVC? Document-Model — тоже MVC?
Redux — это не совсем MVC, но выполняет ту же функцию: разделяет данные, представления и изменения данных.
Про "легко" и "MVC" я написал, потому что для реализации его принципов в связке React и Redux не нужно никаких надстроек, не нужно своего кастомного кода — нужно лишь придерживаться пары простых принципов, как раз для хорошей организации кода
Ну справедливости ради глобальное состояние не подразумевает отсутствие декомпозиции.
Каждая часть состояния может управляться независимыми редьюсерами.
В случае календаря его внутреннее состояние — это открыт datepicker или закрыт, текущий выбранный месяц и год, режим показа (некоторые календари позволяют переходить в режим выбора месяца или года по клику по дате). Для такого компонента состояние — это нормально, и не нужно выносить его в application state. В то же время список дат из рабочего графика — это состояние внешнее по отношению к компоненту, это application state и его нужно хранить и обрабатывать редьюсерами.
При использовании Stateful-компонентов, чтобы достать их State, придется использовать Refs.
вы хотите зачем-то знать state дочерних компонентов? зачем использовать refs для того, чтобы читать this.state?
1. Не называйте это MVC. Во-первых, то что, вы показали — это ближе к MVVM, во-вторых, не пытайтесь подогнать redux под какие-то другие термины. Не ставьте себя в рамки. Redux — это redux, определенная архитектура приложения со своими особенностями и best practices
2. Может сложится впечатление, что state — это что-то плохое. Это не так! Использовать state можно и нужно, если этот state связан только с внутренним состоянием компонента, а не с состоянием приложения. Например, можно кешировать сложные вычисления уровня представления (смещения, цвет и т.д.). Не стоит загаживать хранилище такими вещами, иначе потом там сам черт ногу сломит.
3.
componentWillMount() {
this.props.tryAutoFill();
}
Не делайте так! Вы сами себе противоречите. Компонент не должен знать про автозаполнение. Одной строкой вы сделали из «глупого» компонента «умный». Разделение должно происходить на уровне логики, а не паттернов (callbacks, props). По факту ваш компонент теперь «знает» про redux (точнее про какую-то логику, которую нужно дернуть, чтобы себя заполнить).
4. В «умных» компонентах нет ничего плохого, при правильном их использовании. Ваш react-redux код по факту и есть «умный» компонент.
Ставить рамки это хорошо. Чем жестче ограничения, тем однообразнее будет код.
Да — я думаю, что state внутри компонента это плохая идея, и стоит избегать его использования. Единственный случай, когда это правда необходимо — это разработка компонента, который может работать в обоих, в Stateful и Stateless, режимах. Классический пример это React-овский Input. Он умеет Controlled/Uncontrolled режимы. И ведет себя сильно по-разному.
tryAutoFill ничем не отличается от updateLogin. Т.е. Компонент знает, что какие-то callback-и будут, знает интерфейс, но не имеет представления о реализации
- "Умные" компоненты появляются только, когда лень реализовывать правильно.
Не стоит загаживать хранилище такими вещами, иначе потом там сам черт ногу сломит.
Неужели вы ни разу не упирались в то, что с каким-то Stateful-компонентом со временным State не удается реализовать нужный сценарий, и приходится много переписывать?
Вас Undo/Redo не беспокоит? Или серверный рендеринг?
Проблемы не надуманы — они реально существуют
2. Внутренняя кухня компонента должна оставаться внутренней кухней. Наверх выносятся только те callback и свойства, которые могут быть интересны внешним компонентам. Но тут очень важно чувствовать границу. Приходит с опытом.
3. >> Т.е. Компонент знает, что какие-то callback-и будут, знает интерфейс
У вас не вызывает диссонанс фразы «глупый компонент» и «компонент знает»? Это неверно. Компонент предоставляет (!) интерфейс для внешних компонентов. Единственное, о чем знает глупый компонент — это свое внутреннее устройство.
Он принимает свойства и вызывает callback'и при изменение СВОЕГО состояние. Есть очень простое правило: если вызов callback'а отвечает на вопрос «почему?» — все ок, а если на вопрос «зачем?» — что-то не так. Поэтому, кстати, и принято у callback'ов делать префикс «on». Посмотрите на реактовские onChange, onClick и т.д — такая маленькая подсказка.
Это ключевые различия. Задача глупого компонента — рендеринг и генерация событий. Только в этом случае компонент может быть безболезненно переиспользован где-то еще.
В вашем случае вызов tryAutoFill() — это знание о внешней логике. Этот вызов должен делать кто-то более «умный», а в «глупый» компонент должны уйти уже нужные значения.
4. Поймите, у вас тоже есть умный компонент, только вы назвали его Controller.
TODO-App. Там не все идеально, но вот большой пример с тем же подходом https://github.com/MrCheater/react-spa-todo
Честный MVC на React + Redux