Front-end application is a very broad concept. This article focuses on pure Web application, commonly known as SPA application. Most of the application development framework, into the early Angular Backbone will use a todomVC framework to explain how to meet the application architecture design. In this article, I also take TodomVC as a starting point and share with our self-developed application development framework Rdeco.

As for the concept of front-end application architecture, I mentioned it in my previous article. After a period of discussion and precipitation, we currently tend to divide front-end application architecture into three different modes, corresponding to the three complexities of front-end application development.

The three models are

  1. Simple mode for single application

  2. Complex patterns of module layering in complex applications

  3. Micro-module mode under multi-application integration

As for the third kind, in terms of most front-end application research and development scenarios, the first two modes are mainly in the majority.

A simple model

The initial form of front-end application is not an application, but a piece of JavaScript script. The concept of front-end application has evolved with the development of front-end engineering and the gradual maturity of application research and development mode.

The existing front-end application architecture mode usually refers to MVC flux or MMVM, but in essence, its core is MVC. The only difference is the number of M, such as centralized single store or multi-store. Each of these patterns is classified as a complex pattern, in which application modules are distributed in different layers that call and integrate with each other depending on the means provided by the framework.

Complex patterns can be implemented in many ways. Let’s return to the simple pattern, which we define as a simple pattern is that all layers are implemented within a single application and do not expose the internal layers of the application externally.

In the case of Todomvc, which contains the basic operations of CURD, the code would look something like this if todomVC were viewed as a cohesive single application or component

import React from 'react'
import { createComponent } from 'rdeco'

export default createComponent({
  name: 'todomvc'.state: {
    todolist: [{text: 'get up'.check: false },
      { text: 'eat'.check: false },
      { text: 'sleep'.check: false},].newTodoValue: ' ',},controller: {
    onChange(e, index) {
      this.state.todolist[index].check = e.target.checked
      this.setter.todolist(this.state.todolist)
    },
    onNewTodoChange(e) {
      this.setter.newTodoValue(e.target.value)
    },
    onDeleteClick(index) {
      this.setter.todolist(
        this.state.todolist.filter((v, i) = > {
          returni ! == index }) ) },onClick() {
      this.state.todolist.push({
        check: false.text: this.state.newTodoValue,
      })
      this.setter.todolist(this.state.todolist)
      this.setter.newTodoValue(' ')}},view: {
    render() {
      return (
        <div className="App">
          <ul>
            {this.state.todolist.map((todo, index) => {
              return (
                <li key={todo.text}>
                  <input
                    onChange={(e)= > this.controller.onChange(e, index)}
                    checked={todo.check}
                    type="checkbox"
                  />
                  {todo.text}
                  <button onClick={()= >This. Controller. OnDeleteClick (index)} > delete</button>
                </li>)})}</ul>
          <input
            type="text"
            onChange={this.controller.onNewTodoChange}
            value={this.state.newTodoValue}
          />
          <br />
          <br />
          <button onClick={this.controller.onClick}>Add a to-do list</button>
        </div>)}}})Copy the code

Example: online codesandbox. IO/s/romantic -…

The benefit of the simple pattern is that the layers within the component are equally clear: the data layer → State, the control layer for responding to interactions → Controller, and the view layer for rendering → View

And because the component context this is shared between the layers, it makes it easy to interoperate between the layers. Simple patterns are great for scenarios where you don’t need to expose the details of the internal layers.

However, in real application scenarios, few business applications can be satisfied with simple mode, mainly due to the completely cohesive front-end component/application scenarios are very few.

Typically, data is persisted to the server, and the interface design of the server does not take into account the front-end scenarios. As a result, the data interface is often shared by different views. When multiple views share the same block of server data, the simple mode makes the logic of data processing redundant. At the same time, the direct relationship between components makes the logic of components less pure.

But the most deadly problem is often when a component directly relies on another component’s internal data, leading to unexpected bugs such as this scenario

Two tables depend on the source data of the same server interface. To save interface requests, Table B may directly obtain data from Table A. However, if Table A changes the source data accidentally, bugs may occur in Table B.

