- PVSM.RU - https://www.pvsm.ru -
Эта статья о том, как построить архитектуру web-приложения в соответствии с принципами MVC на основе React [2] и Redux [3]. Прежде всего, она будет интересна тем разработчикам, кто уже знаком с этими технологиями, или тем, кому предстоит использовать их в новом проекте.
Концепция MVC позволяет разделить данные (модель), представление и обработку действий (производимую контроллером) пользователя на три отдельных компонента:
Модель (англ. Model):
Представление (англ. View) — отвечает за отображение информации (визуализацию).
React.js [4] это фреймворк для создания интерфейсов от Facebook. Все аспекты его использования мы рассматривать не будем, речь пойдет про Stateless-компоненты и React исключительно в роли View.
Рассмотрим следующий пример:
class FormAuthView extends React.Component {
componentWillMount() {
this.props.tryAutoFill();
}
render() {
return (
<div>
<input
type = "text"
value = {this.props.login}
onChange = {this.props.loginUpdate}
/>
<input
type = "password"
value = {this.props.password}
onChange = {this.props.passwordUpdate}
/>
<button onClick = {this.props.submit}>
Submit
</button>
</div>
);
}
}
Здесь мы видим объявление Functional-component-а FormAuthView. Он отображает форму с двумя Input-ами для логина и пароля, а также кнопку Submit.
FormAuthView это Stateless-компонент, т.е. он не имеет внутреннего состояния и все данные для отображения получает исключительно через Props. Также через Props этот компонент получает и Callback-и, которые эти данные меняют. Сам по себе этот компонент ничего не умеет, можно назвать его "Глупым", так как никакой логики обработки данных в нем нет, и сам он не знает, что за функции он использует для обработки пользовательских действий. При создании компонента он пытается использовать Callback из Props для автозаполнения формы. Про реализацию функции автозаполнения формы этому компоненту тоже ничего неизвестно.
Это пример реализации слоя View на React.
Redux является предсказуемым контейнером состояния для JavaScript-приложений. Он позволяет создавать приложения, которые ведут себя одинаково в различных окружениях (клиент, сервер и нативные приложения), а также просто тестируются.
Использование Redux подразумевает существование одного единственного объекта Store, в State которого будет хранится состояние всего вашего приложения, каждого его компонента.
Чтобы создать Store, в Redux есть функция createStore.
createStore(reducer, [preloadedState], [enhancer])
Её единственный обязательный параметр это Reducer. Reducer это такая функция, которая принимает State и Action, и в соответствии с типом Action определенным образом модифицирует иммутабельный State, возвращая его измененную копию. Это единственное место в нашем приложении, где может меняться State.
Определимся какие Action-ы нужны, для работы нашего примера:
const EAction = {
FORM_AUTH_LOGIN_UPDATE : "FORM_AUTH_LOGIN_UPDATE",
FORM_AUTH_PASSWORD_UPDATE : "FORM_AUTH_PASSWORD_UPDATE",
FORM_AUTH_RESET : "FORM_AUTH_RESET",
FORM_AUTH_AUTOFILL : "FORM_AUTH_AUTOFILL"
};
Напишем соответствующий Reducer:
function reducer(state = {
login : "",
password : ""
}, action) {
switch(action.type) {
case EAction.FORM_AUTH_LOGIN_UPDATE:
return {
...state,
login : action.login
};
case EAction.FORM_AUTH_PASSWORD_UPDATE:
return {
...state,
password : action.password
};
case EAction.FORM_AUTH_RESET:
return {
...state,
login : "",
password : ""
};
case EAction.FORM_AUTH_AUTOFILL:
return {
...state,
login : action.login,
password : action.password
};
default:
return state;
}
}
И ActionCreator-ы:
function loginUpdate(event) {
return {
type : EAction.FORM_AUTH_LOGIN_UPDATE,
login : event.target.value
};
}
function passwordUpdate(event) {
return {
type : EAction.FORM_AUTH_PASSWORD_UPDATE,
password : event.target.value
};
}
function reset() {
return {
type : EAction.FORM_AUTH_RESET
};
}
function tryAutoFill() {
if(cookies && (cookies.login !== undefined) && (cookies.password !== undefined)) {
return {
type : EAction.FORM_AUTH_AUTOFILL,
login : cookies.login,
password : cookies.password
};
} else {
return {};
}
}
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")
});
}
}
Таким образом, данные приложения и методы работы с ними описаны с помощью Reducer и ActionCreators. Это пример реализации слоя Model с помощью Redux.
Все React-компоненты так или иначе будут получать свой State и Callback-и для его изменения только через Props. При этом ни один React-компонент не будет знать о существовании Redux и Actions вообще, и ни один Reducer или ActionCreator не будет знать о React-компонентах. Данные и логика их обработки полностью отделены от их представления. Я хочу особенно обратить на это внимание. Никаких "Умных" компонентов не будет.
Напишем Controller для нашего приложения:
const FormAuthController = connect(
state => ({
login : state.login,
password : state.password
}),
dispatch => bindActionCreators({
loginUpdate,
passwordUpdate,
reset,
tryAutoFill,
submit
}, dispatch)
)(FormAuthView)
На этом всё: React-компонент FormAuthView получит login, password и Callback-и для их изменения через Props.
Результат работы этого демо-приложения можно посмотреть на Codepen [5].
Велик соблазн сделать какие-то компоненты поудобнее и написать их код побыстрее, завести внутри компонента State. Мол какие-то его данные временные, и хранить их не нужно. И всё это будет работать до поры до времени, пока, например, вам не придется реализовать логику с переходом на другой URL и возвращением обратно — тут всё сломается, временные данные окажутся не временными, и придется всё переписывать.
При использовании Stateful-компонентов, чтобы достать их State, придется использовать Refs. Такой подход нарушает однонаправленность потока данных в приложении и повышает связность компонентов между собой. И то и другое — плохо.
Также некоторые Stateful-компоненты могут иметь проблемы с серверным рендеренгом, ведь их отображение определяется не только с помощью Props.
А еще следует помнить, что в Redux Action-ы обратимы, но изменения State внутри компонентов — нет, и если смешать такое поведение — ничего хорошего не получится.
Надеюсь, описание честного MVC подхода при разработке с использованием React и Redux будет полезно разработчикам для создания правильной архитектуры своего web-приложения.
Если есть возможность в полной мере использовать концепцию MVC, то давайте её использовать, и не нужно изобретать что-то другое. Это подход проверенный на прочность десятилетиями, и все его плюсы, минусы и подводные камни давно известны. Придумать что-то лучше навряд ли получиться.
Автор: DevExpress
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/158149
Ссылки в тексте:
[1] Image: https://habrahabr.ru/company/devexpress/blog/305812/
[2] React: https://github.com/facebook/react
[3] Redux: https://github.com/reactjs/redux
[4] React.js: https://facebook.github.io/react/
[5] Результат работы этого демо-приложения можно посмотреть на Codepen: http://codepen.io/MrCheater/pen/GqQpYY
[6] рекомендованным подходом: https://facebook.github.io/react/docs/reusable-components.html#stateless-functions
[7] Источник: https://habrahabr.ru/post/305812/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.