俄罗斯贵宾会-俄罗斯贵宾会官网
做最好的网站

你需要 Mobx 还是 Redux ?

你需要 Mobx 还是 Redux ?

2018/02/22 · JavaScript · mobx, Redux

原文出处: 熊建刚的博客   

在过去一年,越来越多的项目继续或者开始使用React和Redux开发,这是目前前端业内很普遍的一种前端项目解决方案,但是随着开发项目越来越多,越来越多样化时,个人又有了不同的感受和想法。是不是因为已经有了一个比较普遍的,熟悉的项目技术栈,我们就一直完全沿用呢,有没有比他更适合的方案呢?恰逢团队最近有一个新项目,于是博主开始思考,有没有可能使用其他可替代技术开发呢?既能提高开发效率,又能拓展技术储备和眼界,经过调研,选择了Mobx,最终使用React+Mobx搭建了新项目,本篇总结分享从技术选型到项目实现的较完整过程,希望共同进步。

索引


前言

当我们使用React开发web应用程序时,在React组件内,可以使用this.setState()this.state处理或访问组件内状态,但是随着项目变大,状态变复杂,通常需要考虑组件间通信问题,主要包括以下两点:

  1. 某一个状态需要在多个组件间共享(访问,更新);
  2. 某组件内交互需要触发其他组件的状态更新;

关于这些问题,React组件开发实践推荐将公用组件状态提升:

Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor

通常多组件需要处理同一状态,我们推荐将共享状态提升至他们的共同最近祖先组件内。更多详情查看

当项目越发复杂时,我们发现仅仅是提升状态已经无法适应如此复杂的状态管理了,程序状态变得比较难同步,操作,到处是回调,发布,订阅,这意味着我们需要更好的状态管理方式,于是就引入了状态管理库,如ReduxMobxJumpsuitAlt.js等。

title: 翻译|A Dummy’s Guide to Redux and Thunk in React
date: 2017-04-15 21:36:07
categories: 翻译
tags: Redux

状态管理

状态管理库,无论是Redux,还是Mobx这些,其本质都是为了解决状态管理混乱,无法有效同步的问题,它们都支持:

  1. 统一维护管理应用状态;
  2. 某一状态只有一个可信数据来源(通常命名为store,指状态容器);
  3. 操作更新状态方式统一,并且可控(通常以action方式提供更新状态的途径);
  4. 支持将store与React组件连接,如react-reduxmobx-react;通常使用状态管理库后,我们将React组件从业务上划分为两类:
    1. 容器组件(Container Components):负责处理具体业务和状态数据,将业务或状态处理函数传入展示型组件;
    2. 展示型组件(Presentation Components):负责展示视图,视图交互回调内调用传入的处理函数;

Mobx VS Redux

目前来看,Redux已是React应用状态管理库中的霸主了,而Mobx则是一方诸侯,我们为什么要选择Mobx,而不是继续沿用Redux呢,那就需要比较他们的异同了。

Mobx和Redux都是JavaScript应用状态管理库,都适用于React,Angular,VueJs等框架或库,而不是局限于某一特定UI库。

Redux和Thunk的使用傻瓜教程

原文参见
Github repo
强烈推荐这篇文章.

如果你和我一样,读了Redux的文档,看了Dan的录像,Wes的课程,仍然不能抓住怎么使用Redux的核心,希望这个傻瓜教程能够帮到你.
在真正实施之前,我做了一些尝试,所以我决定写一点从已经存在的使用fetch JSON数据的app一步步转变为使用Redux和Redux Thunk的app.如果你不知道Thunk是什么,也不要担心,我们会在”Redux 之路”部分来使用它执行函数的异步调用.
这个教程需要你对React和ES6/2015有基本的掌握.

Redux

要介绍Redux,我们就不得不谈到Flux了:

Flux is the application architecture that Facebook uses for building client-side web applications.It’s more of a pattern rather than a formal framework

Flux是Facebook用来开发客户端-服务端web应用程序的应用架构,它更多是一种架构模式,而非一个特定框架。详解Flux

而Redux更多的是遵循Flux模式的一种实现,是一个JavaScript库,它关注点主要是以下几方面:

  1. Action:一个JavaScript对象,描述动作相关信息,主要包含type属性和payload属性:
    1. type:action 类型;
    2. payload:负载数据;
  2. Reducer:定义应用状态如何响应不同动作(action),如何更新状态;
  3. Store:管理action和reducer及其关系的对象,主要提供以下功能:
    1. 维护应用状态并支持访问状态(getState());
    2. 支持监听action的分发,更新状态(dispatch(action));
    3. 支持订阅store的变更(subscribe(listener));
  4. 异步流:由于Redux所有对store状态的变更,都应该通过action触发,异步任务(通常都是业务或获取数据任务)也不例外,而为了不将业务或数据相关的任务混入React组件中,就需要使用其他框架配合管理异步任务流程,如redux-thunkredux-saga等;

非Redux方法

components/ItemList.js中创建一个React组件,用于fetch和显示items列表.

Mobx

