preface
Redux is also one of THE articles I listed in THE LAST TIME series, as I’m exploring a solution for state management in THE business I’m currently developing. So, the idea here is to learn from Redux, from his state. After all, success always requires standing on the shoulders of giants.
Then again, writing about Redux in 2020 is a bit dated. However, Redux must have looked back at Flux, CQRS, ES, etc.
This article first from the Redux design concept to part of the source code analysis. Next we’ll focus on how Redux Middleware works. For handwriting, I recommend: Fully understand Redux (achieving a REdux from zero)
Redux
Redux is not particularly Giao tech, but the concept is really good.
Basically, it’s a big closure that provides a setter and getter. Plus a pubSub… What else reducer, middleware, or actions are based on his rules and addressing user pain points, nothing more. Let’s talk a little bit…
Design idea
Back in the jQuery era, we were process-oriented, but with react’s popularity, we came up with state-driven UI development. We think of a Web application as a one-to-one correspondence between state and UI.
But as our Web applications become more and more complex, the state behind an application becomes more and more difficult to manage.
Redux is a state management solution for our Web application.
As shown in the figure above, a store is a state container provided by Redux. It stores all the states needed by the View layer. Each UI corresponds to a state behind it. Redux does the same. One state corresponds to one View. As long as the state is the same, the View is the same. State drives the UI.
Why use itRedux
As mentioned above, we now have a state-driven UI, so why do we need Redux to manage state? React itself is a state drive view but not.
The reason is that the front end is becoming more and more complex. Often a front-end application has a lot of complex, irregular interactions. It is accompanied by various asynchronous operations.
Any operation could change the state, which would result in our application of state becoming more and more chaotic, and the passive cause becoming more and more obscure. It’s easy to lose control of when, why, and how these states occur.
As mentioned above, if our page is complex enough, the state changes behind the view might look something like this. There is parent-child communication, sibling communication, parent-child communication, and even cross-hierarchy communication between components.
Our ideal state management would look something like this:
Purely on an architectural level, the UI is completely separate from the state, and one-way data flow ensures that the state is manageable.
And that’s what Redux does!
- each
State
Is predictable - Unified management of actions and states
Let’s take a look at some of the concepts in Redux. In fact, beginners are often confused by the concept.
store
Where the data is stored, you can think of it as a container, there’s only one Store for the entire app.
State
The value of the application state stored at a certain time
Action
View is a notification to change state
Action Creator
You can think of it as the Action factory function
dispatch
View emits the Action medium. It’s the only way
reducer
Combine a new State based on the actions and states currently received. Notice that it has to be pure
The three principles
The use of Redux is based on three principles
Single data source
Single data source This is perhaps the biggest difference from Flux. In Redux, the state of the entire application is stored in an object. Of course, this is the only place to store app state. An Object tree is an Object tree. Different branches correspond to different components. But ultimately there is only one root.
Also benefits from a single State tree. “Undo/redo” or even playback that was previously difficult to achieve. It’s a lot easier.
The State read-only
The only way to change state is to dispatch an action. Action is just a token. Normal Object.
Any state changes can be understood as non-view layer changes (network requests, user clicks, etc.). The View layer simply emits an intent. How to satisfy is entirely up to Redux itself, namely reducer.
store.dispatch({
type:'FETCH_START',
params:{
itemId:233333
}
})
Copy the code
Use pure functions to modify
The so-called pure function, you have to be pure, don’t change around. I won’t go into the written words here. The pure function modification we said here is actually the reducer we said above.
A Reducer is a pure function that accepts the current state and action. Then return a new state. So here, the state is not updated, it’s just replaced.
The reason for pure functions is predictability. As long as the incoming state and action are always the same, it can be understood that the new state returned is always the same.
conclusion
There’s more to Redux than that. There are other things like middleware, actionCreator, etc. In fact, they are all derivatives in the process of use. We mainly understand the idea. Then go to the source code to learn how to use.
Source code analysis
The Redux source code itself is pretty simple, so we’ll cover compose, combineReducers, and applyMiddleware in the next article
Redux source code itself is very simple, code is not much. Learning it is mainly to learn his programming ideas and design paradigms.
Of course, we can also take a look at the Redux code to see how the big guys use TS. So in the source code analysis, we also spend a lot of effort to look at the Redux type description. So let’s start with type
src/types
The purpose of looking at type declarations is to learn about Redux’s TS type declarations. So we’re not going to repeat the form of similar statements.
actions.ts
Type declarations don’t have much logic to say either, so I’ll just comment them out
// Interface definition for Action. The type field is explicitly declared
export interface Action<T = any> {
type: T
}
export interface AnyAction extends Action {
// Add additional arbitrary fields to the Action interface (we usually write the AnyAction type, with a "base class" to constrain the type field)
[extraProps: string]: any
}
export interface ActionCreator<A> {
// Function interface, generic constraint function return A
(... args: any[]): A
}
export interface ActionCreatorsMapObject<A = any> {
// Object with the value ActionCreator
[key: string]: ActionCreator<A>
}
Copy the code
reducers.ts
// define A function that takes S and inherits Action's A by default AnyAction and returns S
export type Reducer<S = any, A extends Action = AnyAction> = (
state: S | undefined.
action: A
) => S
// It can be understood that the key of S serves as the key of the ReducersMapObject, and then the value is the Reducer function. In we can think of as traversal
export type ReducersMapObject<S = any, A extends Action = Action> = {
[K in keyof S]: Reducer<S[K], A>
}
Copy the code
The above two statements are relatively straightforward. The next two are a little more difficult
export type StateFromReducersMapObject<M> = M extends ReducersMapObject<
any,
any
>
? { [P in keyof M]: M[P] extends Reducer<infer S, any> ? S : never }
: never
export type ReducerFromReducersMapObject<M> = M extends {
[P in keyof M]: infer R
}
? R extends Reducer<any, any>
? R
: never
: never
Copy the code
Let’s explain the first of the two statements (a little more difficult).
StateFromReducersMapObject
Add another genericM
The constraintM
If inheritanceReducersMapObject<any,any>
Then go{ [P in keyof M]: M[P] extends Reducer<infer S, any> ? S : never }
The logic of the- Otherwise it is
never
. What is not { [P in keyof M]: M[P] extends Reducer<infer S, any> ? S : never }
Obviously, this is an object,key
fromM
Inside the object, which isReducersMapObject
Incoming from insideS
.key
The correspondingvalue
It’s a judgment callM[P]
Whether inherited fromReducer
. Otherwise it’s nothinginfer
Key words andextends
Always cooperate with use. In this case, it means returnReducer
theState
The type of
other
Other types in the types directory, such as Store and Middleware, are declared in this way, but can be read if you are interested. Then take its essence and apply it to their TS projects
src/createStore.ts
As you can see, the entire createstore. ts is a createStore function.
createStore
Three parameters:
reducer
: is reducer, pure Function of newState is calculated according to action and currentStatepreloadedState
: the initial Stateenhancer
: Enhance the store to have third-party functionality
CreateStore is a collection of closure functions.
INIT
// A extends Action
dispatch({ type: ActionTypes.INIT } as A)
Copy the code
This method is reserved for Redux to initialize State, which means dispatch goes to the branch of our default Switch Case Default and gets the default State.
return
const store = ({
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[?observable]: observable
} as unknown) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
Copy the code
Ts returns dispatch, subscribe, getState, replaceReducer, and [? Observable].
Here we briefly introduce the implementation of the first three methods.
getState
function getState() :S {
if (isDispatching) {
throw new Error(
I reducer is executing, newState is producing! Not now.
)
}
return currentState as S
}
Copy the code
And the simple way to do that is return currentState
subscribe
Subscribe adds a listener that is called on each Dispatch action.
Returns a function that removes this listener.
Use as follows:
const unsubscribe = store.subscribe((a)= >
console.log(store.getState())
)
unsubscribe();
Copy the code
function subscribe(listener: () = >void) {
// If listenter is not a function, I will report an error (ts static check can check this, but! That's compile time, this is run time.)
if (typeoflistener ! = ='function') {
throw new Error('Expected the listener to be a function.')
}
// The same sample as getState
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()
// Add the listeners directly to the nextListeners
nextListeners.push(listener)
return function unsubscribe() {// Also use closures to see if the subscription is available, and then remove the subscription
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// Modify the subscription status
ensureCanMutateNextListeners()
// Find the location and remove the listener
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
Copy the code
An explanation is to add a function to the listeners
Say here ensureCanMutateNextListeners again, how many story source that have mentioned this method. It’s also a little confusing to me.
The implementation of this method is very simple. Check whether the current listening array is equal to the next array. If it is! Then I have a copy.
let currentListeners: (() = > void|) []null = []
let nextListeners = currentListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
Copy the code
So why? Leave an egg here. Wait until after Dispatch to check out this puzzle.
dispatch
function dispatch(action: A) {
// Action must be a normal object
if(! isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// Must contain the type field
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant? '
)
}
/ / same as above
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
// Set the dispatch tag to true (explains where those judgments are coming from)
isDispatching = true
// New state from the reducer passed in
// let currentReducer = reducer
currentState = currentReducer(currentState, action)
} finally {
// Change the status
isDispatching = false
}
/ / will nextListener assigned to currentListeners, listeners (note that review ensureCanMutateNextListeners
const listeners = (currentListeners = nextListeners)
// Trigger the listener one by one
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
Copy the code
The method is very simple. It’s all in the comments. Here we are again looking back on it ensureCanMutateNextListeners meaning
ensureCanMutateNextListeners
let currentListeners: (() = > void|) []null = []
let nextListeners = currentListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function subscribe(listener: () = >void) {
// ...
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
function dispatch(action: A) {
/ /...
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
// ...
return action
}
Copy the code
From above, it looks as if all the code needs is an array to store the listener. But the fact is, we are exactly our listeners that can be unSubscribe. Slice also changes the array size.
We add a copy of listeners to avoid missing listeners because of the subscribe or unsubscribe changes made to the listeners.
The last
Limited space, write this for the time being ~
Middleware, which I’m going to focus on later, is a more generic form of Middleware, and it’s not even Redux. By this point, you can write your own state management plan.
And combineReducers is also I think is clever design fee. So these pages, I will move to the next ~
Refer to the link
- redux
- See all 10 lines of code
Redux
implementation Redux
Chinese document
Study and communication
- Pay attention to the public number [full stack front selection], get good articles recommended every day
- Add wechat id: is_Nealyang (note source) to join group communication
Public account [Full stack front End Selection] | Personal wechat 【is_Nealyang】 |
---|---|