前面我们介绍了 flux 架构以及其开源实现 redux,在这一节中,我们将完整的介绍 redux:

  • redux 介绍

    • redux 是什么

    • redux 概念

    • redux 三原则

  • redux Stores

  • redux Action

  • redux Reducers

  • redux 数据流动

3.1.1 redux 介绍

redux 是什么

Redux is a predictable state container for JavaScript apps译:Redux 是为 Javascript 应用而生的可预估的状态容器

定义有些抽象,简单来讲 redux 可以理解为基于 flux 和其他一些思想(Elm,函数式编程)发展出来的前端应用架构库,作为一个前端数据状态容器体现,并可以在 React 和其他任何前端框架中使用。

redux 概念

  • store: 同 flux,应用数据的存储中心

  • action: 同 flux ,应用数据的改变的描述

  • reducer: 决定应用数据新状态的函数,接收应用之前的状态和一个 action 返回数据的新状态。

  • middleware: redux 提供中间件的方式,完成一些 flux 流程的自定义控制,同时形成其插件体系

redux 三原则

单一的 store

区别于 flux 模式中可以有多个 state,整个应用的数据以树状结构存放在一个 state 对象中。

  1. console.log(store.getState())
  2. /* Prints
  3. {
  4. visibilityFilter: 'SHOW_ALL',
  5. todos: [
  6. {
  7. text: 'Consider using Redux',
  8. completed: true,
  9. },
  10. {
  11. text: 'Keep all state in a single tree',
  12. completed: false
  13. }
  14. ]
  15. }
  16. */

state 只读

state 包含的应用数据不能随意修改,修改数据的唯一方式是 dispatch action,action 描述要修改的信息(这和 flux 架构上是一致的,不过在设计上更加严格,见后面的 reducer 设计)。

  1. store.dispatch({
  2. type: 'COMPLETE_TODO',
  3. index: 1
  4. })
  5. store.dispatch({
  6. type: 'SET_VISIBILITY_FILTER',
  7. filter: 'SHOW_COMPLETED'
  8. })

数据的改变由纯函数生成

在 redux 中,应用数据的改变路径为:

  1. store.dispatch(action)

  2. newState = reducer(previousState, action)

  3. reducer 为纯函数

纯函数是函数是编程的思想,只要参数相同,函数返回的结果永远是一致的,并且不会对外部有任何影响(不会改变参数对象的数据)。也就是说 reducer 每次必须返回一个新状态,新状态由旧状态和 action 数据决定。

3.1.2 安装 redux

安装 redux 核心和 react-redux 集成进 react

  1. $ npm install --save redux
  2. $ npm install --save react-redux

3.1.3 redux store

在 redux 中 store 作为一个单例,存放了应用的所有数据,对外提供了如下的 API:

  1. 获取数据 store.getState()

  2. 通过触发 action 来更新数据 store.dispatch(action)

  3. pubsub 模式提供消息订阅和取消 store.subscribe(listener)

创建并初始化 store

redux 提供 createStore 方法来创建一个 store

  1. /**
  2. * [createStore description]
  3. * @param {[type]} reducer [reducer 函数]
  4. * @param {[type]} initialState [应用初始化状态]
  5. * @return {[type]} [description]
  6. */function createStore(reducer, initialState) {
  7. // ....
  8. }

创建一个 store :

  1. var redux = require('redux');
  2. var todoAppReducer = require('./reducers');
  3. var initalState = {
  4. todos: []
  5. };
  6. var store = redux.createStore(todoAppReducer, initialState);

触发 action

redux 修改应用数据的唯一方式为 store.dispatch(action)

eg:

  1. store.dispatch({
  2. type: 'ADD_TODO',
  3. title: 'new todo'
  4. })

消息订阅和取消

为了让用户监听应用数据改变,redux 在 store 集成了 pubsub 模式

订阅

  1. // 每次应用数据改变,输出最新的应用数据
  2. store.subscribe(function(){
  3. console.log(store.getState())
  4. })
  5. // 如果新增了 todo 会触发上面的订阅函数
  6. store.dispatch({
  7. type: 'ADD_TODO',
  8. title: 'new todo'
  9. })

取消

subscribe 返回的函数即为取消函数

  1. var unsubscribe = store.subscribe(function(){
  2. console.log(store.getState())
  3. })
  4. // ....
  5. unsubscribe();

设计应用数据结构

