Please welcome the protagonist:

  • DvaJS: Project address
  • React-coat: project address
Dva: I currently have 12,432 stars on Github. What about you? React-coat: I currently have 74. Dva: How dare you make fun of me? React-coat: Who am I afraid of? Dva:...Copy the code
Dva: I'm from Ali, from famous family, and you? React-coat: Personal project. Dva: How dare you find fault with me? React-coat: WHO am I afraid of when I grow wild? Dva:...Copy the code

DvaJS and React-Coat are both frameworks of the React+Redux+ Redux-Router ecosystem. They both introduce the invocation style of traditional MVC to MVVM and are quite similar in many ways.

DvaJS is already well known, has been online for several years, and is naturally better than React-Coat in terms of documentation, stability, testing adequacy, accessibility, and more. React-coat is just my personal project, which has been used in the company before. It was released to the outside world after it was upgraded to 4.0 in January this year and felt stable.

Apart from other factors, this article makes an in-depth comparison between the two in terms of design ideas and users’ use of apis. The Internet is a magical world, everyone has the opportunity to express their own views, just the so-called new ants are not afraid of elephant, I hope Dva will not mind, after all, the two are not the same magnitude, there is no progress without ridicule. In addition, if there is any misunderstanding of DvaJS, please criticize and correct it.

< p style = “max-width: 100%; clear: both; min-height: 1em; Please take a seat and rest yourself with a cup of tea…


Development of language

  • Dva is JS based and supports Typescript
  • React-coat supports JS based on Typescript

Dva claims to support Typescript, but when we look at the official Typescript example, we don’t really feel like we’re using Typescript. The data types of actions, models, and Views are isolated. The file path in the routing configuration is also not reflected, full of strings, I am confused…

To take a Model example, define a Model in Dva:

export default {
  effects: {
    // The Call, Put, and State types need to be imported manually
    *fetch(action: {payload: {page: number}}, {call: Call, put: Put}) {
      // With yield, data will not be reflected by usersservice.fetch
      const data = yield call(usersService.fetch, {page: action.payload.page});
      // The following save reducer will be triggered, but there is no strong relationship between them
      // How do I automatically constrain the playload type from the save Reducer?
      // If the save reducer is changed to save2, how to make the type here automatically feel error?
      yield put({type: "save".payload: data}); }},reducers: {
    save(state: State, action: {payload: {list: [] {}})return{... state, ... action.payload}; ,}}};Copy the code

Conversely, look at defining the same Model in a react-coat:

class ModuleHandlers extends BaseModuleHandlers {
  // this.state, this.actions, and this.dispatch are all integrated into the Model and can be called directly
  @effect("loading") // Inject the loading state
  public async fetch(payload: {page: number}) {
    // It is more intuitive to use await and data automatically reflects type
    const data = await usersService.fetch({page: action.payload.page});
    // Use method calls, more intuitive, and parameter types and method names are automatically constrained
    this.dispatch(this.actions.save(data));
  }

  @reducer
  public save(payload: {list: []}): State {
    return{... this.state, ... payload}; }}Copy the code

In addition, the react-coat demo uses a large number of TS generic operations to ensure mutual checks and constraints between modules, models, actions, views, and routers. For details, see react-coat-HelloWorld

Conclusion:

  • React-coat converts Typescript to productivity, while DVA just lets you play with Typescript.
  • React – Coat has a more intuitive and natural API call.

Integration framework

The two integration frameworks are similar and both belong to the Redux ecosystem. The biggest differences are:

  • Dva integrates redux-Saga, using yield to handle asynchrony
  • React-coat uses native async + await

Redux-saga has many advantages, such as ease of testing, ease of Fork multitasking, race between multiple Effects, and more. But the downsides are also obvious:

  • Too many concepts complicate matters
  • When yield is used, typescript types cannot be returned

Conclusion:

Whether you like Saga or not is a matter of personal choice. There is no absolute standard.


Page vs Module

Umi and DVA prefer to use the Page as the main line to organize the site structure and bind it to the Router.

In the component design approach, we mentioned Container Components, which in DVA we usually constrain to Route Components because in DVA we usually design Container Components in page dimensions.

Therefore, dVA projects mostly have this directory structure:

