React + Redux 入坑指南
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 异步解决方法
- redux-thunk 中间层做数据异步转换
- redux-saga 使用 ES6 generator / yield
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 入坑指南的更多相关文章
- electron入坑指南
electron入坑指南 简介 electron 实际集成chrome浏览器和node环境, 运行你写的网页 app 基本目录结构 index.html 名称可以不是index, 这个文件与普通网页的 ...
- C语言入坑指南-被遗忘的初始化
前言 什么是初始化?为什么要初始化?静态变量和局部变量的初始化又有什么区别?实际应用中应该怎么做?本文将一一回答这些问题. 什么是初始化 初始化指的是对数据对象或者变量赋予初始值.例如: int va ...
- Elasticsearch入坑指南之RESTful API
Elasticsearch入坑指南之RESTful API Tags:Elasticsearch ES为开发者提供了非常丰富的基于Http协议的Rest API,通过简单的Rest请求,就可以实现非常 ...
- ElasticSearch入坑指南之概述及安装
---恢复内容开始--- ElasticSearch入坑指南之概述及安装 了解ElasticSearch ElasticSearch(简称ES)基于Lucene的分布式全文检索引擎.使用ES可以实现近 ...
- eclipse中导入外部包却无法查看对应源码或Javadoc的入坑指南
eclipse中导入外部包却无法查看对应源码或Javadoc的 入坑指南 出现这个错误的原因是,你虽然导入了.jar包,但没有配置对应的Javadoc或源码路径,所以在编辑器中无法查看源 码和对应AP ...
- Rust入坑指南:核心概念
如果说前面的坑我们一直在用小铲子挖的话,那么今天的坑就是用挖掘机挖的. 今天要介绍的是Rust的一个核心概念:Ownership.全文将分为什么是Ownership以及Ownership的传递类型两部 ...
- Rust入坑指南:鳞次栉比
很久没有挖Rust的坑啦,今天来挖一些排列整齐的坑.没错,就是要介绍一些集合类型的数据类型."鳞次栉比"这个标题是不是显得很有文化? 在Rust入坑指南:常规套路一文中我们已经介绍 ...
- Rust入坑指南:亡羊补牢
如果你已经开始学习Rust,相信你已经体会过Rust编译器的强大.它可以帮助你避免程序中的大部分错误,但是编译器也不是万能的,如果程序写的不恰当,还是会发生错误,让程序崩溃.所以今天我们就来聊一聊Ru ...
- Rust入坑指南:朝生暮死
今天想和大家一起把我们之前挖的坑再刨深一些.在Java中,一个对象能存活多久全靠JVM来决定,程序员并不需要去关心对象的生命周期,但是在Rust中就大不相同,一个对象从生到死我们都需要掌握的很清楚. ...
随机推荐
- EasyUI---tree
EasyUI的tree在获取action返回的json字符串时最少具有三个属性id.text和children,这样在读取时才会在页面正常显示树形 这里比较重要的就是在数据库中对数据的存储吧,说白了还 ...
- Java资源大全中文版(Awesome最新版)(转载)
原文地址:http://www.cnblogs.com/best/p/5876559.html 目录 业务流程管理套件 字节码操作 集群管理 代码分析 编译器生成工具 构建工具 外部配置工具 约束满足 ...
- 一种更清晰的Android架构(转)
一种更清晰的Android架构 一种更清晰的Android架构 原文链接 : Architecting Android…The clean way? 译者 : Mr.Simple & So ...
- VS.Net 2015 Update3 学习(2) jquery-form, jquery-validation,jquery-validation-unobtrusive一起用
我觉DataAnnotations非常酷的一个功能.但是教程中的@ajaxform的却不怎么优雅,需要全局的onSucces等函数.因此我使用jquery-form做ajax提交. function ...
- 使用vs2010创建、发布、部署、调用 WebService
原文地址:使用vs2010创建.发布.部署.调用 WebService作者:吴超 一 使用vs2010创建 WebService 1 打开VS2010,菜单 文件->新建->项目2 ...
- File system needs to be upgraded. You have version null and I want version 7
安装hbase时候报错: File system needs to be upgraded. You have version null and I want version 7 注: 我安装的hba ...
- C#获取CSV文件内容对逗号和引号分隔的处理
我们知道,使用excel工具保存成csv文件时有几个规则: 1.每一行的单元格内容之间用逗号分隔. 2.如果单元格的内容本身有逗号,这个单元格的内容将会用引号包含. 3.如果单元格的内容本身有引号 1 ...
- a.redhat系统如何卸载默认jdk
Redhat系统安装之后,会默认有openjdk在安装(下图已经是卸载掉了) 这个openjdk下面的需要进行卸载,你可以通过命令进行卸载,例如首先查看JDK,然后默认直接通过rpm命令进行卸载L 卸 ...
- python的字符串内建函数
http://www.ziqiangxuetang.com/python/python-strings.html
- 版本控制简介,git使用----使用GitHub托管代码
关于版本控制: 很久以前,人们苦于对写过的代码进行版本的管理,经常过了一段时间想恢复原来写过的代码却又忘了不知道丢到哪儿去了,有的人用加上时间后缀来命名文件的方法,便于后期维护,但是这样做的麻烦也很大 ...