But if Table B also gets data from the server, then the requests are redundant, and performance and efficiency are affected. So a simple pattern based on component cohesion is not sufficient in this scenario. Complex patterns are needed

Complex patterns

The core of the complex pattern is to expand the layering from a single module to the whole application level and realize the ordered interoperation of the layered modules within the application.

In the case of TodomVC in the above example, this means stripping the operations around Todolist into a separate model and then simulating the Service layer with localStorage. Having model and localStorage interoperate, isolating the server from the view layer, starts with the code

import React from "react";
import { create, createComponent } from "rdeco";

const todomvcService = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    if (localStorage.getItem("todolist")) {
      resolve(JSON.parse(localStorage.getItem("todolist")));
    } else {
      resolve([
        { text: "Wake up".check: false },
        { text: "Eat".check: false },
        { text: "Sleep".check: false}]); }},100);
});

const todomvcModel = create({
  name: "todomvc-model".state: {
    todolist: []},subscribe: {
    "todomvc-model": {
      state: {
        todolist({ nextState }) {
          localStorage.setItem("todolist".JSON.stringify(nextState)); }}}},controller: {
    onMount() {
      todomvcService.then((data) = > {
        this.setter.todolist(data);
      });
    },
    onCompleteTodo(index, checked) {
      this.state.todolist[index].check = checked;
      this.setter.todolist(this.state.todolist);
    },
    onAddTodo(text) {
      this.state.todolist.push({
        text,
        check: false
      });
      this.setter.todolist(this.state.todolist);
    },
    onDeletTodo(index) {
      this.setter.todolist(
        this.state.todolist.filter((v, i) = > {
          returni ! == index; })); }}});export default createComponent({
  name: "todolist".subscribe: {
    "todomvc-model": {
      state: {
        todolist({ nextState }) {
          this.setter.todolist(nextState); }}}},state: {
    todolist: todomvcModel.state.todolist,
    newTodoValue: ""
  },
  controller: {
    onChange(e, index) {
      todomvcModel.controller.onCompleteTodo(index, e.target.checked);
    },
    onNewTodoChange(e) {
      this.setter.newTodoValue(e.target.value);
    },
    onDeleteClick(index) {
      todomvcModel.controller.onDeletTodo(index);
    },
    onClick() {
      todomvcModel.controller.onAddTodo(this.state.newTodoValue);
      this.setter.newTodoValue(""); }},view: {
    render() {
      return (
        <div className="App">
          <ul>
            {this.state.todolist.map((todo, index) => {
              return (
                <li key={todo.text}>
                  <input
                    onChange={(e)= > this.controller.onChange(e, index)}
                    checked={todo.check}
                    type="checkbox"
                  />
                  {todo.text}
                  <button onClick={()= >This. Controller. OnDeleteClick (index)} > delete</button>
                </li>
              );
            })}
          </ul>
          <input
            type="text"
            onChange={this.controller.onNewTodoChange}
            value={this.state.newTodoValue}
          />
          <br />
          <br />
          <button onClick={this.controller.onClick}>Add a to-do list</button>
        </div>); }}});Copy the code

Example: online codesandbox. IO/s/romantic -…

After refactoring, todomVC’s hierarchy changed from cohesive internal to external

Take React as an example. At present, there are many frameworks that can implement complex patterns, such as classic Redux Mobx or its own official framework, or useContext combined with useReducer. One problem with these frameworks, however, is that the design of application architectures tends to evolve from simple to complex patterns.

That is, we often cannot design the application architecture according to the complex pattern in the early stage, so that if the application is designed without sufficient business changes in the later stage, it will lead to additional design costs, and the probability will become overdesign. In this regard, we developed Rdeco with the evolution of the application architecture in mind, thus introducing an objectification of the description structure. You can compare two versions of the code before and after. Todomvc’s evolution from simple to complex mode is not disruptive, but rather a soft transition.

If you are interested, you can follow our program github.com/kinop112365… 👏 🏻 start