Redux学习笔记---简单使用以及源码阅读
前言
平时使用React做开发的同学对redux都不会陌生,这是一个基于flux架构的十分优秀的状态管理库。这是Redux官方文档对它的描述。
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。
实际上优秀的Redux并不是只作为React的拓展,它可以融合任何框架。本文作为笔者的学习笔记,同样也希望分享给大家。如果错漏,恳请指出。您的批评与指正是我前进路上的一大动力。
基础使用
首先先来介绍它的三个核心概念,分别是action、reducer以及store
- action
action是数据的唯一来源,通常使用store.dispatch()将action传入store
- reducer
reducer中一般放入一些逻辑处理,响应action并发送到store
- store
store就是把它们联系到一起的对象。包含了我们熟悉的getState()、dispatch()等方法
更为详尽的资料请阅读官方文档,笔者在这里就不在赘(抄)述(袭)
先来一个计数器
接下来我们先来实现一个计数器,直接上代码吧
app.js import React, { Component } from 'react' import store from './store' import { addCount, decCOUNT, decCount } from './actions/Count' class App extends Component { constructor(props) { super(props) this.state = { count: null } this.add = this.add.bind(this) this.dec = this.dec.bind(this) } componentDidMount() { console.log(store.getState()) this.setState({ count: store.getState().count }) } add() { store.dispatch(addCount()) this.setState({ count: store.getState().count }) } dec() { store.dispatch(decCount()) this.setState({ count: store.getState().count }) } render() { return ( <div> {this.state.count} <button onClick={this.add}>add</button> <button onClick={this.dec}>dec</button> </div> ) }} export default App复制代码
这是十分简单的代码,大家看的时候也可以直接略过。
actionTypes.js里面的定义了我们action的类型
actionTypes.js // ------------------add-------------------- export const ADD_COUNT = 'ADD_COUNT' export const DEC_COUNT = 'DEC_COUNT'复制代码
/actions/Count.js里面就是定义我们的action,然后我们把action传到reducer里面去处理
// /actions/Count.js import { ADD_COUNT, DEC_COUNT} from "../actionTypes"; export function addCount() { return { type: ADD_COUNT }} export function decCount() { return { type: DEC_COUNT }} // /reducers/Count.js import { ADD_COUNT, DEC_COUNT} from "../actionTypes"; const initialState = { count: 0 } export function Count(state = initialState, action) { const count = state.count switch (action.type) { case ADD_COUNT: return { count: count + 1 } case DEC_COUNT: return { count: count - 1 } default: return state }}复制代码
接下来就是store.js
//store.js import {createStore} from 'redux' import {Count} from './reducers/Count' const store = createStore(Count) export default store复制代码
这样,一个简单的计数器我们就做好了 看看效果吧。
React-Redux
React-Redux是Redux的官方React绑定库。它能够使你的React组件从Redux store中读取数据,并且向store分发actions以更新数据
让我们来看看它怎么使用
//app.js import React, {Component} from 'react' import store from './store' import { connect } from 'react-redux' import { addCount, decCount } from './actions/Count' class App extends Component { constructor(props) { ``` } render() { const { count } = this.props return ( ````` ) }} function mapStateToProps(state) { return { count: state.count } } export default connect(mapStateToProps)(App) //index.js ReactDOM.render(<Provider store={store}> <App /> </Provider>, document.getElementById('root'))复制代码
只要这样就OK啦,编写mapStateToprops函数,可以将store中的state和props关联起来,取值的时候只要 类似于const { count } = this.props就可以了
createStore
我们创建store的时候是调用了createStore函数,并将一个reducer作为参数传进去,现在我们来看看它的源码吧
import $$observable from 'symbol-observable' import ActionTypes from './utils/actionTypes' import isPlainObject from './utils/isPlainObject' export default function createStore(reducer, preloadedState, enhancer) { ```` return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }复制代码
可以看到createStore.js中,默认导出的是createStore函数,其接受的参数有三个,然后返回一个对象,也就是说我们创建的store对象中return中的几个方法。我们来一个一个看。
初始判断
if ( (typeof preloadedState === 'function' && typeof enhancer === 'function') || (typeof enhancer === 'function' && typeof arguments[3] === 'function') ) { ``` } if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { ``` } if (typeof enhancer !== 'undefined') { ``` }复制代码
首先它会做一些判断,我们只传入了reducer参数,所以不会走这些逻辑,然后定义一些变量
let currentReducer = reducer //就是我们传入的reducer let currentState = preloadedState //undefined let currentListeners = [] let nextListeners = currentListeners //listener数组,存储subscribe方法传入的函数 let isDispatching = false复制代码
getState()
然后就到了我们熟悉的store.getState()方法,如果在reducer计算的时候调用这个方法,就会报一个错误,。正常使用的话是返回currentState
function getState() { if (isDispatching) { throw new Error( ``` ) } return currentState }复制代码
subcribe()
然后就到了subscribe方法,这里采用的是一个观察者模式
function subscribe(listener) { if (typeof listener !== 'function') { ``` } if (isDispatching) { throw new Error( ``` ) } let isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) //push进观察者数组 return function unsubscribe() { //移除 if (!isSubscribed) { return } if (isDispatching) { throw new Error( ``` ) } isSubscribed = false ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } }复制代码
dispatch()
下面就是我们的dispatch方法了,它接收一个action作为参数,利用一开始传入的reducer去计算新的state,并在计算完成后依次调用subscribe方法传入的函数
function dispatch(action) { if (!isPlainObject(action)) { throw new Error( '``` ) } if (typeof action.type === 'undefined') { throw new Error( ``` ) } if (isDispatching) { ``` } try { isDispatching = true currentState = currentReducer(currentState, action) //这里就是调用reducer去计算新的state } finally { isDispatching = false } const listeners = (currentListeners = nextListeners)//计算完之后开始执行观察者数组里面的函数 for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }复制代码
combineReducer
我们不可能所有的逻辑都放在一个reducer里面,要拆分reducer,这时候可以用到combineReducer。
基础用法
//Goods.js const initialState = { goods: [{ price: 100, size: 'M', id: 0 }, { price: 200, size: 'L', id: 1 }]} export function Goods(state = initialState, action) { return state }复制代码
//store.js
const rootReducers = combineReducers({
Count,
Goods})
const store = createStore(rootReducers)复制代码
这个时候我们发现state被合并成了一个新的state
源码
让我们来解开combineReducer的面纱吧!看combineReducers方法之前,我们先看assertReducerShape方法
assertReducerShape主要是对传入的reducers做了一层筛选,保证reducers的initialState存在,以及它们的action需要有自己的命名空间
function assertReducerShape(reducers) { Object.keys(reducers).forEach(key => { const reducer = reducers[key] const initialState = reducer(undefined, { type: ActionTypes.INIT }) if (typeof initialState === 'undefined') { throw new Error( ``` ) } if ( typeof reducer(undefined, { type: ActionTypes.PROBE_UNKNOWN_ACTION() }) === 'undefined' ) { throw new Error( ``` ) } } ) } 复制代码
接下来就是combineReducers
const reducerKeys = Object.keys(reducers) const finalReducers = {} for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (process.env.NODE_ENV !== 'production') { //对reducers的第一层筛选 if (typeof reducers[key] === 'undefined') { warning(`No reducer provided for key "${key}"`) } } //最后把reducers放在finalReducers数组里 if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) let unexpectedKeyCache if (process.env.NODE_ENV !== 'production') { unexpectedKeyCache = {} } let shapeAssertionError try { //这里就是对reducers的又一层筛选 assertReducerShape(finalReducers) } catch (e) { shapeAssertionError = e } return function combination(state = {}, action) { if (shapeAssertionError) { throw shapeAssertionError } if (process.env.NODE_ENV !== 'production') { ``` } let hasChanged = false const nextState = {} for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey === 'undefined') { ``` } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state }复制代码
我们可以看到最后它返回了一个combination函数,实际上就是合并过后的store,只要有一个小store(这边我们姑且这么叫,每一个reducer对应一个小store)发生了重新的计算,就会返回一个新的state状态,也就是我们最终得到的store
applyMiddleware
Redux还为我们提供了强大的中间件拓展,让我们来看一下。
基础使用
这里我们以redux-thunk为例,来学习中间件的用法
//store.js import thunk from 'redux-thunk' const store = createStore(rootReducers, applyMiddleware(thunk)) //actions/Count.js export function asyncTest() { return (dispatch,getState) => { setTimeout(() => { dispatch(asyncData()) }, 2000); } } export function asyncData(data) { return { type: ASYNC_DATA, count: -100 } } //App.js componentDidmount(){ store.dispatch(asyncTest() }复制代码
我们来看一下效果
源码
然后就是得来看看applyMiddleware干了啥了。
export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }复制代码
这个createStore是个啥意思呢,这个时候就要看回我们createStore的源码了。跟没使用中间件的时候不一样。这个时候会走到这个逻辑
return enhancer(createStore)(reducer, preloadedState)
我们使用的时候是这样
import { createStore, combineReducers, applyMiddleware} from 'redux' const store = createStore(rootReducers //reducer, //preloadedState=undefined applyMiddleware(thunk) //这里的返回结果才是真正的enhancer,所以我们要看applyMiddleware返回了什么 )复制代码
applyMidderware调用是会返回一个函数,我们姑且称他为函数A,函数A接受一个createStore参数,返回一个函数B,函数B可以调用createStore,生成store。所以这里应该是函数B执行,参数是createStore。这个createStore是我们import进来的。这里调用createStore生成一个store,然后对middlewares遍历(我们只传入了thunk),为每一层中间件传入getState和dispatch。然后就执行compose方法
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce((a, b) => (...args) => a(b(...args))) }复制代码
compose很短,而且注释直接告诉我们,把传入的函数数组从前往后进行嵌套调用。我们传入compose函数的参数都是形如next=>action=>{}这样的函数,经过compose的处理后,每一个函数的next实则是action=>{}的返回值,执行完之后就将原生的dispatch方法传入,更新state
dispatch = compose(...chain)(store.dispatch)
这个时候我们再来看我们是用的thunk中间件。我们就可以理解了它的_ref里面为什么有dispatch和getState,因为是在applyMiddleware函数中传入的
function createThunkMiddleware(extraArgument) { return function (_ref) { var dispatch = _ref.dispatch, getState = _ref.getState; return function (next) { return function (action) { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; }; } } var thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;复制代码
如果他的action是一个函数,那么它就会直接终止中间件的调用,直接执行action,更新state
最后
Redux我觉得最难理解的就是它的applyMiddleware方法了,我现在的弟弟水平也只能理解到这种程度。希望以后再回来看的时候会有更深的理解吧!