What is the story

Official description:

Redux is a JavaScript state container that provides predictable state management

Redux workflow

  1. The user is triggered by the ViewactionIs throughdispatchTo distributeactionthe
  2. And then callreducerMethod, passed to the currentstateactionReturns the new state by calculating
  3. whenstateWhen it changes, it calls the storesubscribeListener function to update the view

Here is a simple flow chart:

The data flows in one direction throughout the process, and if you want to modify it, you can only dispatch actions through Dispatch

The composition of the story

store

A store is a place where data is stored, you can think of it as a container. There can only be one store for the entire app.

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

In the code above, the creatStore function takes the Reducer function as an argument and returns the newly generated store object

Store has two core methods, getState and Dispatch. The former is used to get the contents of the store (state), while the latter is used to modify the contents of the store

state

Data stored in the stroe object

action

If the state changes, the view changes. However, the user does not touch state, only view. Therefore, the state change must be caused by the view, and the action is the view’s notification that the state should change.

// Action is an abstraction of behavior. It is an object. The type attribute is required and represents the name of the action. Other attributes can be set freely
const add = {
  type: 'ADD_TODO'.payload: 'Learn Redux'
};
Copy the code

actionCreator

When we write code, every time we update the state, since the action is an object, we need to write a type property every time we write, so it feels redundant, so we can define a function, specify the type in it, and then pass in different modification information. This function is called actionCreator

  • Action is an object that describes state changes
  • ActionCreator is a function that creates actions, so don’t get confused

An 🌰

 // The addTodo function is an actionCreator.
function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
// Create an action with addTodo
const action = addTodo('Learn Redux');
Copy the code

dispatch

Dispatch is the only way a view can send an action dispatch(Action)

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

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

// Use actionCreator
store.dispatch(addTodo('Learn Redux'))
Copy the code

reducer

When the store receives the action, it must give a new state for the view to change. And that’s what the Reducer is doing, updating state

Reducer: is a pure function used to modify the state of the store and accepts two parameters state and action.

const defaultState = 0;
// Define a reducer function
const reducer = (state = defaultState, action) = > {
  switch (action.type) {
    case 'ADD':
      return state + action.payload;
    default: 
      returnstate; }};// Call the function to return the new state
const state = reducer(1, {
  type: 'ADD'.payload: 2
});
Copy the code

In the code above, when the Reducer function receives the action named ADD, it returns a new state as the result of the addition calculation.

However, in practical applications, the Reducer function does not need to be called as above. The store.dispatch method triggers the automatic execution of the reducer function, so the store needs to know the Reducer function. Pass the Reducer into the createStore method.

subscribe

Store allows you to set up a listener function using the store.subscribe method, which is automatically executed whenever state changes

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

Obviously, the view can be automatically rendered by simply putting the view’s update function (in the case of the React project, the component’s render or setState method) into the listener

The store.subscribe method returns a function that can be called to unsubscribe listening

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

The characteristics of the story

Unidirectional data flow. In unidirectional data flow, the change of data is predictable.

The source code

Here we look at the source code of Redux, in fact, is not complicated, mainly exported has been under the several methods for our use

createStore

Here’s how to use the first createStore:

import { createStore, applyMiddleware } from "redux";

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

The createStore method receives three parameters

  • The first reducer is a pure function
  • The second is the initialized state
  • The third is the middleware pairdispatchextend

The createStore source code has been removed from the ts section


export default function createStore(reducer, preloadedState, enhancer) {
  / /... The part of checking parameter type is removed

  // If the second parameter is a function and the third parameter is not passed, the second parameter is considered enhancer and preloadedState is undefined
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }
  // Checking incoming middleware must be a function
  if (typeofenhancer ! = ='undefined') {
    if (typeofenhancer ! = ='function') {
      throw new Error(
        `Expected the enhancer to be a function. Instead, received: '${kindOf( enhancer )}'`)}// If there is an incoming middleware function it receives createStore as an argument and returns a reducer preloadedState as an argument
    return enhancer(createStore)(
      reducer,
      preloadedState
    ) 
  }
  // Check the reducer type must be a function
  if (typeofreducer ! = ='function') {
    throw new Error(
      `Expected the root reducer to be a function. Instead, received: '${kindOf( reducer )}'`)}// Record the current reducer
  let currentReducer = reducer
  // Record the state of the current store
  let currentState = preloadedState
  // Used to record events subscribed to in subscribe
  let currentListeners: (() = > void|) []null = []
  // The actions of listeners are mainly on the nextListeners
  let nextListeners = currentListeners
  // Record whether dispatch is currently in progress
  let isDispatching = false

  // If the nextListeners are equal to the currentListeners, change the nextListeners to point to a copy of the currentListeners and make sure they don't point to the same reference
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  // ------ getState returns 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
  }

  // ------ is a function that collects dependency inputs
  function subscribe(listener: () => void) {
    if (typeoflistener ! = ='function') {
      throw new Error(
        `Expected the listener to be a function. Instead, received: '${kindOf( listener )}'`)}// Do not call dispatch when the reducer is present
    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.')}// Prevents multiple calls to the unsubscribe function
    let isSubscribed = true
    Make sure the nextListeners and currentListeners don't point to the same reference here
    ensureCanMutateNextListeners()
    // Perform push listener on the nextListeners
    nextListeners.push(listener)
    // returns the untying function
    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
      // The same operation ensures that they do not refer to the same reference
      ensureCanMutateNextListeners()
      // Delete operations are also modifications to the nextListeners
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null}}// ------ update state triggers subscription
  function dispatch(action) {
    // Parameter verification can be ignored
    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.')}// Call is not allowed if dispatch is being dispatched to prevent an infinite loop from dispatching dispatchers in the reducer
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')}// Dispatch can continue only after executing a finally operation and then executing another lock operation
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    // Assign the nextListeners to currentListeners and then iterate through the listener array to trigger a subscription
    
    /** 问 题 : Why do we use nextListeners currentListeners? There are only one listeners to publish and subscribe: We use both arrays of nextListeners and currentListeners on the Store, but we also use them on both subscribe and unsubscribe. Listeners = currentListeners = nextlisteners Before you cancel the subscription by ensureCanMutateNextListeners let him two point to different references A: So we're using currentListeners for our listeners, and nextListeners for our arrays, just to make sure we don't run into problems with the following listeners if we unsubscribe a subscription to the same reference, we're missing an item, In React, we can call unsubscribe when A executes to unsubscribe B. const unsbscribeA = store.subscribe(() => {console.log('a')}) srore.subscribe(() => { unsbscribeA() console.log('b') }) Srore.subscribe (() => {console.log('c')}) on the listeners, but on the second notice we called unsbscribeA and the currentListeners were deleted. If we use only one listener array, then the array will be deleted by the second item, and the third listener function will not be executed, raising an exception */
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

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

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


  // Perform a dispatch to initialize the data
  dispatch({ type: ActionTypes.INIT })

  const store = ({
    dispatch: dispatch as Dispatch<A>,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable // Internal methods are ignored
  }
  return store
}

Copy the code

getState

This is the way to get the latest state

// Return the current state
function getState(){
    return currentState
}
Copy the code

replaceReducer

Replace the current Reducer and reinitialize state

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

subscribe

In redux subscribe, dispatch means publish-subscriber mode

Subscribe is used to register listener events, collect dependencies into the listeners array, and return a function to unbind listeners

dispatch

Is the only way to distribute action changes state

After dispatch(Action), Redux calls reducer and passes in action. After state execution is complete, new state is returned. Finally, functions in the Listeners array are iterated and executed successively to trigger subscription

Specific implementation of the logic of the above source code in the comments ~