The SRC ├ ─ ─ components ├ ─ ─ layouts ├ ─ ─ models │ └ ─ ─ globalModel. Js ├ ─ ─ pages │ ├ ─ ─ photos │ │ ├ ─ ─ page. Js │ │ └ ─ ─ model. The js │ ├ ─ ─ videos │ │ ├ ─ ─ page. Js │ │ └ ─ ─ model. JsCopy the code

A few questions:

  • Single Page SPA, what is a Page? Where is its boundary? How does it differ from other components? It may not be nested in another Component one day, or it may be Modal pop-ups one day.
  • Some components may be referenced by multiple pages. Which Page should they be placed under?
  • Why is the route strongly associated with the Page? Do Page switches have to be routed? How about not routing?
  • Model following Page? Models are abstract data that may have a one-to-many relationship with the UI.

Take a look at the React – coat

In react-Coat, there is no concept of Page, only View, because a View may be routed into a so-called Page, may be modal pop-up into a popover, or may be directly nested by other views.

Suppose you have a PhotosView:

// Load in routing mode, called Page
render() {
  return( <Switch> <Route exact={true} path="/photos/:id" component={DetailsView} /> <Route component={ListView} /> </Switch> );  }Copy the code
// You can also use the props parameter directly to control the loading
render() {
  const {showDetails} = this.props;
  return showDetails ? <DetailsView /> : <ListView />;
}
Copy the code
  • How you load it, that’s an internal matter of the PhotosView, but for the outside world, you just load the PhotosView itself.
  • As for DetailsView and ListView, it doesn’t know how it will be loaded in the future.

The main organizational structure in React-Coat is Module, which is divided according to the principle of high cohesion and low coupling of business functions: one Module = one model(to maintain data) and one set of Views (to display interaction). A typical directory structure is as follows:

SRC ├ ─ ─ components ├ ─ ─ modules │ ├ ─ ─ app │ │ ├ ─ ─ views │ │ │ ├ ─ ─ the View1. The TSX │ │ │ ├ ─ ─ View2. The TSX │ │ │ └ ─ ─ index. The ts │ │ ├ ─ ─ model. Ts │ │ └ ─ ─ but ts │ ├ ─ ─ photos │ │ ├ ─ ─ views │ │ │ ├ ─ ─ the View1. The TSX │ │ │ ├ ─ ─ View2. The TSX │ │ │ └ ─ ─ index. The ts │ │ ├── ├─ exercisesCopy the code

Conclusion:

  • Dva takes UI Page as the main line to weave business functions, and binds it to routes, which is quite rigid. It is ok in simple applications, but for projects with complex interactivity, the reuse of Model and UI will become very troublesome.
  • React- Coat differentiates Moduel with high cohesion and low coupling of business functions, making it more flexible and consistent with programming philosophy.

Routing design

Routes in Dva are centrally configured and registered using the app.router() method. It involves concepts such as Page, Layout, ContainerComponents, RealouteComponents, loadComponent, loadMode, etc. More complex applications have dynamic routing, permission determination, etc., so router.js is a long, smelly, unreadable thing to write. And with some relative paths and string names, there is no way to cause TS checks.

Later in UMI + DVA, routes are automatically generated in the Pages directory structure, which is ok for simple applications but causes new problems for complicated ones. For example, a Page may be nested by multiple pages, and a model may be shared by multiple pages. So umi came up with some unspoken rules:

Models are divided into two categories, one is global model, the other is page model. The global model is stored in the/SRC /models/ directory, and all pages can be referenced; Page Model cannot be referenced by other pages. Here are the rules: SRC /models/ pages/**/models/**/.js for global model /pages/**/models/**/.js for page Model global model Model /**/*.js file in the path of the model /**/*.js file page Model will be looked up. Pages /a/b.js, pages/a/b/models/**/.js + pages/a/models/**/.js, and so on. Models /**/*.js is not needed when there is only one modelCopy the code

Take a look in react-coat:

Centralized routing configuration is not used, routing logic is scattered among components, and there are less enforced concepts and hidden rules.

In a word: Everything is Component

Conclusion:

React-coat routes are unlimited and simpler.


Code splitting and loading on demand

In Dva, since the Page is bound to the route, load on Demand can only be used in the route, which needs to be configured:

{
  path: '/user'.models: (a)= > [import(/* webpackChunkName: 'userModel' */'./pages/users/model.js')].component: (a)= > import(/* webpackChunkName: 'userPage' */'./pages/users/page.js'),}Copy the code

