你需要 Mobx 还是 Redux ?

你需要 Mobx 还是 Redux ?

2018/02/22 · JavaScript · mobx, Redux

原文出处: 熊建刚的博客   

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

索引

  • 1 前言
  • 2 状态管理
  • 3 Mobx VS Redux
    • 3.1 Redux
    • 3.2 Mobx
    • 3.3 函数式和面向对象
    • 3.4 单一store和多store
    • 3.5 JavaScript对象和可观察对象
    • 3.6 不可变(Immutable)和可变(Mutable)
    • 3.7 mobx-react和react-redux
  • 4 选择Mobx的原因
  • 5 不选择Mobx的可能原因
  • 6 代码对比
    • 6.1 架构
    • 6.2 注入Props
    • 6.3 定义Action/Reducer等
    • 6.4 异步Action
  • 7 一些想法
  • 8 参考

图片 1

前言

当我们使用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

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

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

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

一、什么情况需要redux?

1、用户的使用方式复杂

2、不同身份的用户有不同的使用方式(比如普通用户和管理员)

3、多个用户之间可以协作

4、与服务器大量交互,或者使用了WebSocket

5、View要从多个来源获取数据

简单说,如果你的UI层非常简单,没有很多互动,Redux就是不必要的,用了反而增加复杂性。多交互、多数据源的场景就比较适合使用Redux。

1、Web应用是一个状态机,视图与状态是一一对应的。

2、所有的状态,保存在一个对象里。

状态管理

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

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

三、Redux的工作流程

图片 2Redux工作流程

首先,用户发出Action

store.dispatch;

然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的Action。Reducer会返回新的State。

let nextState = todoApp (previousState , action);

State一旦变化,Store就会调用监听函数。

// 设置监听函数

store.subscribe;

lisrener 可以通过store.getState()得到当前状态。如果使用的是React,这时可以触发重新渲染View。

function listener () {

let nextState=store.getState();

component.setState( newState );

}

如果现在没理解以上流程,不要急,看完以下API就差不多能动的Redux的核心机制啦。

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 API

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列表.

store

Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个Store。

Redux提供createStore这个函数,用来生成Store。

下面代码中,createStore函数接受另一个函数作为参数,返回新生成的Store对象。

import { createStore } from 'redux';

const store = createStore ;

Store 对象包含所有的数据,如果想得到某个时点的数据,就要对Store生成快照。这种时点的数据集合,就叫做State。当前此刻的State,可以通过store.getState()拿到。

import { crateStore } from 'redux';

const store = createStore ;

const state = store.getState();

Redux 规定,一个State对应一个View。只要State相同,View就相同,你知道State,就知道View是什么样,反之亦然。

State的变化,会导致VIew 的变化。但是,用户接触不到State,只能接触到View。所以,State的变化必须是VIew导致的。

Action就是View发出的通知,表示state应该要发生变化啦。

Action是一个对象,其中的type属性是必须的,表示Action的名称。其他属性可以自由设置。

const action = {

type : 'ADD_TODO',

payload ; 'learn Redux'

};

上述代码中,Action的名称是ADD_TODO,它携带的信息是字符串Learn Redux。

可以这样理解,Action描述当前发生的事情。改变State的唯一办法,就是使用Action,它会运送数据到Store。

view 要发送多少种信息,就会有多少种Action。如果都手写,会很麻烦。可以定义一个函数来生成Action,这个函数就叫做ActionCreator。

const ADD_TODO = '添加TODO';

function addTodo {

return {

type: ADD_TODO,

text

}

}

const action = addTodo(' Learn Redux');

store.dispatch()是View发出Action的唯一方法。

import { createStore } from 'redux';

const store=createStore ;

store.dispatch({

type : 'ADD_TODO';

payload : 'Learn Redux'

});

上面的代码中,store.diapatch接受了一个Action对象作为参数,将它发出去,结合ActionCreator,这段代码可以改写如下。

import { createStore } from 'redux';

const store=createStore ;

store.dispatch(add Todo('Learn Rudux'));

const ADD_TODO = '添加TODO';

function addTodo {

return {

type: ADD_TODO,

text

}

}