Mobx是一个透明函数响应式编程(Transparently Functional Reactive Programming,TFRP)的状态管理库,它使得状态管理简单可伸缩:

Anything that can be derived from the application state, should be derived. Automatically.

任何起源于应用状态的数据应该自动获取。

其原理如图:

俄罗斯贵宾会 1

  1. Action:定义改变状态的动作函数,包括如何变更状态;
  2. Store:集中管理模块状态(State)和动作(action);
  3. Derivation(衍生):从应用状态中派生而出,且没有任何其他影响的数据,我们称为derivation(衍生),衍生在以下情况下存在:
    1. 用户界面;
    2. 衍生数据;衍生主要有两种:
      1. Computed Values(计算值):计算值总是可以使用纯函数(pure function)从当前可观察状态中获取;
      2. Reactions(反应):反应指状态变更时需要自动发生的副作用,这种情况下,我们需要实现其读写操作;

import {observable, autorun} from 'mobx'; var todoStore = observable({ /* some observable state */ todos: [], /* a derived value */ get completedCount() { return this.todos.filter(todo => todo.completed).length; } }); /* a function that observes the state */ autorun(function() { console.log("Completed %d of %d items", todoStore.completedCount, todoStore.todos.length ); }); /* ..and some actions that modify the state */ todoStore.todos[0] = { title: "Take a walk", completed: false }; // -> synchronously prints: 'Completed 0 of 1 items' todoStore.todos[0].completed = true; // -> synchronously prints: 'Completed 1 of 1 items'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import {observable, autorun} from 'mobx';
 
var todoStore = observable({
    /* some observable state */
    todos: [],
 
    /* a derived value */
    get completedCount() {
        return this.todos.filter(todo => todo.completed).length;
    }
});
 
/* a function that observes the state */
autorun(function() {
    console.log("Completed %d of %d items",
        todoStore.completedCount,
        todoStore.todos.length
    );
});
 
/* ..and some actions that modify the state */
todoStore.todos[0] = {
    title: "Take a walk",
    completed: false
};
// -> synchronously prints: 'Completed 0 of 1 items'
 
todoStore.todos[0].completed = true;
// -> synchronously prints: 'Completed 1 of 1 items'

罗列一下基础组件

首先我们使用包含各种items的state配置一个静态的组件,2 个boolean state分别用于根据loading和error来渲染出各自的单独显示组件.

 import React, { Component } from 'react';
class ItemList extends Component {
  constructor() {
      super();
      this.state = {
          items: [//items在列表中现实的内容
              {
                  id: 1,
                  label: 'List item 1'
              },
              {
                  id: 2,
                  label: 'List item 2'
              },
              {
                  id: 3,
                  label: 'List item 3'
              },
              {
                  id: 4,
                  label: 'List item 4'
              }
          ],
          hasErrored: false, //网络请求错误的状态
          isLoading: false   //网络请求中的状态
      };
  }
  render() {
      if (this.state.hasErrored) {//根据Errored的状态决
      //定是否加载这个组件,网络请求错误时,false=>true
          return <p>Sorry! There was an error loading the items</p>;
      }
      if (this.state.isLoading) {
      //网络请求中的组件,发出请求时,false=>true
          return <p>Loading…</p>;
      }
      return (
          <ul>
              {this.state.items.map((item) => (
                  <li key={item.id}>
                      {item.label}
                  </li>
              ))}
          </ul>
      );
  }
}
export default ItemList;

可能看起来还不是特别能说明问题,但是已经是一个好的开端了.
渲染的时候,组件输出四个items列表,但是如果你把isLoadinghasError的state由false改为false的时候,对应的<p></p>就会显示出来.(注意每个组件都是return出来的,每次只显示一个).

函数式和面向对象

Redux更多的是遵循函数式编程(Functional Programming, FP)思想,而Mobx则更多从面相对象角度考虑问题。

Redux提倡编写函数式代码,如reducer就是一个纯函数(pure function),如下:

(state, action) => { return Object.assign({}, state, { ... }) }

1
2
3
4
5
(state, action) => {
  return Object.assign({}, state, {
    ...
  })
}

纯函数,接受输入,然后输出结果,除此之外不会有任何影响,也包括不会影响接收的参数;对于相同的输入总是输出相同的结果。

Mobx设计更多偏向于面向对象编程(OOP)和响应式编程(Reactive Programming),通常将状态包装成可观察对象,于是我们就可以使用可观察对象的所有能力,一旦状态对象变更,就能自动获得更新。

改为动态值

直接编码items对于组件来说不是特别有用,所以最好从JSON API来fetch items数据,如果这样做的话,我们就可以把isLoadinghasError改为合适的状态.
响应值和我们直接编码是一样,但是在实际生产中,你可能会拉回一个图书畅销榜的列表,最新的blog帖子或者其他app中需要的内容.
为了fetch items,我们将使用合适的Fetch API.Fetch使得执行请求比传统的XMLHttpRequest更容易,并且返回的是响应值的promise对象(这一点对于Thunk很重要).Fetch并不是在所有的浏览器中都可以使用,所以你需要在项目中添加依赖项.

 npm install whatwg-fetch --save