所有数据都存放在一个 store 中,以 todoApp 为例子,state 的数据结构可能为

  1. {
  2. visibilityFilter: 'SHOW_ALL',
  3. todos: [
  4. {
  5. text: 'Consider using Redux',
  6. completed: true,
  7. },
  8. {
  9. text: 'Keep all state in a single tree',
  10. completed: false
  11. }
  12. ]
  13. }

当应用变大的时候,数据结构可能没有这么简单,这时候需要找到一个较好的结构来设计应用数据,下面是两个 redux 在设计 state 上的 tip:

  1. 业务数据和 UI 状态数据分离,尽量避免 UI 状态数据放在 store 中,即便放在 store 中也好和业务数据分离。

  2. 避免嵌套,在一个复杂的场景,数据对象可能很深,出现多层,那在设计的时候可以通过 id 的方式来引用,可以参考 normalizr的简化方式

3.1.4 redux action

我们已经知道 action 就是数据修改的描述信息,不过在实际使用过程中需要理解下面的这些规范:

  1. action 描述数据结构

  2. action 类型常量

  3. action creator

action 描述数据结构

redux 对 action 对象的数据结构做了简单规范,每个对象必须包含一个字段 type,应用通过 type 来识别 action 类型,其他字段不做任何限制。

eg:

  1. {
  2. type: "ADD_TODO",
  3. text: 'Build my first Redux app'
  4. }
  5. {
  6. type: "REMVOE_TODO",
  7. index: 1
  8. }
  9. {
  10. type: "TOGGLE_TODO",
  11. id: 'a1s2d1'
  12. }

action 类型常量

为了项目的规范,通常把 action type 定义为名称常量,放在一个单独的文件中管理,这在大型项目中是一个很好的习惯。

eg:

  1. var ADD_TODO = "ADD_TODO"
  2. {
  3. type: ADD_TODO,
  4. text: 'Build my first Redux app'
  5. }

action creator

在 flux 模式小节已经介绍过,为了规范化 action 通过 action creator 来创建

  1. function addTodo(text) {
  2. return {
  3. type: ADD_TODO,
  4. text: text
  5. }
  6. }

原来的 dispatch 的使用改为了

  1. // 旧的方式
  2. store.dispatch({
  3. type: ADD_TODO,
  4. text: text
  5. })
  6. // 新的方式
  7. store.dispatch(addTodo(text))

3.1.5 redux reducer

reducer 应该是最为陌生的概念,理解 reducer 是理解 redux 的关键,牢记 reducer 决定应用数据的改变

reducer 基础

  1. /**
  2. * [reducer description]
  3. * @param {[type]} previewsState [之前状态]
  4. * @param {[type]} action [redux action]
  5. * @return {[type]} newState [新状态]
  6. */function reducer(previewsState, action) {
  7. var newState;
  8. switch(action.type) {
  9. case ..
  10. case ..
  11. default:
  12. newState = previewsState
  13. }
  14. return newState;
  15. }

首先 reducer 是一个纯函数,接收到之前的应用状态和 action 并返回一个新状态,为了每次返回一个新状态,可以通过 Object.assign() 方法返回一个新的对象,也可以使用 Immutable.js (后面的章节会讲解 Immutable.js)。

  1. function todoApp(state, action) {
  2. state = initialState
  3. switch (action.type) {
  4. case SET_VISIBILITY_FILTER:
  5. return Object.assign({}, state, {
  6. visibilityFilter: action.filter
  7. })
  8. default:
  9. return state
  10. }
  11. }

redux 会在初始的时候调用一次 reducer (这时参数 previewsState 为 undefined), 可以借用这个机会返回应用初始状态数据。

eg:

  1. // reducers/todoApp.jsvar initialState = {todos: ...};
  2. function todoApp(state, action) {
  3. if (typeof state === 'undefined') {
  4. return initialState
  5. }
  6. // 返回默认值return state
  7. }
  8. module.exports = todoApp;

总结需要注意的点:

  1. 纯函数特性,不能修改 previewsState,不能调用异步 API,无论什么时候,传入相同的参数都能立即返回相同的结果(不能调用 Math.random, Data.now 这些函数,因为会导致不同的结果)

  2. 默认返回 previewsState (在 action 不会得到处理的情况)

  3. 处理 state 为 undefined 的情况

reducer 组合

