The original link: blog.zebwu.com/2020/12/08/…

Redux provides a predictable state management solution. Take a look at the source code.

OverView

This series reads the source code implementation from the top down of everyday apis. Start by looking at the API exposed by Redux.

Source link

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}
Copy the code

Look, these are all familiar apis. This article starts with createStore.

Before reading the source code

Before reading createStore, let’s review what the Redux documentation says about createStore.

The official sample

import { createStore } from 'redux'

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return state.concat([action.text])
    default:
      return state
  }
}

const store = createStore(todos, ['Use Redux'])

store.dispatch({
  type: 'ADD_TODO'.text: 'Read the docs'
})

console.log(store.getState())
// [ 'Use Redux', 'Read the docs' ]
Copy the code

A store can maintain a state tree, which can only be changed through dispatch() actions. And can subscribe to state changes to update the view. How are these functions implemented? In what way does store maintain state? Want to understand the implementation behind it. Reading source code with questions in mind is more purposeful and efficient.

Start reading

closure

Open SRC/createstore. ts and look at the declared variables first

function createStore() {
  // Some error handling
	let currentReducer = reducer Reducer is currently in use
  let currentState = preloadedState as S
  let currentListeners: (() = > void|) []null = []
  let nextListeners = currentListeners
  let isDispatching = false
  // ...
  
  return store;
}
Copy the code

That’s right, redux maintains state in a simple way, in a closure, in the variable currentState.

getState

Getting State from a store is also simple. View the source code

function getState() :S {
  // Return the current State directly
  return currentState as S
}
Copy the code

I’m just going to return currentState. In this way, reading the source code does not seem very difficult. Let’s move on.

subscribe

View the source code

 function subscribe(listener: () => void) {
   	// Some error handling
    let isSubscribed = true // Mark whether to register to ensure that the registration can be cancelled only once.

    ensureCanMutateNextListeners() // Shallow copy a listener array
    nextListeners.push(listener) // Add a new listener

    return function unsubscribe() {
      if(! isSubscribed) {// The registration that has already been cancelled cannot be cancelled again.
        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()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1) // Delete the listener
      currentListeners = null // Manually dereference and release}}Copy the code

See the notes for instructions. Here we need to pay attention to ensureCanMutateNextListeners purposes.

function ensureCanMutateNextListeners() {
  if (nextListeners === currentListeners) {
    nextListeners = currentListeners.slice()
  }
}
Copy the code

If the nextListeners and currentListeners reference the same array, make a shallow copy of the Listeners to prevent bugs caused by registering/deregistering listeners during Dispatch.

dispatch

Next, take a look at the implementation of the familiar Dispatch function.

function dispatch() {
  if (isDispatching) {
    throw new Error('Reducers may not dispatch actions.')}try {
    isDispatching = true
    currentState = currentReducer(currentState, action) // Use reducer to obtain the new state
  } finally {
    isDispatching = false // The guarantee will be executed
  }

  // Traverses all registered listeners
  const listeners = (currentListeners = nextListeners)
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
  }

  return action
  }
}
Copy the code

Obtain the new state from reducer and update currentState. Then call all the listeners. Note that isDispatching = false is added in the finally statement, so as to ensure that isDispatching = False can be executed even when exceptions occur in the statements in the try block.

ReplaceReducer

View the source code

function replaceReducer<NewState.NewActions extends A> (
nextReducer: Reducer<NewState, NewActions>
) :Store<ExtendState<NewState.StateExt>, NewActions.StateExt.Ext> & Ext {
  // TODO: do this more elegantly; ((currentReduceras unknown) as Reducer<
    NewState,
    NewActions
  >) = nextReducer

  dispatch({ type: ActionTypes.REPLACE } as A)
  // change the type of the store by casting it to the new store
  return (store as unknown) as Store<
    ExtendState<NewState, StateExt>,
    NewActions,
    StateExt,
    Ext
  > &
    Ext
}
Copy the code

Replace currentReducer with nextReducer and change the type of store.

Reference

  • redux – Github

  • createStore | Redux

  • try… catch – JavaScript | MDN