Redux 笔记


《深入浅出React & Redux》第3章 从Flux到Redux
作者认为要了解Redux,首先要从Flux开始了解,这样我们可以知道Flux一族框架贯彻的最重要的观点--单线数据流,并且通过发现Flux的缺点,从而更加深刻的认识Redux相对于Flux的改进之处。

Flux

Flux简短历史介绍

Flux是和React同时面世的,React和Flux相辅相成,Facebook认为两者结合才能构建大型JavaScript应用。

我们可以这样理解,React是用来替换jQuery的,那么Flux就是替换Backbone.js、Ember.js等MVC一族架构的。但是Flux并不是一个MVC框架,事实上,Flux认为MVC框架存在很大的问题,所以Flux推翻了MVC框架,并用一个全新的思维来管理数据流转

MVC 框架的缺陷

MVC框架.png

Facebook描述的MVC框架

Facebook描述的MVC框架.png

Model和View之间缠绕着太多的复杂依赖关系,并且相互调用,非常乱。

MVC框架提出的数据流很理想,用户请求先到达controller,由controller调用Model获取数据,然后把数据交给View进行渲染。但是在实际框架实现中,总是允许View 和 Model可以直接通信,就出现了Facebook描述的MVC框架的情况发生。

Flux单项数据流

Facebook推出Flux时,有很多质疑。很多人认为Flux只不过是一个对数据流管理更加严格的MVC框架而已。这种说法不完全正确,但是一定意义上也说明了Flux的一个特点:更严格的数据流控制。

Flux单向数据流.png

Redux的原则

Flux的基本原则是“单向数据流”,Redux在此基础上强调了三个基本原则:

  • 单一数据源 (Single Source of Truth)
  • State是只读的 (State is read-only)
  • 使⽤纯函数来执⾏修改数据(Changes are made with pure functions)这里说的纯函数就是Reducer,Redux 这个名字的前三个字母Red代表的就是Reducer。按照Redux创作者Dan Abramov的说法,Redux = Reducer + Flux

Reducer 不是一个Redux特定的术语,而是计算机科学中的一个通用概念,很多语言和匡佳都有对Reducer函数的支持。如JavaScript,数组类型就有reduce函数,接受的参数就是一个reducer,reduce做的事情就是把数组所有元素依次做“规约”,对每个元素都调用一次参数reducer,通过reducer函数完成规约所有元素的功能。

示例讲解

在书中,作者按照如下顺序改写demo的code,讲解每一步的改进之处。

   Flux -->
   纯React --> 
   纯React + 拆分容器组件+展示组件 -->
   纯React + 拆分容器组件+展示组价+自己写的Provider(context概念) -->
   react-redux库的使用

纯React + 拆分容器组件+展示组件

这里拆分组件主要是用了容器组件和展示组件的概念,在容器组件中获取store,将store中拿到的data通过props传递给展示组件,类似于react-redux中的connect函数的作用。
因为react-redux并不需要将组件拆分为容器组件和展示组件。

组件Context

在使用react-redux的时候,只是照本宣科,不理解。在本书中,通过作者描述使用纯React+手写provider了解了provider的作用,并为什么会被作为一个线程的组件放到react-redux库中。

Redux的三大原则中第一条:单一数据源,也就是说Redux应用全局就一个store,如果我们在每个文件使用store的时候都直接使用import导入,依然会有问题。
因为当开发一个独立的组件的时候,都不知道这个组件会存在哪个应用中,当然也不可能预先知道定义唯一Redux Store的文件位置进行导入,所以在组件中直接导入store是非常不利于组件复用。

一个应用最好只有一个地方需要直接导入store,这个位置当然应该是在调用最顶层React组件的位置。在项目中,一般来说就是index.js入口文件。其余组件避免直接导入store。

面对这种问题,两种解决方法:
解决方法1. 让组件的上层组件把store传递下来。也就是用props。这个缺陷就是,从上到下,所有的组件都需要帮忙传递store这个props,即使有的组件根本使用不到。

解决方法2.React中提供了一个叫做Context的功能,使用这个功能能够完美的解决这个问题。

React的Context.png

Context,“上下文环境”,让一个树状组件上所有组件都能访问一个共同的对象,为了完成这个任务,需要上级组件和下级组件配合。