一个 todo 应用可能有很多操作,在真实场景上面的 todoApp reducer 可能膨胀为如下的结构:

  1. function todoApp(state = initialState, action) {
  2. switch (action.type) {
  3. case SET_VISIBILITY_FILTER:
  4. return Object.assign({}, state, {
  5. visibilityFilter: action.filter
  6. })
  7. case ADD_TODO:
  8. return Object.assign({}, state, {
  9. todos: [
  10. ...state.todos,
  11. {
  12. text: action.text,
  13. completed: false
  14. }
  15. ]
  16. })
  17. case TOGGLE_TODO:
  18. return Object.assign({}, state, {
  19. todos: state.todos.map((todo, index) => {
  20. if(index === action.index) {
  21. return Object.assign({}, todo, {
  22. completed: !todo.completed
  23. })
  24. }
  25. return todo
  26. })
  27. })
  28. default:
  29. return state
  30. }
  31. }

这时候可以对 todoApp reducer 做拆分,将它拆分为多个不同的 reducer,todoApp reducer 的作用只是组合其他 reducer 。

拆分后可以如下:

  1. function todos(state = [], action) {
  2. switch (action.type) {
  3. case ADD_TODO:
  4. return [
  5. ...state,
  6. {
  7. text: action.text,
  8. completed: false
  9. }
  10. ]
  11. case TOGGLE_TODO:
  12. return state.map((todo, index) => {
  13. if (index === action.index) {
  14. return Object.assign({}, todo, {
  15. completed: !todo.completed
  16. })
  17. }
  18. return todo
  19. })
  20. default:
  21. return state
  22. }
  23. }
  24. function visibilityFilter(state = SHOW_ALL, action) {
  25. switch (action.type) {
  26. case SET_VISIBILITY_FILTER:
  27. return action.filter
  28. default:
  29. return state
  30. }
  31. }
  32. function todoApp(state = {}, action) {
  33. return {
  34. visibilityFilter: visibilityFilter(state.visibilityFilter, action),
  35. todos: todos(state.todos, action)
  36. }
  37. }
  38. module.exports = todoApp

todoApp 不再负责整个应用状态的修改,而是将责任分配给了其他的 reducer, 每个子 reducer 相互独立,负责各自的责任,这个时候应用数据的改变不是靠一个 reducer 改变的,而是由多个 reducer 组合起来,这是 redux 应用的基础,叫做 reducer 组合。

可以发现 reducer 的组合和状态数据的结构是相同的,都可以理解为树状结构。 state 根对象有一个根 reducer (createState 传入的 reducer)这里是 todoApp , state 对象下面的第一级数据对应不用的 reducer,visibilityFilter 和 todos , 理解起来这也是一个典型的 分而治之策略。

对于 reducer 的组合,redux 提供了 combineReducers() 方法来简化,上面的 todoApp 可以简化为:

  1. var todoApp = redux.combineReducers({
  2. visibilityFilter: visibilityFilter,
  3. todos: todos
  4. })

这样的写法和上面的写法作用完全相同

3.1.5 redux 数据流动

redux 继承 flux 最核心的地方是 数据的单向流动,
上面已经详细介绍了 redux 的各个概念,下面将这些概念结合起来,看看数据怎么在 redux 中流动的。

第一步:调用 store.dispatch(action)

可以在任何地方触发 dispatch,例如 UI 交互,API 调用

第二步: Redux store 调用 rootReducer

redux 收到 action 过后,调用根 reducer 并返回最新的状态数据。(根 reducer 内部组合其他 reducer 返回部分的最新状态)

第三步:接收新状态并 publish 给订阅者

当 rootReducer 返回最新的状态后,通知订阅函数 store.subscribe(listener) 。在 React 中,可以订阅状态更新,在订阅函数中获取最新的状态过后,修改根组件的数据:

  1. component.setState(newState)