Store 收到Action以后,必须给出一个新的State,这样View才会发生变化。这种State的计算过程就叫做Reducer。Ruducer是一个函数,他接受Action和当前State作为参数,返回一个新的State,下面是一个实际的例子

const defaultState = 0;

const reducer = (state=defaultState,action)=>{

switch (action.type){

case 'ADD';

return state +action.payload;

default:

return state;

}

};

const state = reducer (1, {

type : 'ADD',

payload: 2

});

上面代码中,reducer函数收到名为ADD的Action以后,就返回一个新的State,作为加法的计算结果。其他运算的逻辑,也可以根据Action的不同来实现。

实际应用中,Reducer函数不用像上面那样手动调用,store.dispatch方法会触发Reudcer自动执行。为此,Store需要知道ReduCer函数,做法就是在生成Store的时候,将Reducer传入createStore方法。

import {createStore } from 'redux';

const store = createStore;

store.dispatch(addTodo('Learn Redux'));

const ADD_TODO='添加Todo';

function addTodo{

return {

type:ADD_TODO,

text

}

}

上面代码中,createStore接受Reducer作为参数,生成一个新的Store,以后每当store.dispatch发过来一个新的Action,就会自动调用Reducer,得到新的State。

Store允许使用store.subscribe方法设置监听函数,一旦State发生变化,就自动执行这个函数。

import { createStore } from 'redux';

const store = createStore ;

store.dispatch(addTodo('Learn Redux'));

const ADD_TODO='添加Todo';

function addTodo{

return {

type:ADD_TODO,

text

}

}

store.subscribe;

显然,只要把View的更新函数(对于React项目,就是组件的render方法和setState方法)放入listen,就会实现view的自动渲染。

store.subscribe方法返回一个函数,调用这个函数就可以解除监听。

let unsubscribe = store.subscribe =>

console.log(store.getState

};

unsubscribe();

Mobx

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

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

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

其原理如图:

图片 3

  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出来的,每次只显示一个).

五、中间件与异步操作

一个关键的问题没有解决:异步操作怎么办?Action发出以后,Reducer立即算出State,这叫同步;Action发出以后,过一段时间再执行Reducer,这叫异步。

怎么才能Reducer在异步操作结束后自动执行呢?这就要用到新的工具:中间件(middleware)

图片 4middleware

为了理解中间件,然我们站在框架作者的角度思考问题:如果要添加功能,你会在那个环节添加?

Reducer:纯函数,只承担计算State的功能,不适合承担其他功能,也承担不了,因为理 论上,纯函数不能进行读写操作。

View :与State一一对应,可以看作是State的视觉层,也不合适承担其他功能。

Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。

想来想去,只有发送Action的这个步骤,即store.dispatch()方法,可以添加功能

这里只介绍如何使用中间件

import { applyMIddleware , crateStore } from 'redux';

import createLogger from 'redux-logger';

const logger= createLogger();

const store = createStore (

reducer,

applyMiddleware

);

上面代码中,redux-logger提供了一个生成器createLogger,可以生出日志中间件logger。然后将它放在applyMiddleware方法之中,传入createState方法,就完成了store.dispatch()的功能增强。

这里有两点需要注意:

createStore方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware就 是第三个参数了。

import { applyMIddleware , crateStore } from 'redux';

import createLogger from 'redux-logger';

const logger= createLogger();

const store = createStore(

reducer,

initial_state,

applyMiddleware

);

中间件的次序有讲究

const store = createStore(

reducer,

applyMiddleware(thunk,promise,logger)

);

上面代码中,applyMiddleware方法的三个参数,就是三个中间件。有的中间件有次序要求,使用前要查一下文档。比如,logger就一定要放到最后,否则输出结果会不正确。

函数式和面向对象

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实时的出现了.

六、异步操作的基本思路

同步操作只要发出一种Action即可,异步操作的差别是他要发出三种Action。

1)操作发起时的Action

2)操作成功时的Action

3)操作失败时的Action

以向服务器取出数据为例,三种Action可以有两种不同的写法

// 写法一:名称相同,参数不同

{ type : 'FETCH_POSTS' }

{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }

{ type: 'FETCH_POSTS', status: 'success', response: { ... } }

// 写法二:名称不同