A few questions:

  • Models and Component are configured separately. How do I ensure that models load all the models required by Component?
  • Would it be too fragmented to have each model and component as a split code?
  • Routing and code splitting are tied together and not flexible enough.
  • Centralized configuration loading logic causes poor readability of configuration files.

In a react-coat, views can be loaded with routes or directly:

// Define code splitting
export const moduleGetter = {
  app: (a)= > {
    return import(/* webpackChunkName: "app" */ "modules/app");
  },
  photos: (a)= > {
    return import(/* webpackChunkName: "photos" */ "modules/photos"); }},Copy the code
// Use route load:
const PhotosView = loadView(moduleGetter, ModuleNames.photos, "Main"); . <Route exact={false} path="/photos" component={PhotosView} />
Copy the code
// Direct load:
const PhotosView = loadView(moduleGetter, ModuleNames.photos, "Main"); . render() {const {showDetails} = this.props;
  return showDetails ? <DetailsView /> : <ListView />;
}
Copy the code

React-coat benefits of doing this:

  • Code splitting only does code splitting and does not involve routing, because modules are not necessarily loaded in a non-routing manner.
  • Routing only does routing, not code splitting, because modules don’t have to do code splitting.
  • A Module is packaged into a bundle, including Model and views, to avoid fragmentation.
  • Loading a View automatically loads all models associated with the View without manual configuration.
  • Splitting routing logic inside views and hiding details from the outside is more in line with the idea of an everything component.

Conclusion:

  • Using React- Coat for code splitting and loading on demand is simpler and more flexible.

The destruction of Redux during dynamic model loading

When USING Dva, I found a serious problem, which made me wonder if I had made a mistake:

Redux-devtools localhost:8000/pages redux-devTools :8000/pages

2. Then click a link to go to localhost:8000/photos and check redux-devTools as follows:

Did my sharp-eyed friends see anything wrong?

When you load the photos model, the State snapshot of the first action @@init changes, forcing the photos in. Isn’t Redux about immutable data?

Conclusion:

When Dva loads the Model dynamically, it breaks the basic principles of Redux, while React-Coat does not.


The Model definition

  • The Model in the Dva follows the Page, and the Page follows the route.
  • The Model in Dva is scattered, so multiple models can be defined or loaded at will. Therefore, UMI has some restrictions, such as:
Models are divided into two categories, one is global model, the other is page model. The global model is stored in the/SRC /models/ directory, and all pages can be referenced; Page Model cannot be referenced by other pages. Global Model is loaded in full, Page Model is loaded on demand in Production, and page Model is loaded in full in development.Copy the code

In a word: around

In a React-coat model, a module can only have one model:

Within the Module, we can further divide it into 'a model(maintain data)' and 'a set of views (show interaction)'. We can write the model in a file named model.js, and put this file in the Module root directory, and the model state can be read by all modules. But can only be modified by their own Module, (suit the combineReducers concept)Copy the code

Conclusion:

  • The Model in React-Coat is simpler and purer, not tied to UI and routing.
  • Manual configuration to load the Model is also required when the Dva route loads the Page on demand.
  • React-coat automatically loads the corresponding Model when the View is loaded on demand.

The Model structure

Dva defines model as an Object with five agreed keys, such as:

{
  namespace: 'count'.state: 0.reducers: {
    aaa(payload) {...},
    bbb(payload) {...},
  },
  effects: {
    *ccc(action, { call, put }) {...},
    *ddd(action, { call, put }) {...},
  },
  subscriptions: { setup({ dispatch, history }) {... }},}Copy the code

There are several problems with this:

  • How to ensure no duplication of naming between Reducers and Effects? Simplicity is fine, but if it’s a long, complex business process that may involve reuse and extraction, using mixins and Extend, how do you guarantee that?

  • How to reuse and extend? The official document reads:

    From this point of view, it’s easier to add or overwrite something. For example, use object. assign to copy Object attributes. Note that there are two levels here. State, reducers, effects and Subscriptions in the Model structure are all object structures, which need to be assigned at this level respectively. You can do this with the help of the DVA community’s DVA-Model-extend library. On the other hand, you can also generate models using factory functions.

    Or one word: around

Now look at how react-Coat solves these two problems in reverse:

