这是一篇详尽的 React Redux 扫盲文。

对 React Redux 已经比较熟悉的同学可以直接看 《React Redux 与胖虎他妈》

是什么

React ReduxRedux 的 React 版,Redux 本身独立于其他框架而存在,又可以结合其他视图框架使用,比如此处的 React。

干嘛的

按个人理解,Redux 是应用的状态管理框架,以事件流的形式来发送事件、处理事件、操作状态和反馈状态。

这么说还是太抽象了,举个简单的例子。比如有个 A 组件,它要改变它自己的一个 div 里面的文字,假设这个文字内容由 this.props.content 决定,那么它可以发送一个事件,这个事件经过一系列的处理,最终会改变 this.props.content

龟龟,这也太秀了吧,改个文字都得这么复杂?没错,如果是这种情况去用 React Redux,那简直就是画蛇添足,没事找事。这里有一篇文章 You Might Not Need Redux,可以考虑自己编写的应用,是否真的需要 React Redux。

回到上面的例子,倘若 A 组件要去改变同级的一个 B 组件里面的文字呢?按照我们之前的做法,我们会在 A B 组件的上一层套上一个 Parent 组件,将修改 B 组件文字的方法传给 A 组件,A 组件调用后改变 Parent 组件的 state,进而改变 B 组件的文字。

那么我们的代码大约是这个样子:

//Parent 组件
render() {
return (
<div>
<A onClick={(newContent) => this.setState({content: newContent})}/>
<B content={this.state.content}/>
</div>
)
} //A 组件
render() {
return (
<div onClick={() => this.props.onClick('This is new content for B')}>Change B's content</div>
)
} //B 组件
render() {
return (
<div>{this.props.content}</div>
)
}

有点费劲呢...可是我们总算实现了功能~

什么?多加了个同级的 C 组件,也要 A 组件来改变里面的文字...

什么?有个深度为 100 的组件,要它来改变 B 组件的文字...

我胖虎出去抽根烟,回来要看到这两个功能实现,不然锤死在座各位。


为了实现这两个功能,回调函数满天飞,特别是第二个功能,你得把回调函数往下传 100 层...

差不多这个时候,你就该考虑 React Redux 了。

像第二个功能,只需要从深层的组件发送一个事件出来,这个事件最终就会改变 B 组件的文字。

嗬,听起来不错。

长啥样

讲了这么多,是时候一睹 React Redux 的真容。


其中 Action、Dispatch 和 Reducer 都是 React Redux 的东西,View 则是代表我们的视图层 React。

先理清几个概念:Store,Action 和 Reducer(Dispatch 是 Store 的一个方法)

  • Store 是整个 React Redux 应用总的状态容器,是一个对象
  • Action 也是一个对象,表明事件,需要有 type 字段
  • Reducer 是一个函数,会根据不同 Action 来决定返回不同的数据

从上面的图看到 View 层可以通过两种方式来更新:

  1. View 层发出 Action,Dispatch 之后到达 Reducer,Reducer 处理后返回新的东西去更新 View
  2. 其它层发出 Action 以同样的方式来更新 View

上面无论哪一种方式,都是遵循单向数据流的规则,即:发送 Action -> Reducer 根据 Action 返回数据 -> Store 被更新 -> View 被更新

从 demo 讲起

空谈误国,实干兴邦。还是边写边介绍为好。

这里实现一个小 demo,就一个按钮和一个数字,点击按钮数字加 1,即计数器。

先说一点,React Redux 将组建区分为 容器组件 和 UI 组件,前者会处理逻辑,后者只负责显示和交互,内部不处理逻辑,状态完全由外部掌控。

“老子有的组件又负责显示又负责处理逻辑,你怕不是在为难我胖虎”


是的,很多情况都是这样,所以一般做法是在外面封装一层,将逻辑和 UI 剥离,外层写逻辑,内层纯粹写 UI。

所以对于计数器这个组件,我们需要多封装一层,使用的是 react-reduxconnect 函数。这个函数顾名思义就是连接用的,连接 UI 组件,生成新的含有逻辑的组件。

connect 是一个高阶函数,可以传入两个函数:

import { connect } from 'react-redux';
import Counter from './Counter'; function mapStateToProps(state) {
return {
count: state.count
}
} function mapDispatchToProps(dispatch) {
return {
onAdd: () => dispatch({type: 'ADD_COUNT'})
}
} const newComponent = connect(mapStateToProps, mapDispatchToProps)(Counter);

connect 函数可以传入两个函数:

mapStateToProps

此函数接收 state 参数(后面会讲到,这个 state 是从 reducer 那里过来的),定义从 state 转换成 UI 组件 props 的规则。该函数返回 props 对象,比如我们取 state 的 count 字段生成新的 props 返回。

