What is the story
Official description:
Redux is a JavaScript state container that provides predictable state management
Redux workflow
- The user is triggered by the View
action
Is throughdispatch
To distributeaction
the - And then call
reducer
Method, passed to the currentstate
和action
Returns the new state by calculating - when
state
When it changes, it calls the storesubscribe
Listener 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 pair
dispatch
extend
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 ~