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中就大不相同,一个对象从生到死我们都需要掌握的很清楚. ...
随机推荐
- Fluent API 配置
EF里实体关系配置的方法,有两种: Data Annotation方式配置 也可以 Fluent API 方式配置 Fluent API 配置的方法 EF里的实体关系 Fluent API 配置分为H ...
- IE11 iframe alternative
<OBJECT classid=clsid:8856F961-340A-11D0-A96B-00C04FD705A2> <PARAM NAME=Location VALUE=http ...
- python基础编程
1.if else var1 = 100 if var1: print ("1 - if 表达式条件为 true") print (var1) #为0时,条件不成立 var2 = ...
- PDF二次开发_iStylePDF表单域的填充
wo讲到PDF表单,我们首先需要认识Adobe定义的PDF表单有哪些.以下是我从网上搜索到的简单介绍: PDF 表单简介 PDF 是可移植文档格式(Portable Document Format)的 ...
- python成长之路【第十篇】:浅析python select模块
一.select介绍 select()的机制中提供一fd_set的数据结构,实际上是一long类型的数组, 每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他文件或命名管道或设备句 ...
- 远程连接redis
1.在ubuntu上的redis作为服务端,默认是打开的 在redis的配置文件redis.conf中,找到bind localhost注释掉. 注释掉本机,局域网内的所有计算机都能访问. bind ...
- ${param.xxx}获取url中的参数
在项目中看到了一个很奇怪的EL表达式...${param.catid}...一直找不到param在哪里定义的...(主要是水平太屎...) 然后从网上查了一下才知道是啥... EL表达式${param ...
- 2017年1月1日 java学习第二天复习
今天是新年的第一天,以前学习没有总结习惯,学习效率和成果都很不好. 学习的过程就是反复的复习和不断学习的过程,开始今天的学习总结 学习java的第二天. 今天学习了java最基础的一些内容,照着 ...
- C# 编程实现非自相交多边形质心
计算公式公式: http://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon 多边形的质心: 一个非自相交的n个顶点的多边形(x0,y0), (x ...
- 浅谈python中得import xxx,from xxx import xxx, from xxx import *
在python中import跟from import都是用来导入的,但是导入的机制不同 1.import xxx:导入模块,或者文件夹,对于调用模块或者文件夹中子模块的变量或者函数,需要使用" ...