此函数还可以接收 ownProps 参数,代表直接在 UI 组件上声明的 props:

function mapStateToProps(state, ownProps) {
console.log(ownProps); //{content: 'hello', color='white'}
return {};
} //比如我们是这么使用 Counter 组件的
<Counter content='hello', color='white' />

mapDispatchToProps

此函数接收 dispatch 参数(实际上是 Store 的 dispatch 方法),定义一系列发送事件的方法,并返回 props 对象。比如上面我们定义发送 ADD_COUNT 事件的方法 onAdd,其中 {type: 'ADD_COUNT'} 就是一个简单的 Action 了。

等等,胖虎有话要说。


你说 mapStateToProps 返回 UI 组件的 props,mapDispatchToProps 也返回 UI 组件的 props,同时 UI 组件自己还定义了 props,那他娘的最后 UI 组件的 props 是什么啊?

答案是,这 3 个 props 合在一块。也就是说,照上面的例子,在 Counter 组件内部可以调用到这些:

this.props.content;
this.props.color;
this.props.count;
this.props.onAdd(); export default class Counter extends React.Component { render() {
return (
<div>
<p>{this.props.count}</p>
<button onClick={this.props.onAdd}>Add</button>
</div>
)
}
}

恩,用 connect 就把外层的容器组件构造好了,我们把刚刚那个含有 connect 函数的文件命名为 index.jsx

Reducer

我们刚刚写的那个 Counter,其实还不能用,因为我们发送事件出去之后,并没有对事件进行处理。

Reducer 就是用来处理 Action 的,实际上是一个函数,比如我们处理上面提到的 ADD_COUNT 事件:

//counter-reducer.js
export default function reducer(state={count: 0}, action) {
switch(action.type) {
case 'ADD_COUNT':
return {
count: state.count + 1
};
default:
return state;
}
}

像这里,如果我们判断事件 type 是 ADD_COUNT 时,将 state 里面的 count 字段属性值 +1 并且返回新的 state 对象,这个对象会传到 mapStateToProps 中。

Reducer 函数里面有 2 点值得注意:

  • 第一个参数 state 表明当前的 state,比如说当数字为 1 时点击 Add 按钮,此时在 Reducer 中该 state 为 {count: 1},随后返回 {count: 2},再下次进来就是 {count: 2} 了。state 可以传入初始化值,比如咱们这里初始值为 0
  • 任何事件所有的 Reducer 都可以接收到,若 Reducer 没有匹配的 case,代表不响应这个事件,要返回当前的 state,即 default 分支返回 state。

Store

有人又好奇了,那这个 state 到底是存在于哪里的?目前我们讨论到的 Reducer 和 mapStateToProps 函数,它们都是接受 state,本身并不持有 state。


实际上,state 存在于 Store 中。后面还会讲到,多个 Reducer 的情况下,一个 Reducer 对应 Store 中的一个 state。

那么,Store 又是怎么作用到我们的 DOM 树上的?

React Redux 是通过 Provider 组件将 Store 这一个全局状态容器绑定到 DOM 树上,Provider 一般作为 React Redux 应用最顶层的组件(Provider 并不真实存在于 DOM 树上):

import { createStore, Provider } from 'react-redux';
import React from 'react';
import reducer from './counter-reducer.js';
import Counter from './components/Counter'; const store = createStore(reducer); ReactDOM.render(
<Provider store={store}>
<Counter/>
</Provider>, document.getElementById('root'));

可以看到我们从 react-redux 这个库引入了 createStore Provider,并使用 createStore 传入上面的 Reducer 创建出 Store,再将 Store 传到 Provider 组件,从而作用于整个 DOM 结构。

此时的项目结构为(当然还有其他一些 webpack 配置文件什么的,就不列举了):

Project
- components
- Counter
- index.jsx
- Counter.jsx
- index.jsx

到此为止,这个计数器已经能正常地运作了。

我们也稍微理解了 React Redux 的工作原理和方式了,再总结一番:

  • 事件流:dispatch(Action) -> Reducer -> new state (Store) -> new props -> update component
  • 分为容器组件和 UI 组件,传统组件可能需要用 connect 作处理
  • Reducer 处理 Action 返回新的 state,需考虑 Action 不匹配的情况
  • 使用 createStore 函数创建 Store,Reducer 作为参数
  • 使用 Provider 作为顶层组件将全局 Store 引入

———

技术上的问题,欢迎讨论。

个人博客:mindjet.github.io