{ type: 'FETCH_POSTS_REQUEST' }

{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }

{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }

除了Action种类不同,同步操作的State也要进行改造,反映不同的操作状态,下面是State的一个例子

let state = {

isFetching : true,

didInvalidate : true,

lastUpdated: 'xxxx'

};

上面代码中,State的属性isFetching表示是否在抓取数据,didInvalidate表示数据是否过时,lastUpdated表示上一次更新的时间

现在,整个异步操作的思路清楚啦。

1)操作开始时,送出一个Action,触发State更新为“正在操作”状态,View重新渲染

2)操作结束后,再送出一个Action,触发State更新为“操作结束”状态,View再一次重新渲染

异步操作至少要送出两个Action:用户触发第一个Action,这个跟同步一样,没有问题;如何才能在操作结束时,系统自动送出第二个Action呢?

奥妙就在Action Creator之中。

class AsyncApp extends Component {

componentDidMount() {

const { dispatch,selectedPost } =this.props

dispatch (fetchPosts(selectedPost))

}

}

上面代码是一个异步组件的例子,加载成功后(componentDidMouth方法),他送出了(dispatch方法)一个Action,向服务器要求数据fetchPosts(selectedSUbredddit).这里的fetchPosts就是Action Creator

const fetchPosts = postTitle => ( dispatch , getState) => {

dispatch ( requestPosts( postTitle));

return fetch ( '/some/API/${postTitle}.json')

.then (response = > response.json

.then( json=> dispatch(receivePosts(postTitle,json)));

};

};

// 使用方法一

store.dispatch(fetchPosts);

// 使用方法二

store.dispatch ( fetchPosts ).then=>

console.log(store.getState

);

上面代码中,fetchPosts是一个Action Creator,返回一个函数,这个函数执行后,先发出一个Action(requestPosts(postTitle)),然后进行操作。拿到结果后,先将结果转换为JSON格式,然后再发出一个Action(recervePosts(postTitle,json)).

上面代码中,有几个地方需要注意。

fetchPosts返回一个函数,而普通的Action Creator默认返回一个对象

返回的函数的参数是disPatchgetState这两个Redux方法,普通的ActionCreator的参数 是Action的内容。

在返回的函数之中,先发出一个Action(requestPosts(postTitle)),表示操作开始

异步操作结束之后,再发出一个Action(ReceivePosts(postTitle,json)),表示操作结束

这样的处理,就解决了自动发送第二个Action的问题,但是又带来一个新的问题,Action是由store.dispatch方法发送的。这时,就要使用中间件redux-thunk

import { createStore, applyMiddware } from 'redux';

import thunk form 'redux-thunk';

import reducer from './reducers';

const store = creareStore(

reducer,

applyMiddleware

};

上面代码使用redux-thunk中间件,改造store.dispatch,使的后者可以接受函数作为参数。因此,异步操作的第一种解决方案就是,写一个返回函数的Action Creator,然后使用redux-thunk中间件改造store.dispatch.

单一store和多store

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

转变到Redux

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

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

*七、React-Redux的用法

为了方便使用,Redux的作者封装了一个React专用的库React-Redux本文主要介绍它

这个库可以选用的。实际项目中,你应该权衡一下,是直接使用Redux,还是使用React-Redux。后者虽然提供了便利,但是需要掌握额外的Api,并且要遵循它的组件拆分规范。

图片 5React & Redux

React-Redux将所有的组件分为两大类:UI组件(presentational component)和容器组件(container component)。

UI组件有一下几个特征。

1)只负责UI的呈现,不带有任何业务逻辑。

2)没有状态(即不使用this.state这个变量)

3)所有数据都由参数(this.props)提供

4)不使用任何Redux的API

下面就是一个UI组件的例子

const Title =

value = > <h1> { value} </h1>;

因为不含有状态,UI组件又称为“纯组件”,即它纯函数一样,纯粹由参数决定他的值

容器组件的特征恰恰相反。

1)负责管理数据和业务逻辑,不负责Ui的呈现

2)带有内部状态

3)使用Redux的ApI

总之,只要记住一句话就可以了:UI组件负责Ui的呈现,容器组件负责管理数据和逻辑

