redux简介

1.使用场景

redux虽然好,也并不是什么情况下都要使用,如果在项目中遇到一下场景,你会自发的寻找一个工具来解决问题,这时就是redux隆重登场的时候了:

业务层面:

  • 用户的使用方式复杂

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

  • 多个用户之间可以协作

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

  • View要从多个来源获取数据

组件层面:

  • 某个组件的状态,需要共享

  • 某个状态需要在任何地方都可以拿到

  • 一个组件需要改变全局状态

  • 一个组件需要改变另一个组件的状态

如果通过上面的考虑,你还是不知道是否需要使用Rduex,那很有可能就是你不需要使用

2.设计思想

两句话概括:

1.  Web 应用是一个状态机,视图与状态是一一对应的。
2.  所有的状态,保存在一个对象里面。
Redux工作流程.png

结合这个图对原理做一下解释,也许有些名字还是不懂,但是可以先有个印象,接下来我们会做进一步的介绍。

用户会在view上进行一系列操作,而每一个操作都对应一个action。Store对象通过调用dispatch方法,传入上次的状态(previousState)和操作(action),经过Reducers针对不同的action,改变previousState的值,然后返回一个newState,然后Stroe对象可以通过getState()方法,获取这个新状态。最后进行相应的视图更新。web应用所有的状态都保存在state中,每一个state都对应一个视图。

3. 核心概念

state:

整个Redux都是围绕状态进行的操作,所有的 state 都被保存在一个单一对象中。建议在写代码前先想一下这个对象的结构。如何才能以最简的形式把应用的 state 用对象描述出来。如果应用较复杂在创建store对象时,可以这些意见:http://cn.redux.js.org/docs/recipes/reducers/NormalizingStateShape.html。应用的初始状态可以在创建Store时传入,也可以根据reducer中state的默认值自动生成。

action:

每一个action都有个对应的操作,所以action中有这个操作的名字(类型)和需要的数据。action 是把数据从应用传到 store 的有效载荷,它是Store数据的唯一来源。一般来说你会通过 store. dispatch() 将 action 传到 store;它本质上是JavaScripte的普通对象:

{
  type: “ADD_TODO”,
  text: 'Build my first Redux app'
}

对象中type字段一般是必须的,用来描述操作的类型。其他的参数都可以根据操作的需要,进行自定义扩充。

这个例子action是像toDolist中添加一项,text字段是这个todo的内容。很明显我们不能每一个不同的内容都有个action,这个需要action creator:

let todo  = function (text) {
    return {
    type: "ADD_TODO",
    text
  }
}  

reducer:

action只是描述了一个操作,而没有描述如何针对这个action进行状态更改。reducer则描述了如何根据传入的action,对状态做相应的改变。

在编写reducer函数是要保证它的纯净性,不要进行以下操作:

  1. 不要修改传入的参数,尤其是在处理state时,要返回新的对象

  2. 执行有副作用的操作,如 API 请求和路由跳转

  3. 调用非纯函数,如 Date.now() 或 Math.random()

要保证只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算

Store:

Store 就是把state、action、 reducers联系到一起的对象。Store 有以下职责

  1. 维持应用的 state

  2. 提供 getState() 方法获取 state

  3. 提供 dispatch(action) 方法,然后通过reducer更新 state

  4. 通过 subscribe(listener) 注册监听器

  5. 通过subscribe(listener) 返回的函数注销监听器

Redux应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合而不是创建多个 store。

// createStore的简单实现
const createStore = (reducer) => { // 源码支持传入三个参数,reducer, preloadedState, enhancer
  let state;
  let listeners = [];

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach(listener => listener());
  };

  const subscribe = (listener) => {
    listeners.push(listener);
    return () => {
      listeners = listeners.filter(l => l !== listener);
    }
  };

  dispatch({}); //获得初始状态

  return { getState, dispatch, subscribe };
};

数据流:

严格的单向数据流是 Redux 架构的设计核心:

  1. 用户行为引起调用store.dispatch(actioin)

  2. 调用reduser函数:两个参数,当前的state和action

  3. 返回计算过的state

  4. 触发所有订阅store.subscribe(listener)

4. 简单实例

下面是一个toDoList的简单实例

http://jsbin.com/noxalaw/edit?html,js,console,output

5.高级用法

异步action:

上面讲到的action触发的动作都是同步的,如果有异步的action该如何处理呢?同步时只需触发一个action,当异步时要要触发三个action,分别为:

    1. 发起:操作开始时,送出一个 Action,触发 State 更新为"正在操作"状态,View 重新渲染

    2. 成功: 操作结束后,再送出一个 Action,触发 State 更新为"操作结束"状态,View 再一次重新渲染

    3. 失败: 请求失败时发出action,触发state更新,view显示错误状态

这三个action对应的描述如下:

{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }

给出一个异步action的示例:

const fetchPosts = subreddit => dispatch => {
  dispatch(requestPosts(subreddit)) //开始action
  return fetch(`https://www.reddit.com/r/${subreddit}.json`)
    .then(response => response.json())
    .then(json => dispatch(receivePosts(subreddit, json))) //成功action
}

Actions creator现在返回了一个函数,而在同步时返回一个普通对像。在默认情况下dispatch的参数只能是一个对象,不能是一个函数。这个时候就需要引入中间件: redux-thunk,让dispatch参数可以传入函数。

所以,异步操作的一种解决方案就是,

  1. 一个返回函数的 Action Creator,

  2. 使用redux-thunk中间件改造store.dispatch,使其能支持函数作为参数

至于中间件是如何操作的,这里就不细述了,有兴趣的同学可以参考http://cn.redux.js.org/docs/advanced/Middleware.html

// redux-thunk的源码
function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

6. 异步action的完整示例

http://jsbin.com/xexax/edit?html,js,console,output

参考:

http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html

http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html

https://tech.meituan.com/redux-design-code.html

https://cn.redux.js.org/docs/basics/

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