This article is about how to build a web application architecture in accordance with MVC principles based on React and Redux . First of all, it will be of interest to those developers who are already familiar with these technologies, or those who will have to use them in a new project.
The concept of MVC allows you to divide the data (model), the presentation and processing of actions (produced by the controller) of the user into three separate components:
Model (eng. Model):
View (English View) - is responsible for displaying information (visualization).
React.js is a framework for creating interfaces from Facebook. We will not consider all aspects of its use, it will be about Stateless components and React exclusively as a View.
Consider the following example:
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> ); } }
Here we see the declaration of the FormAuthView component. It displays a form with two Input for login and password, as well as the Submit button.
FormAuthView is a Stateless component, i.e. it does not have an internal state and receives all data for display exclusively through Props. Also through Props, this component also receives Callbacks that change this data. By itself, this component does not know how, you can call it "Stupid", since there is no data processing logic in it, and he himself does not know what kind of functions he uses to process user actions. When creating a component, he tries to use Callback from Props to auto-complete the form. This component is also unknown about the implementation of the form auto-fill function.
This is an example of implementing a View layer on React.
Redux is a predictable state container for JavaScript applications. It allows you to create applications that behave in the same way in different environments (client, server, and native applications), as well as just being tested.
Using Redux implies the existence of a single Store object, the State of which will store the state of your entire application, each of its components.
To create a Store, Redux has a createStore function.
createStore(reducer, [preloadedState], [enhancer])
Its only required parameter is Reducer. Reducer is a function that accepts State and Action, and in accordance with the type of Action in a certain way modifies an immutable State, returning its modified copy. This is the only place in our application where State can change.
Let us determine which actions are necessary for the work of our example:
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" };
Write the appropriate 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; } }
And ActionCreators:
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") }); } }
Thus, these applications and methods of working with them are described using the Reducer and ActionCreators. This is an example implementation of the Model layer using Redux.
All React-components one way or another will receive their State and Callback-and for its change only through Props. At the same time, no React component will know about the existence of Redux and Actions in general, and no Reducer or ActionCreator will know about React components. The data and logic of their processing are completely separated from their presentation . I want to pay special attention to this. There will be no "smart" components.
Let's write Controller for our application:
const FormAuthController = connect( state => ({ login : state.login, password : state.password }), dispatch => bindActionCreators({ loginUpdate, passwordUpdate, reset, tryAutoFill, submit }, dispatch) )(FormAuthView)
That's all: The FormAuthView React Component will receive a login, password, and Callback for changing through Props.
The result of this demo application can be viewed on Codepen .
It is tempting to make some components more comfortable and to write their code as soon as possible, to get inside the State component. They say some of his data is temporary, and do not need to store them. And all this will work for the time being, for example, until you have to implement logic with switching to another URL and returning back - everything will break, temporary data will not be temporary, and you will have to rewrite everything.
When using Stateful components, you will need to use Refs to get their State. This approach violates the unidirectionality of the data flow in the application and increases the connectivity of the components among themselves. Both are bad.
Also, some stateful components may have problems with server rendering, because their display is determined not only with the help of Props.
And it should be remembered that in Redux the Actions are reversible, but the State changes inside the components are not, and if you mix this behavior, nothing good will happen.
I hope the description of an honest MVC approach in developing using React and Redux will be useful for developers to create the right architecture for their web application.
If there is an opportunity to fully use the concept of MVC, then let's use it, and do not need to invent something else. This approach has been tested for durability for decades, and all its advantages, disadvantages and pitfalls have long been known. Come up with something better unlikely to turn out.
Source: https://habr.com/ru/post/305812/
All Articles