React Redux 与胖虎

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

对 React Redux 已经比较熟悉的同窗能够直接看 《React Redux 与胖虎他妈》react

是什么

React ReduxRedux 的 React 版,Redux 自己独立于其余框架而存在,又能够结合其余视图框架使用,好比此处的 React。android

干吗的

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

这么说仍是太抽象了,举个简单的例子。好比有个 A 组件,它要改变它本身的一个 div 里面的文字,假设这个文字内容由 this.props.content 决定,那么它能够发送一个事件,这个事件通过一系列的处理,最终会改变 this.props.contentgit

龟龟,这也太秀了吧,改个文字都得这么复杂?没错,若是是这种状况去用 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 的真容。

RR架构

其中 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。

相关文章
相关标签/搜索