Redux源码解析

356 阅读7分钟

什么是Redux

官方描述:

Redux 是 JavaScript 状态容器,提供可预测化的状态管理

Redux工作流程

  1. 用户通过 view 触发 action, 是通过 dispatch 来派发 action
  2. 然后调用 reducer方法,传入当前的 stateaction, 通过计算返回新的 state
  3. state 发生变化后,就会调用 store 的 subscribe监听的函数,来更新视图

下面就是一个简单流程图:

image.png

在整个过程中,数据是单向流动的,如果想对其修改,只能通过 dispatch 派发 action

Redux的组成

store

store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 store。

import { createStore } from 'redux';
const store = createStore(reducer);

上面代码中,creatStore函数接收了 reducer函数 作为参数,返回新生成的store对象

store有两个核心方法,分别是 getStatedispatch 。前者用来获取store的内容(state) ,后者用来修改store的内容

state

stroe 对象中存放的数据

action

state 的变化,会导致 view 的变化。但是,用户接触不到 state,只能接触到 view。所以,state 的变化必须是 view 导致的,action 相当于是 view 发出的通知,表示 state 应该要发生变化了。

// action 是行为的抽象,它是一个对象。其中的type属性是必须的,表示 action 的名称。其他属性可以自由设置
const add = {
  type: 'ADD_TODO',
  payload: 'Learn Redux'
};

actionCreator

在我们写代码的时候 每次更新state的时候,由于action是对象,我们每次写的时候都要写一个 type 属性,这样感觉会比较冗余,这是我们可以自定义一个函数,在里面指定好 type,然后传入不同的修改信息就可以了,这个函数就叫做 actionCreator

  • action 是一个对象,用来描述 state 的变化
  • actionCreator是一个函数,用来创建 action,大家不要混淆了哦

举个🌰

 // addTodo函数就是一个 actionCreator。
function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
// 通过 addTodo 创建了一个action
const action = addTodo('Learn Redux');

dispatch

dispatch 是 view 发出 action 的唯一方法 dispatch(action)

import { createStore } from 'redux';
const store = createStore(fn);

store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});

// 用 actionCreator 的话就可以这么写
store.dispatch(addTodo('Learn Redux'))

reducer

store 收到 action 以后,必须给出一个新的 state,这样 view 才会发生变化。而这正是 reducer 要做的事情,更新 state

reducer:是一个纯函数,用来修改store的状态,接收两个参数state,action

const defaultState = 0;
//定义了一个reducer函数
const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case 'ADD':
      return state + action.payload;
    default: 
      return state;
  }
};
//调用函数,返回新的state
const state = reducer(1, {
  type: 'ADD',
  payload: 2
});

上面代码中,reducer函数收到名为 ADD 的 action 以后,就返回一个新的 state,作为加法的计算结果。

但是在实际应用中,reducer 函数并不需要像上面这样子调用,store.dispatch 方法会触发 reducer 的自动执行,为此store 需要知道 reducer 函数,做法就是在生成 store 的时候,将 reducer 传入createStore 方法。

subscribe

store允许使用store.subscribe方法设置监听函数,一但 state 发生变化,就会自动执行这个函数

import {createStore} from "redux"
const store = createStore(reducer)
store.subscribe(listener)

显然,只要把view的更新函数(对于react项目,就是组件的render方法或者setState方法)放入listener,就会实现view的自动渲染

store.subscribe 方法返回一个函数,调用这个函数就可以解除监听

let unsubscribe= store.subscribe(()=>{
      console.log(store.getState())
})
unsubscribe()

Redux的特点

单向数据流,在单向数据流中,数据的变化的可预测的。

源码

在这里我们看下 redux的源码,其实并不复杂,主要导出了已下这几种方法供我们使用

code.png

createStore

这里首先看第一个 createStore,使用方法:

import { createStore, applyMiddleware } from "redux";

const store = createStore(
  reducer,
  initState,
  applyMiddleware(middleware1, middleware2)
);

createStore方法接收三个参数

  • 第一个为 reducer, 是一个纯函数
  • 第二个为初始化的 state
  • 第三个是中间件对dispatch进行扩展

我们看createStore 源码,去掉了ts部分,建议耐心看完~


