Redux学习笔记---简单使用以及源码阅读

简介: Redux学习笔记---简单使用以及源码阅读

前言

平时使用React做开发的同学对redux都不会陌生,这是一个基于flux架构的十分优秀的状态管理库。这是Redux官方文档对它的描述。

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。

实际上优秀的Redux并不是只作为React的拓展,它可以融合任何框架。本文作为笔者的学习笔记,同样也希望分享给大家。如果错漏,恳请指出。您的批评与指正是我前进路上的一大动力。

基础使用

首先先来介绍它的三个核心概念,分别是actionreducer以及store

  • action

action是数据的唯一来源,通常使用store.dispatch()action传入store

  • reducer

reducer中一般放入一些逻辑处理,响应action并发送到store

  • store

store就是把它们联系到一起的对象。包含了我们熟悉的getState()dispatch()等方法

更为详尽的资料请阅读官方文档,笔者在这里就不在赘(抄)述(袭)

www.redux.org.cn/

先来一个计数器

接下来我们先来实现一个计数器,直接上代码吧

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

这样,一个简单的计数器我们就做好了 看看效果吧。

3.png

链接

React-Redux

React-ReduxRedux的官方 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中的stateprops关联起来,取值的时候只要 类似于 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)

4.png


这个时候我们发现state被合并成了一个新的state

源码

让我们来解开combineReducer的面纱吧!看combineReducers方法之前,我们先看assertReducerShape方法

assertReducerShape 主要是对传入的reducers做了一层筛选,保证reducersinitialState存在,以及它们的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()
}

我们来看一下效果 5.png 链接



源码

然后就是得来看看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),为每一层中间件传入getStatedispatch。然后就执行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里面为什么有dispatchgetState,因为是在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方法了,我现在的弟弟水平也只能理解到这种程度。希望以后再回来看的时候会有更深的理解吧!


相关文章
|
2月前
|
JavaScript CDN
Vue3基本使用(基础部分)
Vue3基本使用(基础部分)
41 5
|
4月前
|
API
Vue3进阶5个小知识点 附带源码
Vue3进阶5个小知识点 附带源码
27 0
|
4月前
|
存储 缓存 JavaScript
【面试题】当面试官让我回答React和Vue框架的区别......
【面试题】当面试官让我回答React和Vue框架的区别......
|
4月前
|
前端开发 JavaScript 容器
第二章 简单实现Hello React案例
第二章 简单实现Hello React案例
|
5月前
|
JavaScript API
【源码&库】跟着 Vue3 的源码学习 reactive 背后的实现原理
【源码&库】跟着 Vue3 的源码学习 reactive 背后的实现原理
47 0
|
8月前
|
JavaScript 前端开发 Java
Javaweb第四章---Vue与指令(入门必看)
Javaweb第四章---Vue与指令(入门必看)
38 0
|
9月前
|
异构计算 索引 容器
creator源码阅读系列第二篇之渲染
creator源码阅读系列第二篇之渲染
|
11月前
|
存储 JavaScript 数据管理
Vue —— 进阶 Vuex(零)(概念、工作原理、环境搭建、基本使用、getters)
Vue —— 进阶 Vuex(零)(概念、工作原理、环境搭建、基本使用、getters)
|
移动开发 JavaScript weex
Vue2源码系列-开篇
前言 大概两年前有学习过vue源码,当时学的比较粗糙,学习到的东西也比较少,差不多都快忘完了。最近打算再次捡起来,同时希望通过博客的方式加深理解和记忆,更希望能遇到一起交流的小伙伴~
|
前端开发
react实战笔记143:修改代码
react实战笔记143:修改代码
71 0
react实战笔记143:修改代码