Please note that this article is about the React Native architecture. If you are developing a Web application using React, this article is for reference only.

This is a long article, which starts with some analysis of the current Flux open source framework, followed by the architecture design process. You can cut to the chase.

The biggest problem with RN is the change of design philosophy, and the previous design methodology is not very suitable. RN only provides the framework for the view, not the framework for building a complete APP.

Considering the following problems encountered at present, we hope the architecture can provide solutions.

  1. Interaction: how to solve the communication between components [parent, child, brother, etc., especially cross-layer or reverse data flow, etc.]; Manipulate components with state or interface;

  2. Responsibilities: where to put component state, where to put business logic, where to put data, because it is too flexible, how to do it can achieve the function, but what is the best, is the most correct?

Todo a question: Due to react is state oriented programming, is equivalent to the components of the react to focus solely on the final state of the data, the data is how to produce the don’t care, but some scenarios, and how the data is produced will affect the components of some behavior (such as a new line for animation effects, query the lines do not need such as 】, it is difficult to describe this in RN…

RN architecture is the guidance and methodology to solve the above problems. It is the most comprehensive choice or provides the basis for the decision by taking the whole development, testing, operation and maintenance situation into consideration.

There are several architectures currently serving react, such as Flux, Reflux, Redux, Relay, and Marty.

Flux

Flux is an officially provided architecture with the purpose of hierarchical decoupling and clear division of responsibilities. It is clear who is responsible for what. For detailed description, please refer to the official documentation.

  1. Action encapsulates the request

  2. Dispatcher registers processors and dispatches requests

  3. A store is a processor that handles business logic and holds data

  4. View is presented according to the data provided by store; Accepts user input and issues an action request.

Data flow: Action-> Dispatcher -> Store -> Component

But I think the decoupling is too fine, it’s too much extra work to do one thing.

Just register the listening action twice, one is store register to dispatcher, one is view register to store.

Furthermore, the listener registered to the Dispatcher should not be registered. The architecture provides no encapsulation at all, exposing a unified callback method that routes different if else stores.

Reflux

The structure is basically the same as that of flux architecture. Some redundant operations of Flux are removed [such as the absence of dispatcher]. The architecture is more concise and compact, and some conventions are more than configurations.

Basically, the architectural redundancy of Flux is simplified, which can be said to be an improved version of Flux without redundancy, but there is no essential change.

╔ ═ ═ ═ ═ ═ ═ ═ ═ ═ ╗ ╔ ═ ═ ═ ═ ═ ═ ═ ═ ╗ ╔ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ╗ ║ Actions ║ ─ ─ ─ ─ ─ ─ > ║ Stores ║ ─ ─ ─ ─ ─ ─ > ║ View Components ║ ╚ ═ ═ ═ ═ ═ ═ ═ ═ ═ ╝ ╚ ═ ═ ═ ═ ═ ═ ═ ═ ╝ ╚ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ╝ ^ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘Copy the code
  1. Easier to listen in on. Listenables and conventions for methods that start with ON. And so on.

  2. Get rid of the Dispatcher.

  3. Action can be aop programmed.

  4. Get rid of waitfor. Store can listen on store.

  5. Component provides a set of mixins that make it easy to register \ unload listening to and interact with the store.

Redux

It’s popular in the community because it’s relatively easy to use

Features:

  1. Layered design, clear responsibilities.

  2. Requirements Store Reducer is a single page, easy to manage.

  3. Action is the request DTO object, is the request type, request data carrier.

  4. Reducer is the method of handling requests. No state is allowed and must be pure methods. Input and output must be strictly observed, with no asynchronous calls allowed in between. It is not allowed to change state directly, and to change state must return a new object.

  5. store

    1. Maintain application state;

    2. Provide the getState() method to getState;

    3. Provide a Dispatch (action) method to distribute requests to update state; The facade mode requires all requests to meet the unified format (routing, monitoring, logging, etc.) and unified invocation mode.

    4. Register a listener to listen for changes in state via SUBSCRIBE (listener).

  6. The official document is written in more detail, from design to development, which is better than Flux

The pain points are as follows. See if you can accept or resolve them:

  1. Redux’s principle 1: State cannot be modified.

    1. In fact, the react state also has the same problem. It is better to design the state without redundancy and minimize such situations

    2. We need to cut both the front and the back of the array because we can’t modify the array directly but update the specified item. If you often need this type of operation, you can choose to use the helper classes React. Addons. update, updeep, or Immutable, the native library that supports deep updates. Finally, always remember to never modify state before cloning it.

  2. A single large reducer split

    1. This design is also hard to make. It’s confusing

    2. In the demo given by the official, it is directly differentiated according to the content of state, which I think is not good. If there is cross-content in the later stage, it will be strange. The combineReducers scheme officially given only reduces the amount of code, and the essence remains unchanged. State is still split and the routing is still done by the business logic itself.

    3. Solution: Still deal with a whole state, we can write the Reducer class instead of methods according to the convention, methods are built in the class according to actionType, and the architecture is automatically routed and called.

    4. Before doing the Java architecture, routing must be called by the architecture, but now I feel that the major flux frameworks are not completely solve the problem.

  3. The official recommended design pattern is that the top-level container components rely on Redux and pass data between components through props. This design still does not solve the problem of interaction and data transfer between components. React: Camsong-github. IO /redux-in-ch…

  4. Bind state to component with connect. We have some black boxes here.

  5. Asynchronous actions are used to request server-side data and use middleware to enhance post-dispatch support for createStore.

Relay

No time, no research

Marty

No time, no research

conclusion

Simple flux encapsulated by open source architecture results in more redundant code.

The complex Redux that the open source architecture encapsulates, with its BINDING to RN, is a black box that is difficult to understand and maintain.

The open-source architecture of reflux between the two is less documented, and its sustainability is unknown. If you must use open source architecture, I think its slightly encapsulated is a more recommended choice.

Not overly complex programs (spa programs are more complex than RN is), these concepts will only make your development harder and more demanding for those who maintain them later.

Let’s continue our brainstorming and abstract summary of the Flux framework, what it does and does not do, in response to the questions raised in the beginning.

  1. 【解决 responsibilities 】 the flux series frameworks are decoupled and layered, so that who should do what, not to do anything else, so that the code reads more predictable and consistent, easy to maintain

  2. [Solution to communication] To continue decoupling, flux series framework adopts event mechanism to solve the communication between layers, and adopts props transmission to solve the communication between components.

The event system is key

Flux series architecture solves communication problems by using event system, in which the callback function is business logic, Redux is [Store Action Reducer], flux is [Action Dispacher store].

Do we really need an event system?

Benefits of event systems:

  1. An event can register multiple callback functions

  2. There is no coupling between the callback functions.

About 1

The need to register more than this situation is not very often, do you have to look at the code you have written, is most of the registration is one.

About 2

Decoupling is really thorough, but what if I need to control the order of execution and wait for A to finish executing before EXECUTING B? Ok, you can register A first and then register B. What if A is a fetch or Ajax operation? The only way to do this is to call B from a’s request-end callback. A depends on B again. Of course, you can continue to dispatch(b), and there is no coupling. However, you should know that there is a cost to register an event, and you need to write an action, and this kind of dispatch mode is really not suitable for human reading. Once dispatched, who will execute the next step does not know, which is not straightforward to call.

Well, that said, the final conclusion is also out, instead of using open source architecture, take advantage of its good ideas, replace its event system with an object-oriented structure, and encapsulate the architecture itself.

Architecture design

Again: How do you apply react Native

The solution to the initial problem is as follows

interaction

  1. External component publishing: A component can only use props to expose functions, not interfaces or other methods

  2. Parent-child: The child of a component communicates with the parent through an interface passed by the parent

  3. Between sibling components:

    1. If A wants to call B, a needs to change the source of B’s props. The source of B’s props is generally the state of the root component. The root component needs to be able to organize and coordinate.

    2. Scheme 2: Using the event mechanism, basically the same as the flux architecture. Slightly complex, and we do not need event features, this architecture is not recommended.

Duties and responsibilities

  1. Root – Stores state, organizes sub-view components, organizes business logic objects, etc

  2. Child View component – Render view according to this.props.

  3. Business logic object – Provides business logic methods

According to the above derivation, I named it as object-oriented ReactNative architecture design. The biggest difference between it and flux series architecture is that the event system [Store Action Dispatcher] or [Store Reducer] is replaced by business logic objects. Business logic object is a group of objects, n objects designed with object-oriented design concept, which is responsible for processing the business logic of the whole page.

Above is the derivation process, dry goods just start…

Object-oriented ReactNative component \ page architecture design

A standalone complete component \ page generally consists of the following elements:

  1. Root component, 1,

    1. Responsible for initializing state

    2. Responsible for providing list of external props

    3. Responsible for composing sub-view components to create page effects

    4. Responsible for registering business logic methods provided by business logic objects

    5. Responsible for managing business logic objects

  2. View sub-components, 0-N,

  3. Business logic objects, 0-N,

The root component

The root component consists of the following elements:

  1. Props – Public property

  2. The state of a state-RN system must be Immutable

  3. Private property

  4. References to business logic objects – initialized in componentWillMount

  5. Private method – begins with an underscore and is passed internally to child components using or

  6. Public methods can be used for both subcomponents and external components, but it is not recommended to use public methods to publish functionality externally. This breaks state-oriented programming, so use props to publish functionality whenever possible

The child view components

The child View component contains:

  1. Props – Public property

  2. Private properties – If you do not understand the following requirements, it is recommended not to put them on the parent component

    1. Redundancy with the parent component’s property or state is never allowed. Either explicit redundancy or computational redundancy, unless you can be sure that clearing is a performance bottleneck.

    2. This property is used only by itself, not by parent and sibling components. If you are not sure, put this component on the parent component to facilitate communication between components

  3. Private methods – used only for rendering views, no business logic allowed

  4. Public methods [not recommended, for the same reason as root components]

Business logic object

Business logic objects consist of the following elements:

  1. Root component object reference -this.root

  2. Constructor – Initializes the root object and initializes the private properties

  3. Private property

  4. Public method – Provides business logic externally

  5. Private methods – start with an underscore for internal use

Ps1: Generic components are only required to meet the above architectural design as much as possible

General-purpose components are generally pure technical components that do not contain any business, with high reuse value, high customization, and usually cannot be used directly and require code customization.

These are the basic parts of a system, such as a mask effect, or a modal popup.

The ultimate goal of architecture is to ensure that the overall structure of the system is good, the code quality is good, and easy to maintain. Generally, the people who write general-purpose components are experienced engineers, and the code quality is guaranteed. Moreover, the usage scenarios and life cycles of general components as parts are different from those of ordinary components \ pages, so it is only required to write general components to meet the architectural design as far as possible.

Ps2: View sub-component reuse problem

Raises the question, do subcomponents need to be reused during design? Whether or not subcomponents need to be reused affects component design.

  1. Need to reuse, only expose props, can manage state internally

  2. No need to reuse, only expose props, no internal state

In fact, generally according to the case of no reuse design, unless the reuse is clear, but then should be removed, into a separate component can exist, so this problem does not exist.

Analyze and verify the architecture design according to the scenario

Trigger the view change scenario

  1. The callback function is triggered asynchronously.

    1. An OR user action in a component lifecycle event triggers the start of the request server

    2. A component lifecycle event or user action triggers the launch of a scheduled task or registers other callback functions (such as an animation end event for the interaction manager)

  2. User action trigger

    1. Direct change of view

    2. Only register callback functions.

Full demo code

This demo is modeled after the Todolist demo provided by Redux.

Camsong-github. IO /redux-in-ch…

The demo screenshot:

Todolist page:

'use strict' let React=require('react-native'); let Immutable = require('immutable'); var BbtRN=require('.. /.. /.. /bbt-react-native'); var { BaseLogicObj, }=BbtRN; let { AppRegistry, Component, StyleSheet, Text, View, Navigator, TouchableHighlight, TouchableOpacity, Platform, ListView, TextInput, ScrollView, }=React; / / root component to start -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- let the root = React. CreateClass ({/ / initialize the simulation data,  data:[{ name:'aaaaa', completed:true, },{ name:'bbbbb', completed:false, },{ name:'ccccc', completed:false, },{name:' DDDDD ', completed:true,}], componentWillMount(){// Init business logic object this.addToDooBj =new addTodoObj (this); this.todoListObj=new TodoListObj(this); this.filterObj=new FilterObj(this); / / the following initialization actions can continue to do some components, such as the request data, etc. / /, of course, these actions are best provided by the business logic object, so the root component will be very clean. / / such as this: enclosing todoListObj. QueryData (); }, // State initialgetInitialState (){return {data:Immutable. FromJS (this.data),// simulate the initialization of data todoName: ",// new task text Render (){return (); curFilter:'all',// all no ok}}, }}); / / business logic object start -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- can use OO design approach designed to multiple objects / / business logic object to conform to the naming conventions: BaseLogicObj class AddTodoObj extends BaseLogicObj{press(){if(! this.getState().todoName)return; let list=this.getState().data; let todo=Immutable.fromJS({name:this.getState().todoName,completed:false,}); this.setState({data:list.push(todo),todoName:''}); } change(e){ this.setState({todoName:e.nativeEvent.text}); } } class TodoListObj extends BaseLogicObj { pressTodo(todo){ let data=this.getState().data; let i=data.indexOf(todo); let todo2=todo.set('completed',! todo.get('completed')); this.setState({data:data.set(i,todo2)}); } } class FilterObj extends BaseLogicObj { filter(type){ let data=this.getState().data.toJS(); if(type=='all'){ data.map((todo)=>{ todo.show=true; }); }else if(type=='no'){ data.map((todo)=>{ if(todo.completed)todo.show=false; else todo.show=true; }); }else if(type=='ok'){ data.map((todo)=>{ if(todo.completed)todo.show=true; else todo.show=false; }); } this.setState({curFilter:type,data:Immutable.fromJS(data)}); }} / / view subcomponents start -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / child view objects in focus on: This. Props to view let Footer=React. CreateClass ({render(){return (); }}); let FooterBtn=React.createClass({ render(){ return ( this.props.onFilterPress(this.props.name)} style={[{padding:10,marginRight:10},this.props.cur?{backgroundColor:'green'}:null]} > {this.props.title} ); }}); Let AddTodo=React. CreateClass ({render(){return (add task); }}); let Todo=React.createClass({ render(){ let todo=this.props.todo; return ( todo.get("show")! =false? this.props.onTodoPress(todo)} style={{padding:10,borderBottomWidth:1,borderBottomColor:'#e5e5e5'}}> {todo.get('completed')==true? } {todo.get('name')} :null); }}); let TodoList=React.createClass({ render(){ return ( {this.props.todos.reverse().map((todo, index) => )} ); }}); module.exports=Root;Copy the code

Business logic object base class BaseLogicObj:

'use strict' class BaseLogicObj{ constructor(root){ if(! Root){console.error(' Instantiating BaseLogicObj must pass in the root component object.'); } this.root=root; } getState(){ return this.root.state; } setState(s){ this.root.setState(s); } getRefs(){ return this.root.refs; } getProps(){ return this.root.props; } } module.exports=BaseLogicObj;Copy the code