export default function createStore(
  reducer,
  preloadedState,
  enhancer
) {
  // ...... 去掉了对参数类型校验部分

  // 这里如果第二个参数为函数且第三个参数未传 就会认为第二个参数是 enhancer, preloadedState 为 undefined
  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. Instead, received: '${kindOf(
          enhancer
        )}'`
      )
    }
    // 如果有传入中间件函数 则会接收 createStore 作为参数,并返回一个函数把 reducer preloadedState 作为参数传入
    return enhancer(createStore)(
      reducer,
      preloadedState
    ) 
  }
  // 这里则是对 reducer类型做的校验 必须是函数
  if (typeof reducer !== 'function') {
    throw new Error(
      `Expected the root reducer to be a function. Instead, received: '${kindOf(
        reducer
      )}'`
    )
  }
    
  // 记录当前的 reducer
  let currentReducer = reducer
  // 记录当前 store 中存的state  
  let currentState = preloadedState
  // 用于记录在 subscribe中订阅的事件
  let currentListeners: (() => void)[] | null = []
  // 副本 其实主要对listeners的操作主要在 nextListeners
  let nextListeners = currentListeners
  // 记录当前是否正在进行 dispatch
  let isDispatching = false

  // 如果 nextListeners 和 currentListeners 相等的话 更改nextListeners让指向 currentListeners 的副本,确保他两个不指向同一个引用
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  // ------ getState 返回当前的 currentState
  function getState() {
    if (isDispatching) {
      throw new Error(
        'You may not call store.getState() while the reducer is executing. ' +
          'The reducer has already received the state as an argument. ' +
          'Pass it down from the top reducer instead of reading it from the store.'
      )
    }
    return currentState
  }

  // ------ 监听函数 收集依赖 入参是一个函数
  function subscribe(listener: () => void) {
    if (typeof listener !== 'function') {
      throw new Error(
        `Expected the listener to be a function. Instead, received: '${kindOf(
          listener
        )}'`
      )
    }
    // 禁止在 reducer 的时候调用 dispatch
    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/store#subscribelistener for more details.'
      )
    }

    // 防止多次调用 unsubscribe 函数
    let isSubscribed = true
    // 在这里确保让nextListeners 和 currentListeners不指向同一个引用
    ensureCanMutateNextListeners()
    // 对 nextListeners 进行操作 push listener
    nextListeners.push(listener)
    // 返回解绑函数
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See https://redux.js.org/api/store#subscribelistener for more details.'
        )
      }

      isSubscribed = false
      // 同样的操作 确保 确保 他两不指向同一个引用
      ensureCanMutateNextListeners()
      // 删除操作 还是对 nextListeners 进行修改
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }

  // ------ 更新state 触发订阅
  function dispatch(action) {
    // 参数校验 可以忽略
    if (!isPlainObject(action)) {
      throw new Error(
        `Actions must be plain objects. Instead, the actual type was: '${kindOf(
          action
        )}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
      )
    }
    
    // 如果正在dispatch 则不允许调用 防止在reducer中调动dispatch造成死循环
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }
    // dispatch 只能执行完一个再执行另一个 相当于加锁操作 finally 后才允许继续 dispatch
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    // 把 nextListeners 赋值为 currentListeners listeners, 然后遍历监听的listener数组,触发订阅
    
    /**
    提问:为什么要用nextListeners currentListeners 两个数组?按道理发布订阅模式只有一个listeners就可以的
    
    分析:
    我们在store中用nextListeners currentListeners 两个数组
    但是在订阅(subscribe),解除订阅(unsubscribe)都是用的 nextListeners
    在更新订阅的时候,也就是下面,listeners = currentListeners = nextListener,
    遍历用的listeners,虽然这里是相等的,但是在之后新增 订阅,解除订阅之前都会通过 ensureCanMutateNextListeners 让他两个指向不同的引用
    
    答:
    所以在遍历的时候使用currentListeners, 要对数组操作的时候就用nextListeners, 就是为了确保在下面遍历过程中不出问题
    如果是同一个引用,在遍历的时候我们去解除一个订阅,就会导致数组少了一项,从而导致最遍历缺失(因为在react中,我们可以在A组件执行的时候调用 unsubscribe 去解除B组件的订阅)
    eg:
    const unsbscribeA = store.subscribe(() => {console.log('a')})
    srore.subscribe(() => {
        unsbscribeA()
        console.log('b')
    })
    srore.subscribe(() => {console.log('c')})
    在上面我们监听了三个函数,但是在执行第二个的时候我们调用了 unsbscribeA, 则会 在currentListeners 里删除对应项,如果我们只用了一个监听数组的话,则就会导致遍历到第二项的时候,数组被删了一个,第三个监听的函数就不会被执行,引发异常
    */
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }
  
  // ------ 替换 reducer
  function replaceReducer(nextReducer){
    if (typeof nextReducer !== 'function') {
      throw new Error(
        `Expected the nextReducer to be a function. Instead, received: '${kindOf(
          nextReducer
        )}`
      )
    }

    currentReducer = nextReducer
   
    dispatch({ type: ActionTypes.REPLACE })
    return store
  }


  // 执行一次 dispatch 用来初始化数据
  dispatch({ type: ActionTypes.INIT })

  const store = ({
    dispatch: dispatch as Dispatch<A>,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable // 内部方法 忽略不看
  }
  return store
}

getState

通过这个方法可以获取到最新的 state

// 比较简单 就是返回了当前的state
function getState(){
    return currentState
}

replaceReducer

替换当前的reducer 并重新初始化state

function replaceReducer(nextReducer){
    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE })
    return store
}

subscribe

在 redux 里 subscribe, dispatch 表示 发布-订阅者模式

subscribe用来注册监听事件,,收集依赖listeners数组中, 并返回一个函数用来解绑监听函数

dispatch

是用来派发 action 修改state的唯一方式

dispatch(action)后,redux会调用reducer并传入action, state执行完成后返回新的 state,最后将listeners数组中的函数遍历依次执行,触发订阅

具体实现逻辑请看上面源码中的注释~