Redux概念
Redux的官网中用一句话来说明Redux是什么:
Redux是JavaScript的可预测的状态容器。
Redux和React没有直接关系,只是结合的比较好。Redux可以独立于其他框架,你也可以使用jq + redux。
Redux有三大原则,也分别对应三个非常重要的概念: store, action以及reducer
单一数据源
一个应用只有一个store,store就像一个仓库或者容器,里面存放的是整个应用的状态 state tree,就像这样
{
count: 0
}
State是只读的
不能直接修改state,唯一想改变state的方法就是触发action。
action描述了对象的行为,也就是描述了这个对象想要做什么
{
type: 'ADD_COUNT',
count: 1
}
就像上面这样,action有一个type属性,用来描述这个action的行为是什么
必须使用纯函数,reducer来修改state
reducer是一个纯函数,什么是纯函数:
- 不改变函数输入的参数
- 相同的输入必然得到相同的输出
Math.random() 每次返回的值都不一样,所以它不是一个纯函数
3. 无副作用
比如,函数内部还有其他的逻辑,像读写系统文件、像服务器发送一个强求、操作dom。
也就是说,不改变外部的任何状态
reducer函数这么写:
const initState = {
count: 0
}
function addReducer(state = initState, action) {
switch (action.type) {
case 'ADD_COUNT':
return {
...state,
count: state.count + action.count
}
default:
return state
}
}
接收一个初始state,匹配到action的type类型之后,做出修改,返回一个新的state
搭建项目
在实现redux之前,得先知道redux具体的使用方法以及有哪些Api,看看redux是怎么使用的
随便找个目录,然后使用create-react-app新建一个项目
npx create-react-app redux-demo
cd redux-demo
进入项目的目录后,分别安装redux redux-logger redux-thunk
yarn add redux redux-logger redux-thunk
安装好之后,删除src目录下的其他文件,仅保留index.js。
简单的运用一下redux,为了方便(偷懒),就不再新建action、reducer和store的文件夹了,都放在一起使用
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux'
// state 初始状态
const initState = {
count: 100
}
// reducer函数
const reducer = (state = initState, action) => {
switch (action.type) {
case 'PLUS':
return {
...state,
count: state.count + action.count
}
case 'MINUS':
return {
...state,
count: state.count - action.count
}
default:
return state
}
}
let store = createStore(reducer)
const { dispatch, getState, subscribe } = store
// 创建两个action,一个是数量加13,一个是数量减11
const plusAction = {
type: 'PLUS',
count: 13
}
const minusAction = {
type: 'MINUS',
count: 11
}
class App extends Component {
constructor(props) {
super(props)
this.plus = this.plus.bind(this)
this.minus = this.minus.bind(this)
this.opration = this.opration.bind(this)
}
state = {
count: getState().count
}
componentDidMount(){
subscribe(() => {
console.log(getState())
})
}
opration(action) {
dispatch(action)
this.setState({
count: getState().count
})
}
plus() {
this.opration(plusAction)
}
minus() {
this.opration(minusAction)
}
render() {
return (
<div>
<div>{this.state.count}</div>
<div>
<button onClick={this.plus}>+</button>
</div>
<div>
<button onClick={this.minus}>-</button>
</div>
</div>
)
}
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
先来看看上面这段代码的演示(白嫖的gif制作,有水印。。。):
当点击“+”时,数字会加13,当点击“-”时,数字会减11。
redux流程解析
首先,点击了按钮,触发了方法,然后调用了store.dispatch(action),接着reducer收到派发的action,开始处理数据
匹配到type为“PLUS”时,count加13;匹配到type为“MINUS”时,count减11;
返回一个新的state,使用store.getState().count拿到改变后的count值,再setState改变组件的状态。
因此,整个流程是这样的:
同时,subscribe订阅了store的状态变化,每次dispatch一个action,subscribe的回调函数都会执行
实现createStore
可以发现,使用的dispatch,getState等API都是来自store,而store来自createStore。
所以,先来看看createStore是怎么实现的
- createStore接收一个reducer参数,返回一个store
- store是一个对象,包含了dispatch、getState和subscribe等方法
- subscribe订阅了store的状态变化,每次dispatch一个action,subscribe的回调函数都会执行,是发布订阅模式
既然state的状态修改都是依赖于store提供的方法,而store又是createStore函数返回,那么,不难知道,state存在于createStore函数中
调用store的dispatch,state会改变,就说明dispatch函数内部,调用了reducer函数来改变了state,每次调用都会改变state的值
这其实是一个闭包,根据闭包的特性,state的值一直都会被保存下来
src目录新建一个文件夹redux,里面新建createStore.js
// createStore.js
const createStore = reduer => {
let state;
// subscibe结合dispatch是一个发布订阅模式
// 需要一个订阅的数组
let listners = []
const subscribe = callback => {
// 将所有要执行的函数存起来,订阅完成,等待被调用
listners.push(callback)
}
const dispatch = action => {
}
// getState返回state
const getState = () => state
// 向外暴露的store对象
return {
subscribe,
dispatch,
getState,
}
}
一个基本结构就搭成了。
// dispatch接收一个action
// dispatch要调用reducer函数来修改state
// dispatch也是一个发布者,将已经订阅的列表依次执行
const dispatch = action => {
state = reducer(state, action)
listners.forEach(listner => {
listner()
})
}
顺便补充两点实现
- 在store.dispatch发出之后,到reducer返回state之前,有几个需要注意的点:
(1) 不能在reducer中使用store.getState()
(2) 不能在reducer中派发action,即store.dispatch()
(3 )不能在reducer中订阅,即store.subscribe
总结为一句话:reducer中不能嵌套调用store的方法
-
createStore需要初始化一下,也就是执行一次dispatch。
这样做的目的在没有调用store.dispatch时,第一次使用store.getState()可以拿到reducer返回的初 始值(因为reducer里,action的type匹配不到会返回传入的默认的initState),不然拿到的会是undefined。
完整的createStore:
const ActionTypes = {
INIT: `@@redux/INIT${Math.random()}`,
}
const createStore = reduer => {
let state;
// 设置一个变量,标志是否正在dispatch
let isDispatching = false
// subscibe结合dispatch是一个发布订阅模式
// 需要一个订阅的数组
let listners = []
const subscribe = callback => {
// 不允许在reducer内部订阅store
if(isDispatching) {
throw new Error('You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api-reference/store#subscribelistener for more details.');
}
// 将所有要执行的函数存起来,订阅完成,等待被调用
listners.push(callback)
}
// dispatch接收一个action
// dispatch要调用reducer函数来修改state
// dispatch也是一个发布者,将已经订阅的列表依次执行
const dispatch = action => {
// 不允许在reducer内部派发action
if(isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
state = reducer(state, action)
}finally {
isDispatching = false
}
listners.forEach(listner => {
listner()
})
}
// getState返回state
const getState = () => {
// 不允许在reducer内部获取调用此方法
if(isDispatching) {
throw new Error('You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api-reference/store#subscribelistener for more details.');
}
return state
}
// 初始化store,第一次调用store.getState()可以拿到reducer返回的默认值
dispatch({ type: ActionTypes.INIT })
// 向外暴露的store对象
return {
subscribe,
dispatch,
getState,
}
}
在redux目录下新建一个index.js,把createStore引入index.js,后续的其他方法也会引入到这个文件,作为统一向外暴露的文件
export { createStore } from './createStore'
将src/index.js里createStore替换成自己实现的
// import { createStore } from 'redux'
import { createStore } from './redux'
再来看看效果,发现和之前一样,说明createStore没有问题。
这里还有个小问题,其实createStore一共可以接收三个参数。
createStore(reducer, preloadedState, enhancer)
enhancer后面我们会讲到,先来看下preloadedState。
preloadedState是传递给reducer的一个默认参数,之前是在index.js声明了一个initState,同时让reducer的state参数拥有一个默认值initState。
其实也可以传给createStore,这样也是一样的效果。
const reducer = (state = {}, action) => {...}
const initState = {
count: 100
}
let store = createStore(reducer, initState)
实现applyMiddleware
一般我们在使用applyMiddleware的时候,都是搭配中间一起使用的。
createStore(reducer, applyMiddleware(中间件))
下面使用一个例子,引入redux-logger来看下
import logger from 'redux-logger'
...
...
let store = createStore(reducer, initState, applyMiddleware(logger))
createStore第二个参数和第三个参数在这里说明一下
上面已经说过,第二参数是preloadedState,传给reducer的初始参数,那第三个参数是做什么的?
根据官方的说明,createStore的第三个参数定义:
type enhancer = (next: StoreCreator) => StoreCreator
也就是说 enchancer接收一个createStore,然后返回一个新的createStore
调用这个createStore,得到的也是一个store对象
也就是说,enhancer是一个函数,这个函数接收createStore为参数,做了某些事之后,返回了新的createStore。
那么问题来了,我们看到过很多示例,都是这样使用的:
createStore(reducer, enhancer)
可是第二个参数不是preloadedState吗? 其实看完源码就知道针对第二个参数有一个判断:
if (typeof preloadedState === 'function' && typeof enhancer === 'function' || typeof enhancer === 'function' && typeof arguments[3] === 'function') {
throw new Error('It looks like you are passing several store enhancers to ' + 'createStore(). This is not supported. Instead, compose them ' + 'together to a single function.');
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.');
}
return enhancer(createStore)(reducer, preloadedState);
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.');
}
- 如果第二个参数preloadedState和第三个参数enhancer都为函数,或者enhancer为函数并且还有第四个参数也为函数,报错
- 如果preloadedState是函数并且enhancer为undefined,也就是enhancer没有传或者手动传了undefined,则将preloadedState赋值给enhancer,preloadedState值就相当于没有,赋值为undefined
- 如果enhancer有值,而且还不是函数,报错;如果同时也是函数,那么就执行这个enhancer函数,传入createStore,返回一个createStore函数,再调用并且传入reducer和preloadedState
- 如果reducer不是函数,报错
看到这里就明白了第二个参数可以传reducer的初始state,也可以直接传enhancer函数,所以createStore(reducer, enhancer)就相当于createStore(reducer, undefined, enhancer)
enhancer的形式已经知道是什么样子了,接收createStore,返回新的newCreateStore,再调用这个返回的newCreateStore得到新的store
先改写createStore
const ActionTypes = {
INIT: `@@redux/INIT${Math.random()}`,
}
const createStore = (reducer, preloadedState, enhancer, ...args) => {
let state;
// 设置一个变量,标志是否正在dispatch
let isDispatching = false
// subscibe结合dispatch是一个发布订阅模式
// 需要一个订阅的数组
let listners = []
if (typeof reducer !== 'function') {
return
}
if ((typeof preloadedState === 'function' && typeof enhancer === 'function') || (typeof enhancer === 'function' && typeof args[0] === 'function')) {
return
}
if (typeof preloadedState === 'function') {
if (typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
return
}
let newCreateStore = enhancer(createStore)
let newStore = newCreateStore(reducer, preloadedState)
return newStore
}
state = preloadedState
const subscribe = callback => {
// 不允许在reducer内部订阅store
if (isDispatching) {
throw new Error('You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api-reference/store#subscribelistener for more details.');
}
// 将所有要执行的函数存起来,订阅完成,等待被调用
listners.push(callback)
}
// dispatch接收一个action
// dispatch要调用reducer函数来修改state
// dispatch也是一个发布者,将已经订阅的列表依次执行
const dispatch = action => {
// 不允许在reducer内部派发action
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
state = reducer(state, action)
} finally {
isDispatching = false
}
listners.forEach(listner => {
listner()
})
}
// getState返回state
const getState = () => {
// 不允许在reducer内部获取调用此方法
if (isDispatching) {
throw new Error('You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api-reference/store#subscribelistener for more details.');
}
return state
}
// 初始化store,第一次调用store.getState()可以拿到reducer返回的默认值
dispatch({ type: ActionTypes.INIT })
// 向外暴露的store对象
return {
subscribe,
dispatch,
getState,
}
}
export default createStore
再来看applyMiddleware,中间件的使用都是传到applyMiddleware里,最后再传给createStore,像这样:
createStore(reducer, applyMiddleware(logger))
createStore(reducer, applyMiddleware(thunk))
createStore(reducer, applyMiddleware(saga))
这说明了applyMiddleware这个函数接收的参数是中间件,返回的是enhancer函数
来结合redux-logger的源码看下:
const logger = ({getState}) => next => action => {
console.group('action', action.type)
console.info('prev state', getState())
console.info('action', action)
let result = next(action)
console.log('next state', getState())
console.groupEnd()
return result
}
分析上面的代码:
let result = next(action)
这个next函数接收的一个action,根据之前打印的logger能够看出state发生改变是在next(action)之后,而redux里想要改变state就得先dispatch一个action
因此,这个next其实就是一个dispatch函数
而这个函数内部返回的函数也接收一个参数action,因此返回的也是一个dispatch函数,newDispatch
- logger函数接收一个参数,getState是被解构出来的,实际上接收的是store
- 返回一个函数,接收了一个next(next为dispatch)作为参数,返回一个dispatch函数
- dispatch函数接收一个action,并且在dispatch函数内部打印了一些log
分析完logger函数的流程,其实可以发现中间件做的事情很简单,就是增强dispatch函数的功能
正式实现applyMiddleware
const applyMiddleware = middleware => {
// applyMiddleware返回的是一个enhancer函数
// enhancer函数接收createStore作为参数,返回一个新的newCreateStore
// newCreateStore接收reducer,返回新的newStore
return createStore => {
return (reducer, preloadedState) => {
// 调用createStore,拿到store
let store = createStore(reducer, preloadedState)
// 将store传入中间件函数,执行中间件的逻辑,拿到一个新函数
const newFunc = middleware(store)
// 新函数接收一个dispatch,返回一个dispatch
const newDispatch = newFunc(dispatch)
// 返回dispatch更新后的store
return { ...store, dispatch: newDispatch }
}
}
}
将redux替换成自己的文件
// import { createStore, applyMiddleware } from 'redux'
import { createStore, applyMiddleware } from './redux'
运行一下结果,仍然可以正常打印出来结果。
compose
applyMiddleware功能虽然已经实现,但是还不支持多个中间件的场景,例如我们会传入多个中间件:
createStore(reducer, applyMiddleware(logger,saga,thunk))
那么applyMiddleware内部是怎么处理这么多中间件的呢?
源码里使用了compose组合函数
const compose = (...args) => {
if (!args.length) {
return arg => arg
}
if (args.length === 1) {
return args[0]
}
return args.reduce((prev, curr) => (...args) => prev(curr(...args)))
}
首先,要拿到一个 dispatch => newDispatch的形式,也就是将每个中间件都执行一遍,拿到返回的那个函数
const applyMiddleware = ()...middlewares) => {
// applyMiddleware返回的是一个enhancer函数
// enhancer函数接收createStore作为参数,返回一个新的newCreateStore
// newCreateStore接收reducer,返回新的newStore
return createStore => {
return (reducer, preloadedState) => {
// 调用createStore,拿到store
let store = createStore(reducer, preloadedState)
// 将store传入中间件函数,执行每一个中间件的逻辑,拿到每一个中间返回的新函数
let funcList = middlewares.map(middleware => middleware(store))
// 然后执行组合函数
const newFunc = compose(...funcList)
// 新函数接收一个dispatch,返回一个dispatch
const newDispatch = newFunc(store.dispatch)
// 返回dispatch更新后的store
return { ...store, dispatch: newDispatch }
}
}
}
上面的组合函数就是函数套娃,一个函数套另一个函数的调用结果,拿applyMiddleware来看,最终要返回一个新的disptach函数
假设现在有三个中间件,分别为
全部执行完依次为:
let fn1 = dispatch => newDispatch1
let fn2 = dispatch => newDispatch2
let fn3 = dispatch => newDispatch3
执行compose的流程为:
- (fn1, fn2) => (...args) => fn1(fn2(...args)),执行结果保存为memoFunc
- 下次执行的是(memoFunc, fn3) => (...args) => memoFunc(fn3(...args))
- 执行完会返回下面的最终返回值,其实是从右向左依次执行完所有的函数
- (memoFunc, fn3) => (...args) => memoFunc(fn3(...args))执行完的结果为 (...args) => (memoFunc(fn3(...args)))
- 将args替换成dispatch函数
- dispatch => memoFunc(fn3(dispatch))
- fn3(dispatch)的返回值是newDispatch3
- dispatch => memoFunc(newDispatch3)
- 再将memoFunc展开,dispatch => fn1(fn2(newDispatch3))
- fn2(newDispatch3)返回newDispatch2,dispatch => fn1(newDispatch2)
- 最终返回dispatch => newDispatch1,这个是compose函数值
- 执行compose函数时,会将所有的dispatch函数都执行一遍,所以每个中间件的逻辑都得以执行
这里在引入一个redux-thunk中间件,同时执行两个中间件看看效果
import thunk from 'redux-thunk'
...
...
let store = createStore(reducer, initState, applyMiddleware(logger, thunk))
异步的action
redux-thunk是一个处理异步逻辑的中间件,它允许我们在action到reducer中间做一些异步操作
来看下redux源码,src目录下新建一个redux-thunk文件夹,里面新建一个index.js
简化后的thunk代码,其实只有短短几行代码:
const thunk = ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState)
}
return next(action)
}
export default thunk
观看源码的时候,发现在createStore里,传递给中间件的dispatch是返回的newDispatch,结合thunk再来看,就很好理解了。
先来改下createStore函数
const applyMiddleware = (...middlewares) => {
// applyMiddleware返回的是一个enhancer函数
// enhancer函数接收createStore作为参数,返回一个新的newCreateStore
// newCreateStore接收reducer,返回新的newStore
return createStore => {
return (reducer, preloadedState) => {
// 调用createStore,拿到store
let store = createStore(reducer, preloadedState)
// 声明一个新的dispatch
let newDispatch;
// 声明一个需要传入中间件的结构
// dispatch为返回的新的dispatch
const middlewareApi = {
getState: store.getState,
dispatch(action){
return newDispatch(action)
}
}
// 将middlewareApi传入中间件函数,执行每一个中间件的逻辑,拿到每一个中间返回的新函数
let funcList = middlewares.map(middleware => middleware(middlewareApi))
// 然后执行组合函数
const newFunc = compose(...funcList)
// 新函数接收一个dispatch,返回一个dispatch
newDispatch = newFunc(store.dispatch)
// 返回dispatch更新后的store
return { ...store, dispatch: newDispatch }
}
}
}
那么再来看thunk函数,它的内部对action做了处理,如果action是一个函数,那么先执行这个action,并且传入的是dispatch和getState。
当具有副作用的action函数执行完成后,再调用dispatch,因为传入的是newDispatch,所以执行这个newDispatch的时候又回到了这个中间件,此时action是一个plain object,那么会继续执行next(action),也就是dispatch(action),继而调用reducer更改state的状态。
来模拟一个异步的action,获取数据,获取成功之后reducer能更改state,最终打印出来异步的数据
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
// import { createStore, applyMiddleware } from 'redux'
// import logger from 'redux-logger'
import { createStore, applyMiddleware } from './redux'
import logger from './redux-logger'
import thunk from 'redux-thunk'
const reducer = (state = {}, action) => {
switch (action.type) {
case 'PLUS':
return {
...state,
count: state.count + action.count
}
case 'MINUS':
return {
...state,
count: state.count - action.count
}
default:
return state
}
}
const initState = {
count: 100
}
let store = createStore(reducer, initState, applyMiddleware(logger, thunk))
const { dispatch, getState, subscribe } = store
// 创建两个action,一个是数量加13,一个是数量减11
const plusAction = {
type: 'PLUS',
count: 13
}
const minusAction = {
type: 'MINUS',
count: 11
}
// 从thunk的源码就可以知道,异步的action会接收一个dispatch
// 作用是执行完异步逻辑如请求接口后,再次调用dispatch,传入一个object对象的action
// 这个dispatch触发后,会调用reducer来更改state
const asyncAction = dispatch => {
setTimeout(() => {
dispatch({
type: 'Async',
username: '彭于晏',
size: 18
})
}, 3000)
}
class App extends Component {
constructor(props) {
super(props)
this.plus = this.plus.bind(this)
this.minus = this.minus.bind(this)
this.opration = this.opration.bind(this)
this.getAsyncInfo = this.getAsyncInfo.bind(this)
}
state = {
count: getState().count
}
opration(action) {
dispatch(action)
this.setState({
count: getState().count
})
}
plus() {
this.opration(plusAction)
}
minus() {
this.opration(minusAction)
}
getAsyncInfo(){
this.opration(asyncAction)
}
render() {
return (
<div>
<div>{this.state.count}</div>
<div>
<button onClick={this.plus}>+</button>
</div>
<div>
<button onClick={this.minus}>-</button>
</div>
<div>
<button onClick={this.getAsyncInfo}>获取异步数据</button>
</div>
</div>
)
}
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
我们模拟了一个3s的异步获取数据,来看下打印:
打印的效果是,3s后,打印出了异步获取的state状态
所以对于异步action来说,整个流程是这样:
View -> dispatch action(side effects function) -> 处理异步逻辑,拿到数据 -> dispatch action(plain object) -> reducer -> state -> View
实现combineReducers
总是发现哪里不对,原来是忘了combineReducers
我们不可能只在项目里写一个reducer函数,因为开发人员根据不同的功能划分,需要完成不同的模块
如果此时都是用了redux管理状态,那么就需要根据具体的功能来对reducer进行更具体的功能划分。
这个时候,combineReducers就登场了
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, combineReducers } from 'redux'
// import logger from 'redux-logger'
// import { createStore, applyMiddleware } from './redux'
import logger from './redux-logger'
// import thunk from 'redux-thunk'
import thunk from './redux-thunk'
const countState = {
count: 100
}
const moneyState = {
count: 100
}
const countReducer = (state = countState, action) => {
switch (action.type) {
case 'PLUS':
return {
...state,
count: state.count + action.count
}
case 'MINUS':
return {
...state,
count: state.count - action.count
}
case 'Async':
return {
...state,
...action.payload
}
default:
return state
}
}
const moneyReducer = (state = moneyState, action) => {
switch (action.type) {
case 'SAVE':
return {
...state,
count: state.count + action.count
}
case 'TAKE':
return {
...state,
count: state.count - action.count
}
default:
return state
}
}
let rootReducer = combineReducers({
countReducer,
moneyReducer,
})
let store = createStore(rootReducer, applyMiddleware(logger, thunk))
const { dispatch, getState, subscribe } = store
// 创建两个action,一个是数量加13,一个是数量减11
const saveAction = {
type: 'SAVE',
count: 10000,
}
const takeAction = {
type: 'TAKE',
count: 1,
}
// 创建两个action,一个是数量加10000,一个是数量减1
const plusAction = {
type: 'PLUS',
count: 13
}
const minusAction = {
type: 'MINUS',
count: 11
}
const asyncAction = newDispatch => {
setTimeout(() => {
newDispatch({
type: 'Async',
payload: {
username: '彭于晏',
size: 18,
}
})
}, 3000)
}
class App extends Component {
constructor(props) {
super(props)
this.plus = this.plus.bind(this)
this.minus = this.minus.bind(this)
this.opration = this.opration.bind(this)
this.getAsyncInfo = this.getAsyncInfo.bind(this)
this.take = this.take.bind(this)
this.save = this.save.bind(this)
}
state = {
count: getState().countReducer.count
}
opration(action) {
dispatch(action)
this.setState({
count: getState().countReducer.count
})
}
plus() {
this.opration(plusAction)
}
minus() {
this.opration(minusAction)
}
save() {
this.opration(saveAction)
}
take() {
this.opration(takeAction)
}
getAsyncInfo() {
this.opration(asyncAction)
}
render() {
return (
<div>
<div>{this.state.count}</div>
<div>
<button onClick={this.plus}>+</button>
</div>
<div>
<button onClick={this.minus}>-</button>
</div>
<div>
<button onClick={this.getAsyncInfo}>获取异步数据</button>
</div>
<div>
<button onClick={this.save}>存钱</button>
</div>
<div>
<button onClick={this.take}>取钱</button>
</div>
</div>
)
}
}
分别点击存钱和取钱,得到的打印结果:
可以看到,经过conbineReducer处理过的state,最终变成了我们传入这个函数的键名 -> 对应的state
redux目录下新建一个combineReducers.js
const combineReducers = reducerList => {
if (typeof reducerList !== 'object') {
return
}
// 检测state是否发生变化的标志
let hasChanged = false
// combineReducers的主要步骤
// combineReducers的返回值会传入createStore,因此这个返回值是一个reducer函数
// 声明一个新的空对象newState
// 遍历reducerList,拿到遍历出来每一个reducer,将每一个reducer都执行一遍
// 执行完拿到的state,赋予newState[key],如: newState['countReducer'] = countReducer执行结果 newState['moneyReducer'] = moneyReducer执行结果
// 返回newState,返回的newState将作为下次dispatch时的初始state
return (state = {}, action) => {
const newState = {}
for (let key in reducerList) {
let currentReducer = reducerList[key]
// prevState也就是上一次dispatch返回的state
// 比如第一次点击了存钱,调用store.dispatch(saveAction) 拿到最终的state值为
/**
* {
* countReducer: {
* count: 100
* },
* moneyReducer: {
* count: 10100
* }
* }
*/
// 当第二次store.dispatch 初始的的state值就为上面的结构,此时在遍历时,state[key]就能得到每个reducer下的state
// 再进行赋值的时候,就看可以按照key更改当前key对应的state
let prevState = state[key]
newState[key] = currentReducer(prevState, action)
// 检测state是否发生变化
hasChanged = hasChanged || newState[key] !== prevState
}
// 还是检测state是否发生变化,再对比最终的state和传入的reducerList的长度是否一致
hasChanged = hasChanged || reducerList.length !== Object.keys(state).length
// 如果state发生了变化,返回newState,作为dispatch中更改后的state值
// 如果reducer里返回的是原来的state,就返回原先的state
return hasChanged ? newState : state
}
}
export default combineReducers
将combineReducer导入到redux/index.js文件,并且作为成员导出
import createStore from './createStore'
import applyMiddleware from './applyMiddleware'
import combineReducers from './combineReducers'
export { createStore, applyMiddleware, combineReducers }
将combineReducers替换成自己实现的,看看效果:
// import { createStore, applyMiddleware, combineReducers } from 'redux'
import { createStore, applyMiddleware, combineReducers } from './redux'
分别点击了存钱和取钱两个按钮,右边log的打印日志可以看到已经实现了多个reducer的状态合并
bindActionCreators
bindActionCreators是结合react-redux的mapDispatchToProps来一起使用的,所以本章就不再展开,放到之后的react-redux实现一文里去实现
总结
- Redux是一个可预测JavaScript的状态管理容器
- 为什么要使用redux?
在React中,使用Redux的主要优势之一是它可以帮你处理应用的共享状态。如果多个组件需要访问同一状态,也就是所谓的“共享状态”时,一般情况下开发者会将该状态提升到附近的父组件,但是如果该父组件的下的子组件层级很深,那么就需要一层一层的向下传递,这样就会让开发者感到十分头疼。
并且,在一层一层向下传递的过程中,中间几个组件压根就不需要这些“共享状态”
但是使用了Redux,开发者就可以在应用的任何地方去访问这些“共享状态”,大大提升了开发效率,更有利于去管理这些状态。
-
Redux只是和React结合的比较好,这并不代表Redux就是React的必需品,它甚至可以在jq里使用。
-
Redux也并非一定要使用,如果开发的应用比较小,那么完全就没有能用到Redux的地方,一些需要管理的状态直接本地缓存就搞定了。
-
Redux有三大原则
- 单一数据源:也就是一个应用只有一个store,所有的状态都存放在store的 state tree中
- state状态只读:不能直接修改state tree的值,必须使用dispatch函数派发一个action来完成
- 使用纯函数reducer来更改state,redcuer接收一个初始的state和action,返回一个新的state。
纯函数有以下特点:
- 不能修改传入的参数
- 相同的输入必须有相同的输出
- 不能有任何副作用,包括读写文件系统,打印log,发送请求,更改dom等操作
- combineReducers
combineReducers可以接收多个reducer,会将所有的reducer集中到一个对象里,按照传入的key添加到对象中,每个reducer执行后返回的state会被赋值到对应的key。
- createStore
它可以接收三个参数,第一个是reducer,第二个是初始化的state对象,第三个是enhancer增强器函数
如果第二个参数传的是enhancer函数,如applyMiddleware('thunk'),那么它实际上就是createStore(reducer, undefined, applyMiddleware('thunk'))
createStore中包含有redux非常重要的三个api
- subscribe,接收一个callback,订阅store的状态变化,一旦调用了dispatch,就会执行callback
- dispatch,调用reducer函数来更改state,同时执行所有的订阅事件,所以dispatch和subscribe实际上就是一个发布订阅者模式
- getState,返回当前的state tree
- applyMiddleware,也是一个非常重要的API,它提供了redux的中间件的接入接口
applyMiddleware返回的是一个enhancer函数,内部的逻辑是在dispatch一个action到reducer之间可以写执行中间件的逻辑,比如打印,执行异步的action等
applyMiddleware也可以接收多个中间件,内部会使用compose组合函数来将所有的中间件逻辑串起来执行。
- Redux的中间件设计原则
Redux的中间件在设计时,基本都是一个格式, ({dispatch,getState}) => next => action => {/* 中间件的逻辑 */} 它的逻辑会在applyMiddleware函数内部被调用