你可能会问,如果一个组件既有UI又有业务逻辑,怎么办? 答案是,将它拆分成下面的结构:外面是一个容器组件,里面包含了一个UI组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。

React-Redux提供connect方法,用于从UI组件生成容器组件。connect的意思,就是将这两种组件连起来。

connect方法的完整API如下。

import { connect } from 'react-redux';

const VIsibleTodoList = connect(

mapStateToProps,

mapDisPatchToProps

)

上面代码中,TodoList是UI组件,VisibleTodoList就是由React-Redux通过connect方法自动生成的容器组件。connect方法接受两个参数:mapStateToPropsmapDispatchToProps。他们定义了UI组件的业务逻辑,前者负责输入逻辑,即将state映射到UI组件的参数,后者负责输出逻辑,即将用户对UI组件的操作映射成Action。

mapStateToProps是一个函数,它的作用就像他的名字一样,建立一个从state对象到props对象的映射关系。

作为函数,mapStateProps执行后应该返回一个对象,里面每个键值对就是一个映射,请看下例

const mapStateToProps = =>{

return {

todos:getVisibleTodos(state.todos,state.visibilityFilter)

}

}

上面代码中,mapStateToProps是一个函数,他接受state作为参数,返回一个对象。这个对象有一个todos属性,代表UI组件的同名参数,后面的getVIsibleTodos也是一个函数,可以从state算出todos的值。

下面就是getVisibleTodos的一个例子,用来算出Todos。

const getVisibleTodos =(todos , filter) =>{

switch {

case 'SHOW_ALL';

return todos

case 'SHOW_COMPLETED';

return todos.filter( t => t.completed)

case 'SHOW_ACTIVE' ;

return todos.filter ( t=> ! t.completed)

default:

throw new Error ( ' Unknown filter: ' +filter)

}

}

mapStateToProps会订阅Store,每当state更新的时候,就会自动执行,重新计算UI组件的参数,从而触发UI组件的重新渲染,

mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象。

// 容器组件的代码

// <FilterLink filter = "SHOW_ALL">

// ALL

// </FilterLink>

const mapStateToProps = (state, ownProps) =>{

return{

active : ownProps.filter===state.visibilityFilter

}

}

使用ownProps作为参数后,如果容器组件的参数发生变化,也会引发UI组件重新渲染。

connect方法可以省略mapStateToProps参数,那样的话,UI组件就不会订阅Store,就是说Store的更新不会引起UI组件的更新。

mapDispatchToProps是connect函数的第二个参数,用来建立UI组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当做Action,传给Store,他可以是一个函数,也可以是一个对象。

如果mapDispatchToProps是一个函数,会得到dispatch和ownProps(容器组件的props对象)两个参数。

const mapDispatchToProps = {

dispatch,

ownprops

} = > {

return {

onClick: () =>{

dispatch({

type: 'SET_VISIBILITY_FILTER',

filter : ownProps.filter

});

}

};

}

从上面代码可以看出,mapDispatchToProps作为函数,应该返回一个对象,该对象的每一个键值对都是一个映射,定义了UI组件的参数怎么发出Action

如果mapDispatchToProps是一个对象,它的每个键名也是对应的UI组件的同名参数,键值应该是一个函数,会被当做Action Creator,返回的Action会由Redux自动发出。举例来说,上面的mapDispatchToProps写成对象就是下面这样的

const mapDispatchToProps = {

onClick : => {

type: 'SET_VISIBILITY_FILTER',

filter : filter

};

}

connect 方法生成容器组件后,需要让组件拿到state对象,才能生成UI组件的参数。React-Redux提供的Provider组件,可以让容器组件拿到state

import { Provider } from 'react-redux'

import { createStore } from 'redux'

import todoApp from './reducers'

import App form ' ./components/App'

let store = createStore;

render(

<Provider store={store}>

<App/>

</Provider>

document.getElementById

)

上面代码中,Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state啦。

使用React-Route的项目,与其他项目没有不同之处,也是使用Provider在Router外面包了一层,毕竟Provider的唯一功能就是传入store对象。

const Root =( { store } ) => (

<Provider store ={store}>

<Router>

<Route path="/" component= { App } />

</Router>

</Provider>

);

