深入Redux架构

一、什么情况需要redux?

    1、用户的使用方式复杂

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

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

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

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

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

二、设计思想

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

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

三、Redux的工作流程

Redux工作流程

首先,用户发出Action

store.dispatch(action);

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

let nextState = todoApp (previousState , action);

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

// 设置监听函数

store.subscribe(listener);

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

function listener () {

    let nextState=store.getState();

    component.setState( newState );

}

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

四、Redux  API

store

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

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

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

import { createStore } from 'redux';

const store = createStore ( fn );

State

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

import { crateStore } from 'redux';

const store = createStore ( fn );

const state =  store.getState();

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

Action

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。

Action Creator

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

const ADD_TODO = '添加TODO';

function addTodo (text) {

    return {

        type: ADD_TODO,

        text

    }

}

const action = addTodo(' Learn Redux');

Store.dispatch()

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

import { createStore } from 'redux';

const  store=createStore ( fn );

store.dispatch({

    type : 'ADD_TODO';

    payload : 'Learn Redux'

});

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

import { createStore } from 'redux';

const  store=createStore ( fn );

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

const ADD_TODO = '添加TODO';

function addTodo (text) {

    return {

        type: ADD_TODO,

        text

  }

}

Reducer

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(reducer);

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

const ADD_TODO='添加Todo';

function addTodo(text){

  return {

    type:ADD_TODO,

    text

  }

}

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

Store.subscribe()

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

import { createStore } from 'redux';

const store = createStore (reducer);

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

const ADD_TODO='添加Todo';

function addTodo(text){

  return {

    type:ADD_TODO,

    text

}

}

store.subscribe(listener);

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

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

let unsubscribe = store.subscribe( () =>

    console.log(store.getState())

};

unsubscribe();

五、中间件与异步操作

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

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

middleware

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

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

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

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

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

中间件的用法

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

import { applyMIddleware , crateStore } from 'redux';

import  createLogger from 'redux-logger';

const logger= createLogger();


const store = createStore (

    reducer,

    applyMiddleware(logger)

);

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

这里有两点需要注意:

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

import { applyMIddleware , crateStore } from 'redux';

import  createLogger from 'redux-logger';

const logger= createLogger();


const store = createStore(

    reducer,

    initial_state,

    applyMiddleware(logger)

);

(2)中间件的次序有讲究

const store = createStore(

    reducer,

    applyMiddleware(thunk,promise,logger)

);

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

六、异步操作的基本思路

同步操作只要发出一种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再一次重新渲染

redux-thunk中间件

异步操作至少要送出两个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(reactjs'));

// 使用方法二

store.dispatch ( fetchPosts (reactjs')).then(()=>

      console.log(store.getState())

);

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

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

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

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

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

(4)异步操作结束之后,再发出一个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(thunk)

};

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

*七、React-Redux的用法

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

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

React & Redux

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

UI 组件

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)

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

mapStateToProps

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

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

const mapStateToProps = (state) =>{

    return {

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

}

}

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

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

const getVisibleTodos =(todos ,  filter) =>{

  switch (filter){

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

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 : (filter) => {

    type:  'SET_VISIBILITY_FILTER',

    filter : filter

};

}

<Provider>组件

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(todoApp);

render(

    <Provider store={store}>

        <App/>

     </Provider>

     document.getElementById('root')

)

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

React-Router路由库

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

const Root =( { store } ) => (

    <Provider store ={store}>

        <Router>

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

        </Router>

    </Provider>

);


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容