Redux

原理

1. 单一数据源

all states ==>Store

  • 随着组件的复杂度上升(包括交互逻辑和业务逻辑),数据来源逐渐混乱,导致组件内部数据调用十分复杂,会产生数据冗余或者混用等情况。
  • Store 的基本思想是将所有的数据集中管理,数据通过 Store 分类处理更新,不再在组件内放养式生长。

2. 单向数据流

dispatch(actionCreator) => Reducer => (state, action) => state

  • 单向数据流保证了数据的变化是有迹可循且受控制的。
  • 通过绑定 Store 可以确定唯一数据来源。
  • actionCreator 通过 dispatch 触发,使组件内事件调用逻辑清晰,具体的事件处理逻辑不用放在组件写,保持 view 层的纯净。
  • Reducer 通过判断不同的 actionType 处理不同数据更新,保证数据有秩序更新。

React + Redux

Action

  • actionType 定义操作类型
  • actionCreator 定义操作具体执行函数

1. Action 基础写法

  • actionType 提供给 Reducer 判断动作类型
  • actionCreator 为可调用的执行函数,必须返回 actionType 类型
// actionType
export const ACTION_TYPE = 'ACTION_TYPE'; // actionCreator
let actionCreator = (config) => {
return {
type: ACTION_TYPE, // 必须定义 type
config // 传递参数 => reducer
}
}

2. Action 异步解决方法

2.1 redux-thunk 使用方法

  • redux-thunk 配置

    redux-thunk 为独立工具,需要另外安装,通过 redux 提供的中间件 applyMiddleware ,绑定到 store 中。
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducers from '../reducers'; let store = createStore(
reducers,
applyMiddleware(thunk)
);
  • Action 使用 redux-thunk

    获取数据方法在异步获取数据后需要再次调用接收方法接收数据。
// 接收方法
let receiveSomething = (res) => {
return {
type: RECEIVE_SOME,
res
}
} // 获取数据方法
export let fetchSomething = (args) => {
return dispatch => {
return fetch(args).then((res) => {
return dispatch(receiveSomething(res))
})
}
}

Reducer

  • 引入 Action 中定义好的 actionType
  • 传入 初始数据 和 actionType 后,返回更新数据(initialState, action) => newState

Reducer 基础写法

1.依据不同执行 ActionType 直接更新状态

import { ACTION_A, ACTION_B } from '../actions';

let initialState = { ... }

function example(state = initialState, action) {
switch(action.type) {
case ACTION_A:
return Object.assign({}, state, action.config)
case ACTION_B:
return Object.assign({}, state, action.config)
}
}

2.对 Action 传递的数据多加一层处理

let doSomething = (config) => {
let { a, b } = config;
// do something with a, b
return { a, b }
} function example(state = initialState, action) {
switch(action.type) {
case ACTION_TYPE:
return Object.assign({},
state,
doSomething(action.config))
}
}

3.合并多个 Reducer

通过 redux 提供的 combineReducers 将不同处理逻辑的 reducer 合并起来。

import { combineReducers } from 'redux';

export default combineReducers({
reducerA,
reducerB
}); // or export let reducer = (state = initialState, action) {
a: processA(state.a, action),
b: processB(state.b, action)
}

Store

1. 将 Store 绑定 React

使用 react-redux 提供的 Provider 可以将 Store 注入到 react 中。

Store 将合并后的 reducers 通过 createStore 创建,此外下面示例代码还使用中间件加入了一层 react-thunk 处理。

import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux'
import thunk from 'redux-thunk';
import reducers from './reducers'; let store = createStore(
reducers,
applyMiddleware(thunk)
); ReactDOM.render((
<Provider store={store}>
// ...
</Provider>
), document.querySelector('#app'));

2. 将 state 绑定到 Component

使用 react-redux 提供的 connect 方法 将组件和所需数据绑定。

需要注意的是,Store 创建时接收的是合并后的 reducers, 因此不同 reducer 上的处理数据绑定在了不同 reducer 对象上,而不是全部挂载在 Store 上。

mapStateToProps 将组件内部所需数据通过 props 传入组件内部。更多绑定机制,具体可参考connect

import React, { Component } from 'react';
import { connect } from 'react-redux'; class ComponentA extends Component {
//...
} let mapStateToProps = (state) => {
// attention !!!
let { reducerA, reducerB } = state;
return {
propA: reducerA.propA,
propB: reducerB.propB
}
}; export default connect(mapStateToProps)(ComponentA);