首先,上级组件要宣传自己支持context,并且提供一个函数来返回代表context的对象。然后,这个上级组件之下的所有子孙组件,只要宣称自己需要这个context,就可以通过this.context 访问到这个共同的环境对象。

provider的由来

因为React只有一个store,因此所有组件需要使用store的话,只能访问这个唯一store,我们就希望顶层的组件来扮演这个context提供者的角色,只要顶层组件提供包含store的context,那么就覆盖了整个应用的组件,简单而且够用。

但是每个应用的顶层组件不同,所以我们需要创建一个特殊的React组件,它将是一个通用的context提供者,可以应用于任何一个应用中,当做React应用所有组件的最外层的父组件,我们把这个组件叫做Provider

Provider的原理

Provider 也是一个React组件,不过它的render函数就是简单的把子组件渲染出来(return this.props.children;),在渲染上,Provider不做任何附加的事情。

每个React组件的props中都有一个特殊属性:children,代表的是子组件,所以this.props.children代表是在Provider之间的子组件。

除了把渲染工作全交给子组件,Provider还需要提供一个函数getChildContext,这个函数返回的就是代表context对象。下列代码中context中只有一个字段scope,而且我们也希望Provider足够通用,所以并不在这个文件中导入store,而是要求Provider的使用者通过props传递进来store。

为了让Provider能够被React认可为一个Context提供者,还需要制定Provider的childContextTypes属性,必须和getChildContext函数返回值对应,只有这两者都齐了,Provider的子组件才有可能访问到Context

import {PropTypes, Component} from 'react';

class Provider extends Component {
//函数getChildContext,这个函数返回的就是代表context对象,这个context只包含store这个字段
  getChildContext() {
    return {
      store: this.props.store
    };
  }
//简单的把子组件渲染出来,在渲染上不做任何处理
  render() {
    return this.props.children;
  }
}

//将store做为Provider的props,由使用者提供该props的值
Provider.propTypes = {
  store: PropTypes.object.isRequired
}

//为了让Provider能够被React认可为一个Context提供者,还需要制定Provider的childContextTypes属性
Provider.childContextTypes = { 
  store: PropTypes.object
};

export default Provider;

使用手写的Provider如何使用store。

//因为我们自己定义了构造函数,所以要带上context参数
constructor(props, context) {
    super(props, context);
}

//在函数中使用就是
  getOwnState() {
    return {
      value: this.context.store.getState()[this.props.caption]
    };
  }

ComponentName.contextTypes = {
  store: PropTypes.object
}

Context功能相当于提供了一个全局可以访问的对象,但是全局对象或者说全局变量是我们应该避免的用法。所以,单纯看React的context功能,必须强调这个功能要谨慎使用,只有对那些每个组件都可能使用,但是中间组件由可能不适用的对象才有必要使用Context,千万不要滥用。(这点有疑问)
对于Redux,因为Redux的Store封装很好,没有提供直接修改状态的功能,就是说一个组件虽然能够访问全局唯一的Store,却不可能直接修改Store中的状态,这样部分克服了作为全局对象的缺点,并不算滥用,所以,使用Context来传递Store是一个不错的选择。

React-Redux部分

React-Redux库已经实现了Provider,我们可以直接导入使用。
import { Provider } from 'react-redux';

react-redux两个最主要的功能:

  • connect:链接容器组件和展示组件
  • Provider: 提供包含store的context

connect

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
connect是react-redux提供的一个方法,接受两个参数:

  • mapStateToProps
  • mapDispatchToProps

执行的结果依然是一个函数,所以才可以在后面又加一个圆括号,把connect函数执行的结果立刻执行,这一次参数是Counter这个组件。

这里有两次函数执行:

  • 第一次是connect函数的执行
  • 第二次是把connect函数返回的函数再次执行,最后产生的就是容器组件,功能相当于纯React + 拆分容器组件+展示组件这个示例中拆分出来的容器组件。

当然我们也可以把connect的结果赋值给一个变量,然后在export这个变量,只是connect已经大大简化了代码,习惯上可以直接导出函数的执行结果,也不用纠结如何命名这个变量。

connect 函数如何工作的?