class ModuleHandlers extends BaseModuleHandlers<State.RootState.ModuleNames> { @reducer public aaa(payload): State {... } @reducer protected bbb(payload): State {... } @effect("loading")
  protected async ccc(payload) {...}
}
Copy the code
  • This means that reducer, effect and SUBSCRIPTIONS are all written in one Class as methods, which are naturally not the same names.
  • Because it is class-based, reuse and extension can take advantage of Class inheritance, overwriting, and overloading.
  • Because of TS, you can also use public or private permissions to reduce exposure.

Conclusion:

React-coat’s model, implemented using classes and decorators, is simpler, better suited for TS type checking, and easier to reuse and extract.


The Action to distribute

In Dva, you need to manually write type and payload in the action, which lacks type verification and static check

dispatch({ type: 'moduleA/query'.payload: {username:"jimmy"}}})Copy the code

Use type reflection directly with TS in react-coat:

dispatch(moduleA.actions.query({username:"jimmy"}))
Copy the code

Conclusion:

React – Coat’s Action distribution is more elegant


React-coat’s unique ActionHandler mechanism

We can simply assume that in Redux, store.dispatch(action) can trigger a registered Reducer, which seems to be an observer mode. To extend the above concept of effect, effect is also an observer. When an action is dispatched, it may trigger multiple observers to be executed, either reducer or effect. Therefore, Reducer and Effect are collectively called ActionHandler

The ActionHandler mechanism is powerful for complex business processes and collaboration across models, as illustrated by:

  • In react-Coat, there are special framework-level actions that are triggered when appropriate, such as:

    Arunachal Pradeshmodule@@router/LOCATION_CHANGE** : triggered when the route changes **@@framework/ERROR** : triggered when an ERROR occurs **module/LOADING** : triggered when LOADING state changes **@@framework/VIEW_INVALID** : triggered when UI interface failsCopy the code

    With ActionHandler, they all become injectable hooks that you can listen for, such as:

    // Listen to your INIT Action
    @effect()
    protected async [ModuleNames.app + "/INIT"] () {const [projectConfig, curUser] = await Promise.all([settingsService.api.getSettings(), sessionService.api.getCurUser()]);
      this.updateState({
        projectConfig,
        curUser,
        startupStep: StartupStep.configLoaded,
      });
    }
    Copy the code
  • In Dva, to handle an effect synchronously you must use put.resolve, which is a bit abstract, while in react-coat it is more intuitive and easier to understand to await directly.

// Handle synchronization effect in Dva
effects: {
    * query (){
        yield put.resolve({type: 'otherModule/query'.payload:1});
        yield put({type: 'updateState'.payload: 2}); }}// In React-coat, aWIat can be used
class ModuleHandlers {
    async query (){
        await this.dispatch(otherModule.actions.query(1));
        this.dispatch(thisModule.actions.updateState(2)); }}Copy the code
  • If ModuleA succeeds in performing an operation, either ModuleB or ModuleC will need to update its State due to the lack of an observer mode for action. Therefore, only ModuleB or ModuleC refresh actions can be written to ModuleA:
// In Dva, active Put is required to invoke ModuleB or ModuleC Action
effects: {
    * update (){
        ...
        if(callbackModuleName==="ModuleB") {yield put({type: 'ModuleB/update'.payload:1});
        }else if(callbackModuleName==="ModuleC") {yield put({type: 'ModuleC/update'.payload:1}); }}}// In react-coat, ActionHandler observer mode can be used:
class ModuleB {
    // Listen for the "ModuleA/update" action in ModuleB
    async ["ModuleA/update"] () {... }}class ModuleC {
    // Listen for the "ModuleA/update" action in ModuleC as well
    async ["ModuleA/update"] () {... }}Copy the code

conclusion

React-coat is much simpler and clearer than Dva for complex processes and cross-model collaboration due to the introduction of ActionHandler mechanism.


conclusion

Ok, first compare these points, the other remember to add again! The difference between the two frameworks can be felt only if you experience them. So give it a try:

git clone https://github.com/wooline/react-coat-helloworld.git
npm install
npm start
Copy the code

Of course, there are many good things about Dva that are already well known, so I won’t repeat them here. To reiterate, the above views are personal. If the Dva is misunderstood in the article, you are welcome to criticize and correct.