Component

1. 概念

React bindings for Redux embrace the idea of separating presentational and container components.

Redux 的 React 绑定库包含了 容器组件和展示组件相分离 的开发思想。

  • Presentational Components 展示型组件
  • Container Components 容器型组件

展示型组件和容器型组件的区别在官方文档中已经给出很详细的解释了,但是中文文档的翻译有误,所以直接看英文比较更容易懂。

Presentational Components Container Components
Purpose How things look (markup, styles) How things work (data fetching, state updates)
Aware of Redux No Yes
To read data Read data from props Subscribe to Redux state
To change data Invoke callbacks from props Dispatch Redux actions
Are written By hand Usually generated by React Redux

组件类型区分的模糊点在于怎么界定组件的内部功能规划。如果判定一个组件为展示型组件,那么它所需数据和处理方法都应该从父级传入,保持组件内部“纯净”。

在实际开发中,一个组件的逻辑跟业务紧密相关。如果需要将数据和方法从外部传入,那么父级组件所做的事情会很多,多重的子组件也会把父级逻辑弄乱,这就不是 redux 的初衷了。

中文文档翻译的意思是:容器组件应该为路由层面的组件,但这样既不符合实际开发需要,也违背了 redux 思想。真正界定两种组件的因素是:

  • 展示型组件: 类似纯模板引擎,外加一层样式渲染,只负责渲染从props传进来的数据或者监听事件和父组件做小联动。它是“纯净”的,不需要使用到 Redux 的一套规则。
  • 容器型组件: 需要异步获取数据,更新组件状态等等。需要跟业务逻辑打交道的组件都可以认为是容器组件。这些逻辑的复杂性需要将数据整合到 Store 里统一管理。

2. Component 基础写法

  • 组件渲染完成后调用Action

当组件 connect 后,dispatch 方法已经注入到 props 中,所以触发 Action 可以从 props 获取 dispatch 方法。

import React, { Component } from 'react';
// actionCreator
import { actionA, actionB } from 'actions/actionA' class ComponentA extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
let { dispatch } = this.props;
dispatch(actionA())
}
}
export default connect()(ComponentA);
  • 组件模板内调用Action

组件内部所需的渲染数据都已经绑定在了 props 上,直接获取即可。

需要注意的是,在事件监听中触发 Action,需要用一个匿名函数封装,否则 React 在渲染时就会执行事件绑定事件,而不是当事件发生再执行。

render() {
let { dispatch, propA, propB } = this.props; return (
<section>
// Attention !!!
<input type="text" onClick={(ev) => dispatch(actionB(ev))} />
<p className={propA}>{propB}</p>
</section>
)
}
  • 容器组件传递方法

容器型组件需要连接 Redux,使用 dispatch 触发 actionCreator。

展示型组件需要用到的方法调用在容器型组件内定义好,通过 props 传入到展示型组件中。

// get actionCreator
import { actionA } from './actions/actionA'; class Parent extends Component {
handleCallback(data) {
// use dispatch
let { dispatch } = this.props;
dispatch(actionA(data));
}
render() {
return (
<Child onSomethingChange={this.handleCallback} />
)
}
}
// connet Redux
export default connect()(Parent);
  • 展示组件接收props

展示型组件不需要用到 Redux 的一切,它的 props 仅仅存在于父级传入的数据和方法。

// don't need action/dispatch/connect
class Child extends Component {
handleSomething(data) {
// handle anything with props
this.props.onSomethingChange(data);
}
render() {
return (
// just markup & style
<input onChange={handleSomething} />
)
}
}

Conclusion

图示箭头代表各概念之间的相互关系,不代表数据流。( 能理解下面这张图,这篇文章就没白看了 -。- )

参考文档

END.