3.1 开始使用 redux的更多相关文章

  1. RxJS + Redux + React = Amazing!(译一)

    今天,我将Youtube上的<RxJS + Redux + React = Amazing!>翻译(+机译)了下来,以供国内的同学学习,英文听力好的同学可以直接看原版视频: https:/ ...

  2. 通过一个demo了解Redux

    TodoList小demo 效果展示 项目地址 (单向)数据流 数据流是我们的行为与响应的抽象:使用数据流能帮我们明确了行为对应的响应,这和react的状态可预测的思想是不谋而合的. 常见的数据流框架 ...

  3. RxJS + Redux + React = Amazing!(译二)

    今天,我将Youtube上的<RxJS + Redux + React = Amazing!>的后半部分翻译(+机译)了下来,以供国内的同学学习,英文听力好的同学可以直接看原版视频: ht ...

  4. redux学习

    redux学习: 1.应用只有一个store,用于保存整个应用的所有的状态数据信息,即state,一个state对应一个页面的所需信息 注意:他只负责保存state,接收action, 从store. ...

  5. webpack+react+redux+es6开发模式

    一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入 ...

  6. Redux初见

    说到redux可能我们都先知道了react,但我发现,关于react相关的学习资料很多,也有各种各样的种类,但是关于redux简单易懂的资料却比较少. 这里记录一下自己的学习理解,希望可以简洁易懂,入 ...

  7. react+redux教程(八)连接数据库的redux程序

    前面所有的教程都是解读官方的示例代码,是时候我们自己写个连接数据库的redux程序了! 例子 这个例子代码,是我自己写的程序,一个非常简单的todo,但是包含了redux插件的用法,中间件的用法,连接 ...

  8. react+redux教程(七)自定义redux中间件

    今天,我们要讲解的是自定义redux中间件这个知识点.本节内容非常抽象,特别是中间件的定义原理,那多层的函数嵌套和串联,需要极强逻辑思维能力才能完全消化吸收.不过我会多罗嗦几句,所以不用担心. 例子 ...

  9. react+redux教程(六)redux服务端渲染流程

    今天,我们要讲解的是react+redux服务端渲染.个人认为,react击败angular的真正“杀手锏”就是服务端渲染.我们为什么要实现服务端渲染,主要是为了SEO. 例子 例子仍然是官方的计数器 ...

  10. react+redux教程(五)异步、单一state树结构、componentWillReceiveProps

    今天,我们要讲解的是异步.单一state树结构.componentWillReceiveProps这三个知识点. 例子 这个例子是官方的例子,主要是从Reddit中请求新闻列表来显示,可以切换reac ...

随机推荐

  1. python+appium学习总结

    经过了这个月的学习,今天终于完成了公司APP系统的自动化的脚本的编写. 通过单元测试框架UNITTEST,进行脚本的连跑,本来还想把测试数据统一写到EXCEL表格内,实现脚本与数据的分离. 后来发现增 ...

  2. MySQL 小数处理函数 round 和 floor

    一. 在mysql中,round函数用于数据的四舍五入,它有两种形式: 1.round(x,d)  ,x指要处理的数,d是指保留几位小数 这里有个值得注意的地方是,d可以是负数,这时是指定小数点左边的 ...

  3. mongo 大数据量更新注意事项

    1.大数据量最好在本地执行更新. 2.在客户端执行更新时需要注意serve活动时间(10分钟),10分钟内解决不了的使用batchSize  或者db.getCollection("&quo ...

  4. 弹出框中的AJAX分页

    $(function() { $("body").on("click",".set-topic",function(){ /*获取所有题目接 ...

  5. Rust <8>:lifetime 高级语法与 trait 关联绑定

    一.生命周期关联:如下声明表示,'s >= 'c struct Parser<'c, 's: 'c> { context: &'c Context<'s>, } ...

  6. 使用apache搭建tomcat集群

    1.安装apache 1.1 下载ApacheX64.rar,并解压 1.2 修改Apache24\conf\httpd.conf文件 配置根目录: 配置ip和端口 1.2 安装apache服务器 以 ...

  7. iView + vue-quill-editor 实现一个富文本编辑器(包含图片,视频上传)

    1. 引入插件(注意IE10以下不支持) npm install vue-quill-editor --savenpm install quill --save (Vue-Quill-Editor需要 ...

  8. 关于UEditor的使用配置(图片上传配置)

    接到新需求,需要在平台上使用富文本编辑器,我这次选择了百度的UEditor 在官网上下载l.net版本的1.4.3开发版本 http://ueditor.baidu.com/website/downl ...

  9. linux下nano命令大全

    nano是一个字符终端的文本编辑器,有点像DOS下的editor程序.它比vi/vim要简单得多,比较适合Linux初学者使用.某些Linux发行版的默认编辑器就是nano. nano命令可以打开指定 ...

  10. 【模板篇】NTT和三模数NTT

    之前写过FFT的笔记. 我们知道FFT是在复数域上进行的变换. 而且经过数学家的证明, DFT是复数域上唯一满足循环卷积性质的变换. 而我们在OI中, 经常遇到对xxxx取模的题目, 这就启发我们可不 ...