Redux是用来管理JavaScript应用的状态的。官方文档地址:Redux。本文讲述如何在React中使用Redux,重点偏向Redux。若是须要了解React,能够参考React官网或者React入门(前一阵入门React时写的文|ω・))。javascript
Redux是什么?java
Redux is a predictable state container for JavaScript apps.react
Redux是一个用在JavaScript应用中的可预测状态容器(这里的状态指的是存放信息的对象)。Redux是用来对状态进行统一管理的,整个应用的状态以一棵对象树的方式被存放在store中。状态是只读的,惟一改变状态的方式是dispatch actions,因此状态的改变是可预测的。经过dispatch改变store中的state,经过subscribe监听状态的改变,经过getState来拿到当前的状态,这样数据的处理都集中在Redux中。git
为何要用Redux?github
想象这样的场景,组件一、组件二、组件三、组件五、组件7(下图标绿的组件)中都须要使用一些数据,由于在React中,数据是单向流动的,因此在组件7中改变数据了,就须要组件7通知组件4改变数据,组件4再通知组件1改变数。在组件1中改变数据以后再层层往下传递数据。这还只是下图这样结构比较简单的应用,若是应用更复杂,那处理起来就是一团乱麻了。因此咱们须要用Redux来对数据(状态)进行统一的管理,当须要改变某数据时,直接改变store中的数据,当要获取数据时,也直接从store中拿数据。json
React和Redux有什么关系?redux
听见Redux这个名字的时候,由于和React同样都是以Re开头的,可能会产生Redux是专门为React解决问题的一个工具这样的误解,但实际上React和Redux啥关系也没有,是两个独立的工具。你能够将Redux和React结合起来使用,也能够将Redux和jQuery,甚至Redux和JavaScript结合使用。app
Redux中有几个必须知道的概念:action、reducer、store。下文中提到的state(状态)指的是存放信息的对象,这个对象是放在Redux的store中的。dom
actions是从应用到store的数据的载体。当咱们须要改变store中的数据的时候,须要dispatch actions。action必须有一个type属性,代表即将执行的action的类型,type一般被定义为一个字符常量。action对象中的其余属性是携带的信息。异步
例如:
{
type: ADD_NUMBER,
number: 1
}
复制代码
action creator是建立action的函数,简单地返回action,它能根据传入的参数设置action的属性值。
例如:
const ADD_NUMBER = 'ADD_NUMBER'
export const addNumber = number => ({
type: ADD_NUMBER,
number
})
复制代码
reducer是纯函数,它是用来指明当action发送(dispatch)到store的时候,state是如何改变的。它的第一个参数为前一个state,第二个参数为action对象,返回的值是下一个state。
纯函数:
- 传入相同的参数会返回相同的结果。
- 执行纯函数不会形成反作用。(这里的反作用指的是函数改变了做用域以外的状态值,好比函数外部定义了变量a,在函数内部改变了变量a的值。)
在reducer中不能作如下操做:改变它的参数;形成反作用;调用非纯函数(好比Math.random()
)。
如下的caculate函数是一个简单的reducer。若是createStore函数没有传入第二个参数(state的初始值),那么首次调用caculate的时候,实参state的值为undefined。因此给state参数设置了一个默认值0。
function caculate (state = 0, action) {
const { number } = action
switch (action.type) {
case 'ADD_NUMBER':
return state + number
case 'SUBTRACT_NUMBER':
return state - number
case 'MULTIPLY_NUMBER':
return state * number
default:
return state
}
}
复制代码
当应用比较大的时候,通常的作法是使用多个小的reducers,每一个reducer处理特定的内容。而后使用Redux提供的combineReducers
将reducers合并为一个reducer,合并后的reducer做为createStore的参数来建立store实例。下文介绍完store以后,会给出一个简单的例子来讲明这部份内容。
store将actions和reducers结合起来,store的功能是:
getState()
拿到state。dispatch(action)
更新state。subscribe(listener)
注册监听器。listener是一个函数,当发送action的时候会执行listener。subscribe(listener)
会返回 一个函数,调用这个函数可以注销监听器。一个应用中只能有一个store,想要拆分数据的话,须要使用多个reducers。
如下代码中的caculate是上文建立好的reducer,addNumber是上文定义的action creator。
import { createStore } from 'redux'
const store = createStore(caculate)
const listener = () => console.log(store.getState()) // 打印当前状态
const unsubscribe = store.subscribe(listener)
store.dispatch(addNumber(1))
store.dispatch(addNumber(2))
store.dispatch(addNumber(3))
unsubscribe()
复制代码
使用store.subscribe(listener)注册了监听器,当store.dispatch()发送action到store,reducer将state改变以后,监听器函数listener会执行,打印出如下结果:
1
3
6
复制代码
我须要另外一个reducer来处理标签切换,如下是相应的action creatore和reducer。
const SELECT_TAB = 'SELECT_TAB'
const selectTab = tab => ({
type: SELECT_TAB,
tab
})
function switchTab (state = 'CACULATE', action) {
switch (action.type) {
case 'SELECT_TAB':
return action.tab
default:
return state
}
}
复制代码
如今就有了两个reducer。使用combineReducers将reducers合并为一个根reducer,将根reducer做为createStore的第一个参数,初始的state值做为createStore的第二个参数来建立store。
let state = {
caculate: 0,
switchTab: 'CACULATE'
}
const reducer = combineReducers({ caculate, switchTab })
const store = createStore(reducer, state)
const listener = () => console.log(store.getState())
const unsubscribe = store.subscribe(listener)
store.dispatch(addNumber(1))
store.dispatch(addNumber(2))
store.dispatch(addNumber(3))
unsubscribe()
复制代码
combineReducers所作的事情是生成一个函数,调用这个函数时,会执行每一个reducer,并将执行的结果从新整合为一个对象,做为新的state。当调用store.dispatch(addNumber(1))
时,两个reducer(caculate和switchTab)都会被调用,它们执行的结果会组成一个新的state树。
代码段中的:
const reducer = combineReducers({ caculate, switchTab })
复制代码
和如下代码段的效果是同样的,
function reducer (state = {}, action) {
return {
caculate: caculate(state.caculate, action),
switchTab: switchTab(state.switchTab, action)
}
}
复制代码
只是combineReducers会有更多的处理操做。
react-redux插件是专门用于在React中使用redux的,react-redux官网地址。
React Redux将展现组件(presentational components)和容器组件(container components)分离了。
Redux官网中有二者的对比表格:
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联系起来。通常使用React Redux提供的connect方法生成容器组件。
connect 方法
调用connect方法会返回一个包装函数,这个函数接收组件并返回一个新的包装组件。这里的包装组件便是上文说到的容器组件,容器组件能将展现组件和Redux联系起来。
connect的使用方法是:
connect(mapStateToProps? mapDispatchToProps?, mergeProps?, options?)(组件)
复制代码
Provider组件
全部的容器组件都须要可以访问Redux的store,若是不使用Provider组件,就只能在全部容器组件的属性中传入store,假如在组件树中深层嵌套了容器组件,可能有的展现组件也须要传入store属性。可是使用Provider组件包裹上应用根组件后,应用中的全部容器组件就都能访问到Redux的store了。
举个例子,定义容器组件的时候这样写:
import { connect } from 'react-redux'
import { addNumber, subtractNumber, multiplyNumber } from '../actions'
import Caculation from '../components/Caculation'
const mapStateToProps = state => ({
caculate: state.caculate
})
const mapDispatchToProps = dispatch => ({
plus: number => dispatch(addNumber(number)),
subtract: number => dispatch(subtractNumber(number)),
multiply: number => dispatch(multiplyNumber(number))
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(Caculation)
复制代码
那么展现组件Caculation的属性中就会有caculate,plus,subtract,multiply这几个属性,其中caculate是state.caculate;plus,subtract,multiply是三个函数,调用函数的时候能进行dispatch操做。
Redux只支持同步数据流,若是想要使用异步actions,须要使用applyMiddleware加强createStore。使用redux-thunk中间件可以发起同步或异步的actions,而且能够设计知足某种条件才dispatch actions。使用Redux Thunk以后,action creator能够返回一个函数,这个函数的第一个参数是dispatch,第二个参数是getState。 首先下载好redux-thunk,并使用applyMiddleware加强createStore:
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
...
const store = createStore(rootReducer, applyMiddleware(thunk))
复制代码
在thunk中间件的帮助下,就能够像下面这样使用异步action 了。在请求开始和请求结束的地方都触发一个action,在请求开始的地方触发的action能够用来展现一个加载中的loading图等。这里只简单地在请求开始时触发了action,没有作后续的使用。
const requestLeaderBoard = () => ({
type: REQUEST_LEADERBOARD
})
const receiveLeaderBoard = (json) => ({
type: RECEIVE_LEADERBOARD,
leaderboard: json.data
})
export function fetchLeaderBoard () {
return dispatch => {
dispatch(requestLeaderBoard())
return fetch(`这里用一个get请求的地址`)
.then(res => res.json())
.then(json => dispatch(receiveLeaderBoard(json)))
}
}
复制代码
为了学习redux写了一个练习项目:源码地址
定义的reducer执行了“多余”的次数。
@@redux/INITd.c.p.m.e.7
。@@redux/INITf.1.c.7.u.x
、@@redux/PROBE_UNKNOWN_ACTIONz.n.g.2.k.9
、@@redux/INITf.1.c.7.u.x
。有3个多出的reducer调用。状况2的问题请教了可爱的同事杨老板,杨老板找到了相应的源码给出了解答,而后我又按照相同的方式在源码中找了下,获得了状况1的答案。
(1)状况1出现的缘由:
当使用createStore建立好一个store以后,Redux会dispatch一个初始化action(以下),以保证每一个reducer返回它们的默认state。
dispatch({ type: ActionTypes.INIT })
复制代码
(2)状况2出现的缘由:
执行第一个action@@redux/INITf.1.c.7.u.x
是由于Redux在执行combineReducers的过程当中调用了一遍reducer,做用是检查调用reducer返回的初始state是否是undefined,若是是undefined会抛出一个错误。
const initialState = reducer(undefined, { type: ActionTypes.INIT })
复制代码
第二个action@@redux/PROBE_UNKNOWN_ACTIONz.n.g.2.k.9
也是在combineReducers执行的过程当中进行处理的,目的是检查action的名称和Redux内私有的名称是否重复,若是重复,就抛出一个错误。
第三个action和状况1同。