转变实际上相当简单.

  • 首先把items的初始化state设置为空数组
  • 现在我们添加一个方法fetch数据,同时还要设定loading和error的状态.
fetchData(url) {//fetch的包装方法
  //进入函数首先设定isLoading state false=>true
  this.setState({ isLoading: true });
  fetch(url)
      .then((response) => {//返回promise独享
          if (!response.ok) {
              throw Error(response.statusText);
          }
          //不管返回的数据是什么,只要返回数据
          //就修改isLoading state true=>false
          this.setState({ isLoading: false });
          return response;
      })
      .then((response) => response.json())
      .then((items) => this.setState({ items })) 
      // ES6 property value shorthand for { items: items }
      .catch(() => this.setState({ hasErrored: true }));//返回数据的解析为json,如果捕获到错误就hasErrored:
      // false=>true
}
  • 函数写完以后,在组件加载的时候就调用函数
 componentDidMount() {
 this.fetchData('http://      5826ed963900d612000138bd.mockapi.io/items');
}

完成以后,代码如下

 class ItemList extends Component {
   constructor() {
       this.state = {
           items: [],
       };
   }
   fetchData(url) {
       this.setState({ isLoading: true });
       fetch(url)
           .then((response) => {
               if (!response.ok) {
                   throw Error(response.statusText);
               }
               this.setState({ isLoading: false });
               return response;
           })
           .then((response) => response.json())
           .then((items) => this.setState({ items }))
           .catch(() => this.setState({ hasErrored: true }));
   }
   componentDidMount() {
       this.fetchData('http://5826ed963900d612000138bd.mockapi.io/items');
   }
   render() {
   }
}

差不多了,组件现在从REST API fetch items数据,在4个items到达之前,你希望看”Loading...”出现提示.如果URL不能返回数据,你应该看到error 的信息.

然而(译注:讨厌的然而),在现实中,组件不应该包含具体的fetch逻辑,data也不应该储存在组件的state中,所以Redux实时的出现了.

单一store和多store

store是应用管理数据的地方,在Redux应用中,我们总是将所有共享的应用数据集中在一个大的store中,而Mobx则通常按模块将应用状态划分,在多个独立的store中管理。

转变到Redux

需要添加Redux, React Redux 和Redux Thunk作为依赖项.

 npm install redux react-redux redux-thunk --save

JavaScript对象和可观察对象

Redux默认以JavaScript原生对象形式存储数据,而Mobx使用可观察对象:

  1. Redux需要手动追踪所有状态对象的变更;
  2. Mobx中可以监听可观察对象,当其变更时将自动触发监听;

理解Redux

有几个Redux的核心原理,我们必须要理解(译注:话很简单,但是要在大脑里构建出Redux的工作流程是有花很多时间的和精力的).

  1. Redux中有一个全局state对象来管理整个应用的state.在篇文章中,全局对象就是我们组件的初始化对象.
  2. 唯一能改变state的是触发一个action,action是一个描述state应该怎么改变的对象,Action Creators可以被dispatched的函数,触发一个变化,执行的内容是返回一个action
  3. 当一个action被dispatch以后,Reducer执行根据action的内容实际改变state对象,如果action没有找到匹配项,就会返回默认的state.
  4. Reducers是纯函数,他们不能有任何的异步操作和mutate-必须要返回一个修改的copy.
  5. 俄罗斯贵宾会,单个的Reducer可以被combine为一个单一的rootReducer,从而创建一个离散的state聚合体.
  6. Store是把action和reducer组织到一起的工具,他包含了rootReducer代表的状态,中间件,允许你执行实际的dispatchactions
  7. 为了在React中使用Redux,<Provider />组件包装整个应用,传递store到子代组件们.

不可变(Immutable)和可变(Mutable)

Redux状态对象通常是不可变的(Immutable):

switch (action.type) { case REQUEST_POST: return Object.assign({}, state, { post: action.payload.post }); default: retur nstate; }

1
2
3
4
5
6
7
8
switch (action.type) {
  case REQUEST_POST:
    return Object.assign({}, state, {
      post: action.payload.post
    });
  default:
    retur nstate;
}

我们不能直接操作状态对象,而总是在原来状态对象基础上返回一个新的状态对象,这样就能很方便的返回应用上一状态;而Mobx中可以直接使用新值更新状态对象。

设计我们的state

从我们现在已有的代码里,可以知道我们的state需要3个属性(properties):items,hasErrored,isLoading,这个三个属性相应的需要三个独立的actions.

现在,这里讲讲为什么Action Creator和Action是不同的,他们也不是1:1的关系:我们需要第四个actiong creator来根据fetch data的不同状态调用其他三个action(creators).这第四个action creator几乎和我们原先的fetchData()一样,但是它不会直接的使用this.setState({isLoading:true})来设置状态,我们将dispatch一个action去做同样的事情:dispatch(isLoading(true)).

本文由俄罗斯贵宾会发布于Web前端,转载请注明出处:你需要 Mobx 还是 Redux ?

您可能还会对下面的文章感兴趣: