Function are
Create a Store object and return it.
The function definitions
The createStore function internally defines many functions, such as getState and dispacth. Ignoring the body of these internal functions, createStore looks like this.
function createStore(reducer,preloadedState,enhancer)}{
let currentReducer = reducer;
let currentState = preloadedState;
let currentListeners=[];
let nextListeners = currentListeners;
let isDispatching = false;
function ensureCanMutateNextListeners() {}
function getState(){}
function subscribe(){}
function dispatch(){}
function replaceReducer(){}
function observable(){}
const store = {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
return store;
}
Copy the code
Using closures allows us to get the current state through store.getState() and change the state through store.dispatch(Action).
Let’s look at the implementation of the various internal functions.
getState
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;
}
Copy the code
The getState function is extremely simple, which returns currentState directly, but checks whether isDispatching is true.
2.dispatch
function dispatch(action) {
if(! isPlainObject(action)) {throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.')}if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant? ')}if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')}try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
Copy the code
The first step is to determine whether the action is a normal object. What kind of object is ordinary? Take a look at the implementation of isPlainObject.
function isPlainObject(obj){
if (typeofobj ! = ='object' || obj === null) return false
let proto = obj
while (Object.getPrototypeOf(proto) ! = =null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto
}
Copy the code
As you can see, this function determines whether OBj is a normal object by determining whether its prototype is equal to the prototype obtained by the while loop. Actions can only be created from Object literals {} or new Object(). But it seems to me that this function could be written like this.
function isPlainObject(obj){
if (typeofobj ! = ='object' || obj === null) return false;
return Object.getPrototypeOf(obj) === Object.prototype;
}
Copy the code
It then checks to see if the Type attribute exists on the action. The reducer is then executed and the result is assigned to currentState.
Listeners registered by store. Subscribe are triggered by the last listeners.
subscribe
function subscribe(listener: () => void) {
if (typeoflistener ! = ='function') {
throw new Error('Expected the listener to be a function.')}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.')}let isSubscribed = true
ensureCanMutateNextListeners()
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()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null}}Copy the code
Pushing listeners into the nextListeners array returns an unsubscribe function. Calling the unsubscribe function removes the listeners from the nextListeners and changes isSubscribed to false to prevent multiple calls.
Now look at enhancer, the third function of createStore.
function createStore= (reducer,preloadedState,enhancer){...if (typeofenhancer ! = ='undefined') {
if (typeofenhancer ! = ='function') {
throw new Error('Expected the enhancer to be a function.')}return enhancer(createStore)(reducer,preloadedState);
}
// Define each internal function, create store object and return. }Copy the code
When enhancer exists and is a function, enhancer is called, followed by the execution result of enhancer. As we can see, enhancer is a higher-order function that receives storeCreator and returns another storeCreator to enhance store functionality. If we want to customize an enhancer, then our enhancer should be a function of the following form
function myEnhancer(storeCreator){
return (reducer,preloadedState) = >{
const store = storeCreator(reducer,preloadedState);
//增强store.returnstore; }}Copy the code
ApplyMiddleware is officially available enhancerCreator, which enhances Store.dispatch.
If you want to use more than one enhancer use the compose function Like this createStore (reducer, defaultState, compose (enhancer1 enhancer2)), can’t createStore (reducer, defaultState enhancer1, enhancer 2), the reason is here.
function createStore= (reducer,preloadedState,enhancer){
if((typeof preloadedState === 'function' && typeof enhancer === 'function') | | (typeof enhancer === 'function' && typeof arguments[3= = ='function')) {throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function.')}... }Copy the code
Welcome to point out the error 😀.
Source code address: github.com/reduxjs/red…