最近在 Github 上维护的项目:

  • LiteWeather [一款用 Kotlin 编写,基于 MD 风格的轻量天气 App],对使用 Kotlin 进行实际开发感兴趣的同学可以看看,项目中会使用到 Kotlin 的委托机制、扩展机制和各种新奇的玩意。
  • Reask [用 React&Flask 开发的全栈项目,前端采用 react-redux]
  • LiteReader [一款基于 MD 的极轻阅读 App,提供知乎日报、豆瓣电影等资源],项目主要使用了 MVVM 设计模式,界面遵循 Material Design 规范,提供轻量的阅读体验。
  • LiveMVVM [Kotlin 编写的 Android MVVM 框架,基于 android-architecture],轻量 MVVM+Databinding 开发框架。

欢迎 star/fork/follow 提 issue 和 PR。

React Redux 与胖虎的更多相关文章

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

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

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

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

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

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

  4. react+redux官方实例TODO从最简单的入门(6)-- 完结

    通过实现了增-->删-->改-->查,对react结合redux的机制差不多已经了解,那么把剩下的功能一起完成吧 全选 1.声明状态,这个是全选状态 2.action约定 3.red ...

  5. react+redux官方实例TODO从最简单的入门(1)-- 前言

    刚进公司的时候,一点react不会,有一个需求要改,重构页面!!!完全懵逼,一点不知道怎么办!然后就去官方文档,花了一周时间,就纯react实现了页面重构,总体来说,react还是比较简单的,由于当初 ...

  6. 重写官方TodoList,对于初学react+redux的人来说,很有好处

    虽然官网的TodoList的例子写的很详细,但是都是一步到位,就是给你一个action,好家伙,全部都写好了,给你一个reducer,所有功能也是都写好了,但是我们这些小白怎么可能一下就消化那么多,那 ...

  7. react+redux教程(四)undo、devtools、router

    上节课,我们介绍了一些es6的新语法:react+redux教程(三)reduce().filter().map().some().every()....展开属性 今天我们通过解读redux-undo ...

  8. react+redux教程(三)reduce()、filter()、map()、some()、every()、...展开属性

    reduce().filter().map().some().every()....展开属性   这些概念属于es5.es6中的语法,跟react+redux并没有什么联系,我们直接在https:// ...

  9. react+redux教程(二)redux的单一状态树完全替代了react的状态机?

    上篇react+redux教程,我们讲解了官方计数器的代码实现,react+redux教程(一).我们发现我们没有用到react组件本身的state,而是通过props来导入数据和操作的. 我们知道r ...

随机推荐

  1. [转载]深入理解maven构建生命周期和各种plugin插件

    我就不复制博主文章了,到原文地址看吧.写这个只是为了自己搜索起来方便些: https://blog.csdn.net/zhaojianting/article/details/80321488 htt ...

  2. bus事件总线传值

    import Vue from 'vue'   var bus = new Vue()   export default bus 监听事件: // header组件 <template> ...

  3. shell脚本视频学习2

    一.函数 1.函数格式 2.函数传入参数 3.手动输入函数中的参数 4.函数返回值 成功返回0,失败返回1 5.输入一个目录,判断目录是否存在,如果不存在则给出提示,如果存在则提示输入要创建的文件名, ...

  4. 《python解释器源码剖析》第1章--python对象初探

    1.0 序 对象是python中最核心的一个概念,在python的世界中,一切都是对象,整数.字符串.甚至类型.整数类型.字符串类型,都是对象.换句话说,python中面向对象的理念观测的非常彻底,面 ...

  5. 03:Java基础语法(二)

    Java基础语法 Java运算符 算术运算符 运算符是一种特殊的符号,用以表示数据的运算.赋值和比较等.1.操作数:参与运算的数据 称为操作数.2.表达式:运算符和操作数的整体 称为表达式.单独的一个 ...

  6. 使用tomcat-maven-plugin自动化部署应用

    目标:将应用自动打包并自动部署到远程tomcat容器. 1.在pom.xml中添加plugin <plugin> <groupId>org.apache.tomcat.mave ...

  7. 【ios bug解决】 输入框聚焦时光标不显示

    解决办法:重写user-select样式 css:   user-select: text;-webkit-user-select:text;

  8. 《Python基础教程》第六章:抽象(一)

    用def定义函数 __doc__是函数属性.属性名中的双下划线表示它是个特殊属性

  9. ionic使用CardIO实现扫描银行卡功能(只能扫描16位以下,并且是浮雕数字)

    在一些涉及支付功能的app,一般需要绑定银行卡,除了靠用户手动输入银行卡号,用手机扫描银行卡也是一种常用的手段,查阅了写资料,发现ionic有CardIO插件可以实现这个功能,就兴致冲冲去尝试,结果有 ...

  10. VUE-练习

    作业一:有红黄蓝三个按钮,以及一个200*200矩形box,点击不同按钮,box的颜色会被切换为指定的颜色 <!DOCTYPE html> <html lang="en&q ...