作为容器组件,要做的工作无外乎两件事情:

  • 把store上的状态转换为内层组件的props
  • 把内层傻瓜组件中的用户动作转话为派送给store的动作

这两个工作,一个是内层傻瓜组件的输入,一个是内层傻瓜组件的输出。
这两个工作的套路也很明显,把store上的状态转化为内层组件的props,其实就是一个映射的关系,去掉框架,最后就是一个mapStateToProps函数应该做的事情。这个函数命名是业界习惯,因为它只是一个模块内的函数。

把内层傻瓜组件中用户动作转化为派送给Store的动作,也就是把内层傻瓜组件暴露出来的函数类型的prop关联上dispatch函数的调用,每个prop代表的回调函数的主要区别就是dispatch函数的参数不同,这就是mapDispatchToProps函数做的事情,和mapStateToProps一样,函数名只是习惯问题。

mapStateToProps和mapDispatchToProps 函数都可以包含第二个参数,代表ownProps,也就是直接传递给外层容器组件的props????还没理解

react-redux的Provider

react-redux的Provider更加严谨,要求store不光是一个object,而且是必须包含三个函数的object,这三个函数是:

  • subscribe
  • dispatch
  • getState

拥有上述三个函数的对象,才能称之为一个Redux的store。

react-redux定义了Provider的componentWillReceiveProps函数,在react组件的生命周期中,componentWillReceiveProps函数在每次重新渲染时都会调用到,react-redux在componentWillReceiveProps函数中会检查这次渲染是代表store的props和上一次的是否一样。如果不一样,就会给出警告,这样做是为了避免多次渲染用了不同的redux store。每个Redux应用只能有一个store,在整个redux生命周期中,都应该保持store的唯一性。





Redux 中文文档

WHY

为什么要引入Redux?
Redux解决的是什么样的问题?
React-Redux
Redux还可以和其他前端框架结合吗?

Redux的核心概念

  • state
  • action
  • reducer
//普通对象描述应用的state
const state = 
{
    todos: [{
        text: 'Eat food',
        completed: true
    }, {
        text: 'Exercise',
        completed: false
    }],
    visibilityFilter: 'SHOW_COMPLETED'
}
// 这个对象就像“Model”,区别是该对象没有setter(修改容器方法)。
//因此其他的代码就不能随意修复它,从而造成难以复现的bug。
//要想更新state中的数据,需要发起action。
//action就是一个个普通的JavaScript对象,
//用来描述发生了什么。
// action 示例
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
// 为了把action和state串起来,需要开发一些函数,
//这些函数就是reducer。
// reducer仅仅只是一个接收 state 和 action,并且
//返回新的state的函数。
// 对于大的应用来说,可能很难开发这样的函数,
//所以,我们编写很多小的reducer函数来分别管理state的一部分。
function visibilityFilter(state = 'SHOW_ALL', action) {
    if (action.type === 'SET_VISIBILITY_FILTER') {
        return action.filter;
    } else {
        return state;
    }
}
function todos(state = [], action) {
    switch (action.type) {
        case 'ADD_TODO':
            return state.concat([{ text: action.text, completed: false }]);
        case 'TOGGLE_TODO':
            return state.map((todo, index) =>
                action.index === index ?
                    { text: todo.text, completed: !todo.completed } :
                    todo
            )
        default:
            return state;
    }
}
//再开发⼀个 reducer 调⽤这两个 reducer,
//进⽽来管理整个应⽤的 state:
function todoApp(state = {}, action) {
    return {
        todos: todos(state.todos, action),
        visibilityFilter: visibilityFilter(state.visibilityFilter, action)
    };
}

永远不要在Reducer里做这些操作

  • 修改传入的参数
  • 执行有副作用的操作,如API请求和路由跳转
  • 调用非纯函数, 如:Date.now()Math.random()

Redux的三大原则

  • 单一数据源 (Single Source of Truth)

    整个应用的state被存储在一颗object tree中,并且这个object tree只存在于唯一一个store中。
    (如何设计store是Redux应用的核心问题)
    
  • State是只读的 (State is read-only)

    惟⼀改变 state 的⽅法就是触发 action,action 是⼀个⽤于描述已发⽣事件的普通对象。
    
  • 使⽤纯函数来执⾏修改数据(Changes are made with pure functions)

    为了描述 action 如何改变 state tree ,你需要编写 reducers。
    