后记:本文属于笔记,所有内容出自是云随风的博客,谢谢大神

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)).

mobx-react和react-redux

使用Redux和React应用连接时,需要使用react-redux提供的Providerconnect

  1. Provider:负责将Store注入React应用;
  2. connect:负责将store state注入容器组件,并选择特定状态作为容器组件props传递;

对于Mobx而言,同样需要两个步骤:

  1. Provider:使用mobx-react提供的Provider将所有stores注入应用;
  2. 使用inject将特定store注入某组件,store可以传递状态或action;然后使用observer保证组件能响应store中的可观察对象(observable)变更,即store更新,组件视图响应式更新。

创建actions

在actions目录下创建itmes.js文件,其中包含我们的action creators.创建三个简单的actions.

    export function itemsHasErrored(bool) {
   return {
       type: 'ITEMS_HAS_ERRORED',
       hasErrored: bool
   };
}
export function itemsIsLoading(bool) {
   return {
       type: 'ITEMS_IS_LOADING',
       isLoading: bool
   };
}
export function itemsFetchDataSuccess(items) {
   return {
       type: 'ITEMS_FETCH_DATA_SUCCESS',
       items
   };
}


    ```











如前面提到的,action creators似能返回函数的函数,使用`export`输出单个action creators,便于在代码中使用.
   第二个action creators接受一个布尔值(true/false)最为参数,返回一个有意义的`type`和布尔值,分配合适的属性.
   第三个,`itemsFetchSuccess()`,当数据成功返回以后,传递数据作为`items`属性的值.通过ES6的魔术属性缩写,我们能够返回一个对象含有属性名叫做`items`,他的值是`items`的数组.

  (Note: that the value you use for type and the name of the other property that is returned is important, because you will re-use them in your reducers)这一句不知道怎么翻译.
  现在,我们有了三个actions,代表我们的状态,把原来的组件方法`fetchData`该给`itemFetchDaga()`action creator.
  默认情况下,Redux action creators是不支持异步actions的,像是fetching data的操作,所以这里我们使用Redux Thunk.Thunk允许你在action creator里返回一个函数代替实际的action.内部函数接受`dispatch`和`getState`作为参数,但是我们仅仅使用`dispatch`.
  实际的简单例子中五秒以后将会触发`itemHasErrored()`函数.


export function errorAfterFiveSeconds() {
// We return a function instead of an action object
//dispatch作为参数传递给胖箭头函数
return (dispatch) => {
    setTimeout(() => {
        // This function is able to dispatch other action creators
        dispatch(itemsHasErrored(true));
    }, 5000);
};

}

现在我们知道thunk是什么了.编写`itemsFetchData()`.

export function itemsFetchData(url) {
return (dispatch) => {
    //已进入fetchdata,按顺序把isLoading state 由
    // false=>true
    dispatch(itemsIsLoading(true));
    //fetch执行实际的异步远程获取数据操作
    fetch(url) 
        .then((response) => {
            if (!response.ok) {//根据状态抛出错误
                throw Error(response.statusText);
            }
            //isLoading又改为false,加载Loading组件
            dispatch(itemsIsLoading(false));
            return response;
        })
        .then((response) => response.json())
        .then((items) => dispatch(itemsFetchDataSuccess(items)))
        .catch(() => 
        dispatch(itemsHasErrored(true)));
        //捕获错误以后HasError的状态 false=>true
};

}

## 创建我们的reducers

action定义好了以后,可以编写reducers接受actions,接着返回appliction的新状态(译注:实际上store中返回的对象都是一个新的对象,不是原对象的引用,这个就叫做immutable,facebook定义了一个immutable.js的技术实际是也是返回一个新的对象的硬拷贝,但是在原对象和修改对象之间共享了一部分内容,这一点有点微妙).
注意:在Redux中,所有的reducers不考虑action,都会调用,所以即就是没有action被应用,你也必须要返回一个原来的定义的`state`.

每一个reducer接收两个参数,之前的state和一个`action`对象.也可以使用ES6的属性来调用默认的参数设定到默认的`初始化state`.

在每个reducer内部,使用`switch`申明来决定到底哪个`action.type`相匹配.如果是简单的reducer,可能没有必要使用`switch`,理论上使用`if/else`可能更快一点.

如果`action.type`一点匹配,然后会返回和`action`相关的属性.和前面提到的一样,`type`和`action[属性名]`是在action creators里定义的.

好啦,了解到这些内容,在`reducers/item.js`中创建items reducers

export function itemsHasErrored(state = false, action) {
switch (action.type) {
case 'ITEMS_HAS_ERRORED':
return action.hasErrored;
default:
return state;
}
}
export function itemsIsLoading(state = false, action) {
switch (action.type) {
case 'ITEMS_IS_LOADING':
return action.isLoading;
default:
return state;
}
}
export function items(state = [], action) {
switch (action.type) {
case 'ITEMS_FETCH_DATA_SUCCESS':
return action.items;
default:
return state;
}
}

注意reducer根据结果store的state属性来命名,`action.type`没有必要想对应.前两个表达完整的意思,第三个`items()`就稍微有点不同.

这是因为,可能会有很多条件返回`items`数组:有可能返回所有的数组,有可能在删除dispatch以后返回`items`的次级结构.或者所有的items都被删除了,会返回一个空数组.

为了重新遍历,每一个reducer都会返回一个截然不同的state属性,不需要考虑reducer内部的条件到底有多少.刚开始花了我很长时间想明白这个问题.
单个的reducers创建好了以后,我们需要把单个的reducer合并(combine)成一个`rootReducer`,创建单一对象.

创建文件`reducers/index.js`

import { combineReducers } from 'redux';
import { items, itemsHasErrored, itemsIsLoading } from './items';
//由于每个reducer返回的都是一个对象
//所以这里的操作就是合并对象的操作,在underscore和loadsh
//里面可以找到合并js对象的代码
export default combineReducers({
items,
itemsHasErrored,
itemsIsLoading
});

我们从`items`里导入每个reducers,使用redux的`combineReducers()`函数来合并输出单一对象(译注:所以每一个reducer返回的对象的属性名应该是唯一的,否则就覆盖了,前面的内容表达过这个意思)
因为我们的reducer的名字和在store中使用的属性名一样,所以我们可以使用ES6的对象字面量.

注意,我有意提到了reducer的前缀,所以当我们的application变得比较复杂的时候,不能出现全局性的`hasErrored`和`isLoading`属性.可以使用不同的error和loading state,所以前缀可以给你很大的灵活性.例如

import { combineReducers } from 'redux';

import { items, itemsHasErrored, itemsIsLoading } from './items';
import { posts, postsHasErrored, postsIsLoading } from './posts';
export default combineReducers({
items,
itemsHasErrored,
itemsIsLoading,
posts,
postsHasErrored,
postsIsLoading
});

替代方法是,可以在import的时候使用别名.但是我更愿意使用独一无二的名字.

## 配置store,注入到你的app中

操作很直接,创建`store/configureStore.js`

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
}

现在在index.js中包含`<Provider />`组件,`configureStore`,配置`store`.包装app(`<ItemList />`),传递进去`store`和`props`.

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import ItemList from './components/ItemList';
const store = configureStore(); // You can also pass in an initialState here
render(
<Provider store={store}>
<ItemList />
</Provider>,
document.getElementById('app')
);

我知道,其实花了很多努力才到了这一步,但是随着设置的完成,我们就可以使用配置来操纵我们的组件了(译注:这里是意译,组件又称为木偶组件,意思很清楚吧?谁是拿着吊线的人呢?就是redux).

## 把组件转化为使用Redux store和方法

跳回到`components/ItemList.js`

在顶部导入需要的部分

import { connect } from 'react-redux';
import { itemsFetchData } from '../actions/items';

`connect`可以让组件链接到Redux的store,`itemsFetchData`是在开始写的action creator.我们仅仅需要导入actin creator.使用`dispatch`来触发actions.(译注:redux里面有很多内容其实是很基础的,例如这里,javascript的函数是一类对象,在js中函数是传引用的,所以函数名可以作为函数的引用,通过另一函数的参数来传递. 厉害 
		

本文由澳门威斯尼人平台登录发布于Web前端,转载请注明出处:你需要 Mobx 还是 Redux ?

相关阅读