CreateStore and combineReducers
Story ideas
Redux is a Javascript state container that provides predictable state management. For an application with a complex point, it is difficult to manage its constantly changing states. If the modification of states is not restricted, the states will affect each other. So we can’t predict what happens when an operation is triggered.
Single data source
The state of the entire application exists in a globally unique Store, which is a tree object that contains all the states in the application. Each component uses a portion of the data from the tree object.
Keep status read-only
Store state cannot be changed directly. To change the state of an application, dispatch must send an Action object.
Data modification is done using pure functions.
The pure function here is reducer(state, action), the first parameter state is the current state, the second parameter action is the object used for dispatch, and the new state value returned by the function is completely determined according to the input parameter value.
createStore
The Redux library doesn’t have many exposed properties, and createStore is one of the most important, which is used to generate our app’s store. Store provides users with the following properties
// Remove the enhancer part of the source createStore
function createStore(reducer, preloadedState) {
var currentReducer = reducer;/ / reducer function
var currentState = preloadedState;/ / the default state
var currentListeners = []; // The current listener function
var nextListeners = currentListeners;
var isDispatching = false; // Whether the application is currently performing distribution operations
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
// Make a shallow copy of the array. Create a new array that has nothing to do with the previous arraynextListeners = currentListeners.slice(); }}function getState() {}
function dispatch(action) {}
function subscribe(listener) {}
// Used to generate the default state for the application
dispatch({ type: ActionTypes.INIT });
}
Copy the code
-
GetState () uses closure technology to get the application state
function getState() { if(isDispatching) {... }State cannot be obtained during reducer execution return currentState; // Get the local variable currentState defined inside the function outside the function using the closure technique } Copy the code
-
Dispatch () receives an action object that must contain the Type attribute as a parameter to dispatch events and calls the Reducer function to return a new state
function dispatch(action) { // Action must be an object if(! isPlainObject(action)) { ... }// The action object must contain the type attribute if (typeof action.type === 'undefined') {... }// No dispatch operation is currently in progress if (isDispatching) { ... } try { isDispatching = true; // Call Reducer to return a new state, where the new state value depends entirely on the input parameters currentState and Action. Therefore, reducer needs to be a pure function so that the state changes can be predictable and traceable. currentState = currentReducer(currentState, action); } finally { isDispatching = false; // The reducer function is finished, and the reducer function status is changed } // There are two currentListeners and nextListeners that assign values to each other. The other is where the createStore function was originally executed. We'll explain why two variables are needed to maintain the process var listeners = currentListeners = nextListeners; // Iterate over the listener functions and execute them in turn to notify the listener of a change in page state for (var i = 0; i < listeners.length; i++) { var listener = listeners[i]; listener(); } return action; } Copy the code
-
The subscribe() function, which receives a callback function as an argument, notifies the component of a status update
function subscribe(listener) { if (typeoflistener ! = ='function') { throw new Error()}// The listener passed in must be a function if (isDispatching) { throw new Error()}// No dispatch operation is currently in progress var isSubscribed = true; // keep a snapshot of currentListeners. The currentListeners and currentListeners are independent of each other and point to different references. ensureCanMutateNextListeners(); nextListeners.push(listener); // Note that the nextListeners are now newer than the currentListeners, which are pre-push listeners return function unsubscribe() { // Returns a function to cancel the current subscription if(! isSubscribed) {return; }if (isDispatching) { throw new Error() } isSubscribed = false; ensureCanMutateNextListeners();// Save a snapshot of the current listeners var index = nextListeners.indexOf(listener); nextListeners.splice(index, 1); // Delete the function currently monitored on the listeners currentListeners = null; }; } Copy the code
-
Unsubscribe () Unsubscribe event
The return value of the subscription function is a function to cancel the current subscription, which can be unsubscribed by calling it directly
Note how many listeners are currently available on the listeners and how many are currently available on the listeners? In subscribe and unsubscribe events are called ensureCanMutateNextListeners function do? ?
// If there is a subscription event inside the subscription event
store.subscribe(function() {
console.log(1)
// The following code is executed only when the current callback function executes
store.subscribe(function() {
console.log(2)})})/ / if we simulate dispatch, don't call in the event of subscription ` ensureCanMutateNextListeners ` function, then print the result of what is it?
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
// both 1 and 2 are printed
/ / call ` ensureCanMutateNextListeners ` function, will only print 1
Copy the code
The main purpose of Redux is to make subscription and unsubscribe events that occur during dispatch execution not immediately present but at the next dispatch, so Redux does two things:
1. Create a copy of currentListeners before each subscription or unsubscribe event, pointing both currentListeners and nextListeners to different references respectively. Subsequent listeners are valid only for nextListeners. CurrentListeners remain the same
2. During each dispatch, currentListeners and nextListeners point to the same reference after the Reducer function is executed.
Everything seemed to be going well, but when I first started the experiment, I used the map method of the array instead of the for loop to iterate over the listener function, which unexpectedly only printed 1, meaning that the current change was not immediately reflected.
listeners.map( listener= > listener() ) // Only 1 is printed
Copy the code
If the array changes during map traversal, will the result of the traversal be affected?
var arr1 = [1.2.3.4.5]
var arr2 = []
var arr3 = arr1.map(item= > {
if(item ===1) {
arr1.push(100)}return item
})
arr3 // [1, 2, 3, 4, 5
var arr4 = arr1.map(item= > {
if(item ===1) {
arr1.push(100)}return item
})
arr4 // [1, 2, 3, 4, 5, 100
Copy the code
Not only map, but also forEach, but I can’t find the reason on the Internet, I wonder if these methods also do the same processing as Redux when encapsulation (the current change is not immediately reflected, the next trigger will be reflected);
combineReducers
The state of the entire application is an object tree, which contains all the states in the application. Each component usually takes part of the data from this big object tree. Moreover, as the application becomes more and more complex, the huge Reducer functions need to be split so that each reducer is only responsible for a part of the data in the Object tree. In this case, we need a main function to combine these sub-states generated by the reducer into a complete object tree with the same structure as the previous one.
So all combineReducers needs to do is return a function that is the reducer function, the first parameter we passed to createStore, and that needs to combine and return the child states generated by the reducer. Say a little round ah ~, directly into the source code
function combineReducers(reducers) {// Note that the reducers here are a series of reducer objects as key values
var reducerKeys = Object.keys(reducers); // Get the key name of the object
var finalReducers = {}; // Shallow copy Final reducer that meets all conditions
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (typeof reducers[key] === 'function') { //reducer must be a function,
finalReducers[key] = reducers[key]; // Function shallow copy}}var finalReducerKeys = Object.keys(finalReducers); // Obtain the final reducer key name
return function combination(state, action) { // Accept a state and an action?? This is the Reducer function
Void 0 = undefined; void 0 = undefined; void 0 = undefined; Void 0 is less than undefined9;
if (state === void 0) {
state = {};
}
var hasChanged = false; // Check whether the state has changed
var nextState = {};
//
for (var _i = 0; _i < finalReducerKeys.length; _i++) {
var _key = finalReducerKeys[_i]; / / for kek
var reducer = finalReducers[_key]; Obtain the reducer according to the key
var previousStateForKey = state[_key]; // Obtain the corresponding child state according to the key
var nextStateForKey = reducer(previousStateForKey, action);Call the reducer function to return the new state
nextState[_key] = nextStateForKey; // Combine State according to reducerKey
// shallow comparison to determine whether the current child state hasChanged, hasChanged is true only once during the traversalhasChanged = hasChanged || nextStateForKey ! == previousStateForKey; }// If the state is null and the value returned by reducer is undefined, hasChanged cannot be determined. Because nextStateForKey and previousStateForKey are both undefined. Therefore, the following line of code is for those attributes that are not defined in the original state, but still return undefined after reducer calculationhasChanged = hasChanged || finalReducerKeys.length ! = =Object.keys(state).length;
return hasChanged ? nextState : state;
};
}
Copy the code