Store

Store就是把 state,action 和 reducer 联系到一起的对象。
Store有以下的职责:

  • 维持应用的state;
  • 提供 getState() 方法获取state;
  • 提供 dispatch(action) 方法更新 state;
  • 通过 subscribe (listener)方法注册监听器;
  • 通过 subscribe (listener) 方法返回的函数注销监听器。

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

  1. 使用 combineReducers () 方法将多个 reducer 合并成为一个
  2. 将合并的 reducers 导入,并传递给 createStore() 方法。

createStore() 方法的第二个参数是可选的,用于设置 state 的初始状态。

发起 Actions

import { addTodo, toggleTodo, setVisibilityFilter, VisibilityFilters }
// 打印初始状态
console.log(store.getState())

// 每次 state 更新时,打印⽇志
// 注意 subscribe() 返回⼀个函数⽤来注销监听器
let unsubscribe = store.subscribe(() =>
    console.log(store.getState())
)

// 发起⼀系列 action
store.dispatch(addTodo('Learn about actions'))
store.dispatch(addTodo('Learn about reducers'))
store.dispatch(addTodo('Learn about store'))
store.dispatch(toggleTodo(0))
store.dispatch(toggleTodo(1))
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED))

// 停⽌监听 state 更新
unsubscribe();

数据流

严格的单向数据流是Redux架构的设计核心。
Redux 应用中数据的生命周期遵循下面4个步骤:

  1. 调用 store.dispatch (action)方法;
  2. Redux store 调用传入的 reducer 函数;
    Store 会把两个参数传入 reducer:当前的state树 和 action。
  3. 根 reducer 应该把多个子 reducer 输出合并成一个单一的state 树;
    根 reducer 的结构完全由你决定。 Redux 原生提供 combineReducers () 辅助函数,来把根 reducer 拆分成多个函数,用于分别处理 state 树的一个分支。
  4. Redux store 保存了根 reducer 返回的完整的 state 树。

流程总结

  1. 根据业务想好初始化的state和操作state的动作
  2. 编写actions
  3. 编写 reducers 去调用 action 和 state,返回新的state
  4. 根据已有的reducer 创建 store
  5. 发起action,检查 store中的 state 是否按期望的action 变化

Redux 搭配 React

安装 react-redux 包

容器组件 和 展示组件

Redux 的 React 绑定库是基于容器组件和展示组件相分离的开发思想。

使用 react-redux 包中的connect 方法

容器组件是把展示组件和Redux关联起来。技术上讲,容器组件就是使用 store.subscribe () 从Redux state 树中读取部分数据,并通过 props 来把这些数据提供给要渲染的组件,
可以通过手工来开发容器组件,但是建议使用 react-redux 库的 connect () 方法来生成,这个方法做了性能优化来避免很多不必要的重复渲染。

使用 connect () 前,需要先定义 mapStateToProps () 这个函数来指定如何把当前 Redux Store State 映射到展示组件的 props中。

除了读取state,容器组件还能分发 action。类似的方式,可以定义 mapDispatchToProps ()方法接收 dispatch () 方法并返回期望注入到展示组件的props中的回调方法。

react-redux 中 connect () 用法

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

推荐阅读更多精彩内容

  • 学习必备要点: 首先弄明白,Redux在使用React开发应用时,起到什么作用——状态集中管理 弄清楚Redux是...
    贺贺v5阅读 8,866评论 9 58
  • redux自述 Redux 是 JavaScript 状态容器,提供可预测化的状态管理。 (如果你需要一个 Wor...
    WittyLu阅读 331评论 0 0
  • 使用redux+react已有一段时间,刚开始使用并未深入了解其源码,最近静下心细读源码,感触颇深~ 本文主要包含...
    字节跳动技术团队阅读 1,450评论 0 5
  • http://gaearon.github.io/redux/index.html ,文档在 http://rac...
    jacobbubu阅读 79,852评论 35 198
  • 前言 本文 有配套视频,可以酌情观看。 文中内容因各人理解不同,可能会有所偏差,欢迎朋友们联系我讨论。 文中所有内...
    珍此良辰阅读 11,871评论 23 111