Author: Coco (Hujiang Front-end Development Engineer)
This article is original and reproduced. Please indicate the author and source.
Original address: https://hackernoon.com/thinking-in-redux-when-all-youve-known-is-mvc-c78a74d35133#.u2jqlinjn
When we were planning to launch our own mobile applications in Spill, the first decision we needed to make was: Which programming language should we use? After some discussion, we finally made the decision: React-Native. Learning a new "language" or framework is not a big problem, but man, I have to tell you that React-Native and Redux are really tough bones to eat. This article does not describe how React-Native works (because that's really not the hardest part). The purpose of the following paragraphs is to help anyone complete the transition from "Thingking in MVC" to "Thinking in Redux". I hope it will help you.
React-Natvie and Redux?
Once you start learning React-Natvie (or React), before someone mentions Redux to you, you're probably only three stack overflow questions behind, or a few blogs on medium.com.
Of course you are very happy. You begin to understand the difference between state and props. You know what componentDidMount is, and you even know how to reasonably create a reusable component. But all of a sudden, you find yourself on egghead.io, where some guys are talking about stores, reducer compositions, action s, and mapping state to props.
You also realize that you can do this before:
$(".my-button").click();
Let a button do something; now? Maybe you can't do anything with one of your buttons in three hours.
Make some analogies
If you come from the world of MVC (or MVVC), you are used to using models, views and controllers. But in Redux, we use actions, reducers, stores, and components to solve problems. It's difficult to "migrate" from MVC to Redux, but here's what I do:
Actions = Controller
Think of your Actions as controller. Whenever you want your App to generate activities (such as loading data, changing the isLoading flag from true to false, etc.), you need to distribute an action. Just like in MVC, you need to call a controller.
Reducer = Model
To some extent. Your reducers will take charge of the current state of the application (e.g. user information, data loaded by the api, data to be displayed). When an action is called, reducer decides what needs to be done. In MVC, you may have a model with setName() method. In Redux, you will have a reducer that handles an action and sets the name to state.
Special thanks to Prabin Varma*. Make store more vivid.
Stores = ???
store is very special in Redux, and it's hard to find something equivalent to it in MVC. But don't worry. store is something that is carefully stored behind the scenes, like a container that stores all the reducer collections that serve state. It has a way to get the current state and expose changes in the way it subscribes to state (using the "connect()" method). This is the secret that Redux allows you to call action s and pass them into components like props.
Components = Views
Components are somewhat similar to your smart view. They are responsible for displaying the information you get from state. I recommend dividing your component into two parts: one is just as a display part (puppet component), and the other is responsible for handling all action and state changes (smart component).
From MVC to Redux
One of the main differences between MVC and Redux is that data in MVC can flow bidirectionally, but in Redux, data is limited to only one-way flow.
Classic MVC. Life was not so hard then.
As you can see (and learn from experience) in the chart above, data can flow in both directions. You press a button in the view layer, which sends a message to your controller, causing the model to update. The model changes some values and returns them to the controller, then the controller refreshes the view. Grey is often simple!
Redux data stream. Life has become terrible.
Things are different in Redux. If you have a component, then you want to do something when the button is pressed. So where do you start? I do this:
- Define your Action
- Define your Reducer
- Define Actions as props in your Component
- Put them on the View
Here is a simple code example to explain the above concepts. In this example, I'll show you how to edit a text input, and then when a user presses a key, it will call action to save the content.
First, start with the Action file
export const MODIFY_NAME = "MODIFY_NAME"; export const SAVE_NAME = "SAVE_NAME"; /** * This is the action we call from the component when the user presses the button. Every time a user enters a character, he calls the action with a new value in the input. Notice the type and payload fields in the function, which we will use in reducer to "modify" our model with the new value. **/export function modifyName(name){ return { type: MODIFY_NAME, payload:{ name } } } /** This is the action we call from the component when the user presses the Save Name button. Notice how we import value. This is done because reducer already holds the value. In addition, there is no payload here. The reason for this is that reducer is not needed. In the reducer step, no additional information is required. At the same time, doing so would typically call an api terminal and something like that, but for brevity, I did not include it. **/export function saveName(){ return{ type: SAVE_NAME } } //Now look at our Reducer. Overall, reducer needs to handle incoming actions. // Import the actions file we defined earlier import * as constants from '../actions.js'; /** The initial state is used to define your reducer. Usually you will set it to default values and empty strings. The reason for this is that when you use these values, you at least guarantee that they have a default value. Think of it as a default constructor. **/const initialState = { name:'', isSaved: false } /** This reducer is responsible for "listening" for the output of the action. The saveName and modifyName functions we defined earlier will be called here. The action parameter is the value (type and payload) defined in the function above that will be return ed. **/function reducer(state=initialState,action){ switch (action.type){/** State is immutable in Redux. You must always return a new one, so here you use the expansion operator of ES6 to copy the values in the incoming state. **/ case constants.MODIFY_NAME: return { ...state, name:action.payload.name } case constants.SAVE_NAME: return { ...state, isSaved:!state.isSaved } } } export default name;
Notice how constants.MODIFY_NAME and constants.SAVE_NAME correspond to the type field of the object we will return in action. It is in this way that reducer learns the identity of action.
Now let's define our "smart" components. This means that this component will define all calls to action.
/** App Home Page **/ 'use strict'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import Name from './presentational/Name'; import * as actions from './actions/name'; /** The actual values (name and isSaved) and the function s required to invoke action are passed in as props. **/ class NameContainer extends Component { render() { return ( <Name name = {this.props.name} isSaved = {this.props.isSaved} modifyName = {this.props.modifyName} saveName = {this.props.saveName} /> ); } } /** It's done to get it. reducer Save values in and return them to component. So that we can passthis.propsTo call them **/ const mapStateToProps = (state,ownProps) =>{ /** Using Redux stores, it allows us to get the value in reducer only through state.name. Notice how name is exported through reducer. **/ const { name, isSaved } = state.name; return {name,isSaved }; } /** In the mapStateToProps function, we map variables in state to property to pass in our presentation component. In the mapDispatchToProps function, we map action handlers to our containers so that we can pass them into the presentation component. **/ const mapDispatchToProps = (dispatch) => { return { modifyName:(name)=>{ dispatch(actions.modifyName(name)) }, saveName:()=>{ dispatch(actions.saveName()) }, } /** As you can see, our ability to props functions and variables into our container depends entirely on this. It is the connect function in react-redux that magically implements these functions. **/ export default connect(mapStateToProps,mapDispatchToProps)(NameContainer);
Now to the simplest part, create a presentation component (V in MVC) that interacts with users.
/** * The puppet component will use the incoming props, which are the data generated by the user's behavior on the smart component. */'use strict'; import React, { Component } from 'react'; import {Text,TextInput, TouchableOpacity} from 'react-native'; import { Actions, ActionConst } from 'react-native-router-flux'; class Name extends Component render() { return ( <View> <TextInput multiline={false} //from the component above value={this.props.name} placeholder="Full Name" //from the component above onChangeText={(name)=>this.props.modifyName(name)} /> <TouchableOpacity onPress= {()=>{this.props.saveName()}> <Text>Save</Text> </TouchableOpacity> </View> ); } } export default Name;
That's it! Although you still need to do some basic template setting filling, I hope this explains how to think in a redux way.
Some of the questions that I've had to fall into the pit for a while (e.g. where did the information go? So I hope to save your time and ease your headache.
If you want to see what apps our team built with React-Native and Redux look like, check out our apps here.( https://spoil.co/app).
The original innovative book "Mobile Web Front-end Effective Development Practice" of iKcamp has been sold in Amazon, Jingdong and Dangdang.