React + Redux 入坑指南的更多相关文章

  1. electron入坑指南

    electron入坑指南 简介 electron 实际集成chrome浏览器和node环境, 运行你写的网页 app 基本目录结构 index.html 名称可以不是index, 这个文件与普通网页的 ...

  2. C语言入坑指南-被遗忘的初始化

    前言 什么是初始化?为什么要初始化?静态变量和局部变量的初始化又有什么区别?实际应用中应该怎么做?本文将一一回答这些问题. 什么是初始化 初始化指的是对数据对象或者变量赋予初始值.例如: int va ...

  3. Elasticsearch入坑指南之RESTful API

    Elasticsearch入坑指南之RESTful API Tags:Elasticsearch ES为开发者提供了非常丰富的基于Http协议的Rest API,通过简单的Rest请求,就可以实现非常 ...

  4. ElasticSearch入坑指南之概述及安装

    ---恢复内容开始--- ElasticSearch入坑指南之概述及安装 了解ElasticSearch ElasticSearch(简称ES)基于Lucene的分布式全文检索引擎.使用ES可以实现近 ...

  5. eclipse中导入外部包却无法查看对应源码或Javadoc的入坑指南

    eclipse中导入外部包却无法查看对应源码或Javadoc的 入坑指南 出现这个错误的原因是,你虽然导入了.jar包,但没有配置对应的Javadoc或源码路径,所以在编辑器中无法查看源 码和对应AP ...

  6. Rust入坑指南:核心概念

    如果说前面的坑我们一直在用小铲子挖的话,那么今天的坑就是用挖掘机挖的. 今天要介绍的是Rust的一个核心概念:Ownership.全文将分为什么是Ownership以及Ownership的传递类型两部 ...

  7. Rust入坑指南:鳞次栉比

    很久没有挖Rust的坑啦,今天来挖一些排列整齐的坑.没错,就是要介绍一些集合类型的数据类型."鳞次栉比"这个标题是不是显得很有文化? 在Rust入坑指南:常规套路一文中我们已经介绍 ...

  8. Rust入坑指南:亡羊补牢

    如果你已经开始学习Rust,相信你已经体会过Rust编译器的强大.它可以帮助你避免程序中的大部分错误,但是编译器也不是万能的,如果程序写的不恰当,还是会发生错误,让程序崩溃.所以今天我们就来聊一聊Ru ...

  9. Rust入坑指南:朝生暮死

    今天想和大家一起把我们之前挖的坑再刨深一些.在Java中,一个对象能存活多久全靠JVM来决定,程序员并不需要去关心对象的生命周期,但是在Rust中就大不相同,一个对象从生到死我们都需要掌握的很清楚. ...

随机推荐

  1. 后缀数组:倍增法和DC3的简单理解

    一些定义:设字符串S的长度为n,S[0~n-1]. 子串:设0<=i<=j<=n-1,那么由S的第i到第j个字符组成的串为它的子串S[i,j]. 后缀:设0<=i<=n- ...

  2. bean找不到异常

    和这种的 原因: 这些都是因为bean注入的时候没有找个要注入的bean 解决办法: 1.查看dubbo文件中,暴露接口是否引入bean 2.如果有引入,查看引入路径和类是否存在.

  3. 1003. Emergency (25)

    As an emergency rescue team leader of a city, you are given a special map of your country. The map s ...

  4. 汇编语言写出的helloworld运行过程

    一:首先说一点,这篇文章建立在懂一点汇编的基础上,有几个简单的命令,说以下: 1:-r命令 -r 查看寄存器 -r 寄存器 (如 -r AX) 修改寄存器的值: 2:-d命令 -d 地址:xxxx:x ...

  5. 泛型数组列表 ArrayList

    为什么使用泛型数组列表而不使用普通数组? 1.普通数组经常会发生容量太大以致浪费的情况 2.普通数组无法动态更改数组 基本概念: 1.采用[类型参数]的[类]---->[泛型类] 2.[泛型类型 ...

  6. phalcon: 当删除循环删除一组数据,需要判断影响的行affectedRows

    phalcon:有一个表,按日期查找半年以为的数据,由于数据量特别大,不能一次:delete删除数据,否则会造成数据表卡顿,数据库锁死. 那么只能循环的删除数据,每次删除100条左右,知道删除为止., ...

  7. ant 错误 Specified VM install not found: type Standard VM, name jdk1.6.0_27

    ant 错误 ant Specified VM install not found: type Standard VM, name jdk1.6.0_27 原因: 安装了新的jdk, 在workspa ...

  8. 数据挖掘算法(四)Apriori算法

    参考文献: 关联分析之Apriori算法

  9. python——创建django项目全攻略(野生程序员到家养程序员的完美进化)

    新建工程 我用pycharm写代码,所以一般就用pycharm创建django工程.右上角File-New Project.选择路径,修改项目名称,确定.就可以创建一个新的django工程.     ...

  10. Linux 系统把英文修改成中文界面

    1.一般安装后的linux系统都是英文的界面,网上查了一下各种说法都有,我只做了如下的配置就好了,下载个中文包,改一下i18n就完事了,并没有那么复杂 下面上图文: 目前是英文的界面 2.下载个中文包 ...