这是一篇详尽的 React Redux 扫盲文。前端
对 React Redux 已经比较熟悉的同窗能够直接看 《React Redux 与胖虎他妈》。react
React Redux 是 Redux 的 React 版,Redux 自己独立于其余框架而存在,又能够结合其余视图框架使用,好比此处的 React。android
按我的理解,Redux 是应用的状态管理框架,以事件流的形式来发送事件、处理事件、操做状态和反馈状态。webpack
这么说仍是太抽象了,举个简单的例子。好比有个 A 组件,它要改变它本身的一个 div 里面的文字,假设这个文字内容由 this.props.content
决定,那么它能够发送一个事件,这个事件通过一系列的处理,最终会改变 this.props.content
。git
龟龟,这也太秀了吧,改个文字都得这么复杂?没错,若是是这种状况去用 React Redux,那简直就是多此一举,没事找事。这里有一篇文章 You Might Not Need Redux,能够考虑本身编写的应用,是否真的须要 React Redux。github
回到上面的例子,假若 A 组件要去改变同级的一个 B 组件里面的文字呢?按照咱们以前的作法,咱们会在 A B 组件的上一层套上一个 Parent 组件,将修改 B 组件文字的方法传给 A 组件,A 组件调用后改变 Parent 组件的 state,进而改变 B 组件的文字。web
那么咱们的代码大约是这个样子:redux
//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 组件来改变里面的文字...bash
什么?有个深度为 100 的组件,要它来改变 B 组件的文字...
我胖虎出去抽根烟,回来要看到这两个功能实现,否则锤死在座各位。
为了实现这两个功能,回调函数满天飞,特别是第二个功能,你得把回调函数往下传 100 层...
差很少这个时候,你就该考虑 React Redux 了。
像第二个功能,只须要从深层的组件发送一个事件出来,这个事件最终就会改变 B 组件的文字。
嗬,听起来不错。
讲了这么多,是时候一睹 React Redux 的真容。
其中 Action、Dispatch 和 Reducer 都是 React Redux 的东西,View 则是表明咱们的视图层 React。
先理清几个概念:Store,Action 和 Reducer(Dispatch 是 Store 的一个方法)
type
字段从上面的图看到 View 层能够经过两种方式来更新:
上面不管哪种方式,都是遵循单向数据流的规则,即:发送 Action -> Reducer 根据 Action 返回数据 -> Store 被更新 -> View 被更新。
空谈误国,实干兴邦。仍是边写边介绍为好。
这里实现一个小 demo,就一个按钮和一个数字,点击按钮数字加 1,即计数器。
先说一点,React Redux 将组建区分为 容器组件 和 UI 组件,前者会处理逻辑,后者只负责显示和交互,内部不处理逻辑,状态彻底由外部掌控。
“老子有的组件又负责显示又负责处理逻辑,你怕不是在为难我胖虎”
是的,不少状况都是这样,因此通常作法是在外面封装一层,将逻辑和 UI 剥离,外层写逻辑,内层纯粹写 UI。
因此对于计数器这个组件,咱们须要多封装一层,使用的是 react-redux
的 connect
函数。这个函数顾名思义就是链接用的,链接 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
函数能够传入两个函数:
此函数接收 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' />
复制代码
此函数接收 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
。
咱们刚刚写的那个 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 点值得注意:
{count: 1}
,随后返回 {count: 2}
,再下次进来就是 {count: 2}
了。state 能够传入初始化值,好比我们这里初始值为 0有人又好奇了,那这个 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 的工做原理和方式了,再总结一番:
技术上的问题,欢迎讨论。
我的博客:mindjet.github.io
最近在 Github 上维护的项目:
欢迎 star/fork/follow 提 issue 和 PR。