Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to understand the principle of Redux

2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly introduces "how to understand Redux principle". In daily operation, I believe many people have doubts about how to understand Redux principle. The editor consulted all kinds of materials and sorted out simple and easy-to-use operation methods. I hope it will be helpful for you to answer the doubts about "how to understand Redux principle". Next, please follow the editor to study!

Basic concept

The concept of Redux has been talked about in many articles, and you must have read it a lot. I will no longer expand it here, but just mention it briefly. The basic concepts of Redux are as follows:

Store

As the name suggests, Store is a warehouse that stores all the states (State) and provides some API to operate him. Our subsequent operations are actually operating this warehouse. If our warehouse is used to store milk, and initially, we don't have a carton of milk in our warehouse, then the State of Store is:

{milk: 0}

Actions

An Action is an action. The purpose of this action is to change a certain state in the Store. Store is still the warehouse above. Now I want to put a carton of milk in the warehouse. The "I want to put a carton of milk in the warehouse" is an Action. The code is like this:

{type: "PUT_MILK", count: 1}

Reducers

The previous "I want to put a carton of milk into the warehouse" just thought about it, but it hasn't been done yet. The specific operation depends on Reducer,Reducer to change the status in the Store according to the received Action. For example, if I receive a PUT_MILK and the number of count is 1, then the result is that milk increases by 1, from 0 to 1. The code is like this:

Const initState = {milk: 0} function reducer (state = initState, action) {switch (action.type) {case 'PUT_MILK': return {... state, milk: state.milk + action.count} default: return state}}

You can see that Redux itself is a simple state machine, Store stores all the states, Action is a notification to change the state, and Reducer receives the notification to change the corresponding state in the Store.

Simple example

Let's take a look at a simple example that includes the concepts of Store,Action and Reducer mentioned earlier:

Import {createStore} from 'redux'; const initState = {milk: 0}; function reducer (state = initState, action) {switch (action.type) {case' PUT_MILK': return {... state, milk: state.milk + action.count}; case 'TAKE_MILK': return {... state, milk: state.milk-action.count}; default: return state }} let store = createStore (reducer); / / subscribe is actually the change of subscription store. Once the store has changed, the incoming callback function will be called / / if it is combined with the page update, the update operation is to execute store.subscribe (() = > console.log (store.getState () here; / / send the action using dispatch store.dispatch ({type: 'PUT_MILK'}) / / milk: 1 store.dispatch ({type: 'PUT_MILK'}); / / milk: 2 store.dispatch ({type:' TAKE_MILK'}); / / milk: 1

Realize it by yourself

Our previous example is short, but it already contains the core functions of Redux, so our first handwritten goal is to replace Redux in this example. To replace this Redux, we need to know what's in it. If we take a closer look, we seem to be using only one of his API:

CreateStore: this API takes the reducer method as a parameter and returns a store. The main function is on this store.

Look at what we use on store:

Store.subscribe: subscribe to changes in state. Callbacks are executed when state changes. There can be multiple subscribe, in which callbacks are executed in turn.

Store.dispatch: issue the method of action, and each time dispatch action executes reducer to generate a new state, and then performs a callback for subscribe registration.

Store.getState: a simple method that returns the current state.

When you see the subscribe registration callback, dispatch triggers the callback. What do you think of? isn't that the publish / subscribe model? I have an article about the publish and subscribe model in detail, here is a direct imitation.

Function createStore () {let state; / / state records all the states let listeners = []; / / saves all registered callbacks function subscribe (callback) {listeners.push (callback); / / subscribe is to save the callbacks} / / dispatch is to take out all the callbacks and execute them in turn function dispatch () {for (let I = 0; I)

< listeners.length; i++) { const listener = listeners[i]; listener(); } } // getState直接返回state function getState() { return state; } // store包装一下前面的方法直接返回 const store = { subscribe, dispatch, getState } return store; } 上述代码是不是很简单嘛,Redux核心也是一个发布订阅模式,就是这么简单!等等,好像漏了啥,reducer呢?reducer的作用是在发布事件的时候改变state,所以我们的dispatch在执行回调前应该先执行reducer,用reducer的返回值重新给state赋值,dispatch改写如下: function dispatch(action) { state = reducer(state, action); for (let i = 0; i < listeners.length; i++) { const listener = listeners[i]; listener(); } } 到这里,前面例子用到的所有API我们都自己实现了,我们用自己的Redux来替换下官方的Redux试试: // import { createStore } from 'redux'; import { createStore } from './myRedux'; 可以看到输出结果是一样的,说明我们自己写的Redux没有问题: 了解了Redux的核心原理,我们再去看他的源码应该就没有问题了,createStore的源码传送门。 最后我们再来梳理下Redux的核心流程,注意单纯的Redux只是个状态机,是没有View层的哦。 除了这个核心逻辑外,Redux里面还有些API也很有意思,我们也来手写下。 手写combineReducers combineReducers也是使用非常广泛的API,当我们应用越来越复杂,如果将所有逻辑都写在一个reducer里面,最终这个文件可能会有成千上万行,所以Redux提供了combineReducers,可以让我们为不同的模块写自己的reducer,最终将他们组合起来。比如我们最开始那个牛奶仓库,由于我们的业务发展很好,我们又增加了一个放大米的仓库,我们可以为这两个仓库创建自己的reducer,然后将他们组合起来,使用方法如下: import { createStore, combineReducers } from 'redux'; const initMilkState = { milk: 0 }; function milkReducer(state = initMilkState, action) { switch (action.type) { case 'PUT_MILK': return {...state, milk: state.milk + action.count}; case 'TAKE_MILK': return {...state, milk: state.milk - action.count}; default: return state; } } const initRiceState = { rice: 0 }; function riceReducer(state = initRiceState, action) { switch (action.type) { case 'PUT_RICE': return {...state, rice: state.rice + action.count}; case 'TAKE_RICE': return {...state, rice: state.rice - action.count}; default: return state; } } // 使用combineReducers组合两个reducer const reducer = combineReducers({milkState: milkReducer, riceState: riceReducer}); let store = createStore(reducer); store.subscribe(() =>

Console.log (store.getState ()); / / Operation? Action store.dispatch ({type: 'PUT_MILK', count: 1}); / / milk: 1 store.dispatch ({type:' PUT_MILK', count: 1}); / / milk: 2 store.dispatch ({type: 'TAKE_MILK', count: 1}); / / milk: 1 / / action store.dispatch for operating rice ({type:' PUT_RICE', count: 1}) / / rice: 1 store.dispatch ({type: 'PUT_RICE', count: 1}); / / rice: 2 store.dispatch ({type:' TAKE_RICE', count: 1}); / / rice: 1

In the above code, we divide the large state into two small milkState and riceState, and the final result is as follows:

Knowing the usage, we try to write it down ourselves. To handwrite combineReducers, let's first analyze what it did. First, its return value is a reducer, and the reducer will also be passed as an argument to createStore, indicating that the return value is the same function as our previous normal reducer structure. This function also receives state and action and returns the new state, except that the new state matches the data structure of the combineReducers parameter. Let's try to write down:

Function combineReducers (reducerMap) {const reducerKeys = Object.keys (reducerMap); / / take out all the key values in the parameter first / / return the reducer function with a normal structure const reducer = (state = {}, action) = > {const newState = {}; for (let I = 0; I)

< reducerKeys.length; i++) { // reducerMap里面每个键的值都是一个reducer,我们把它拿出来运行下就可以得到对应键新的state值 // 然后将所有reducer返回的state按照参数里面的key组装好 // 最后再返回组装好的newState就行 const key = reducerKeys[i]; const currentReducer = reducerMap[key]; const prevState = state[key]; newState[key] = currentReducer(prevState, action); } return newState; }; return reducer; } 官方源码的实现原理跟我们的一样,只是他有更多的错误处理,大家可以对照着看下。 手写applyMiddleware middleware是Redux里面很重要的一个概念,Redux的生态主要靠这个API接入,比如我们想写一个logger的中间件可以这样写(这个中间件来自于官方文档): // logger是一个中间件,注意返回值嵌了好几层函数 // 我们后面来看看为什么这么设计 function logger(store) { return function(next) { return function(action) { console.group(action.type); console.info('dispatching', action); let result = next(action); console.log('next state', store.getState()); console.groupEnd(); return result } } } // 在createStore的时候将applyMiddleware作为第二个参数传进去 const store = createStore( reducer, applyMiddleware(logger) ) 可以看到上述代码为了支持中间件,createStore支持了第二个参数,这个参数官方称为enhancer,顾名思义他是一个增强器,用来增强store的能力的。官方对于enhancer的定义如下: type StoreEnhancer = (next: StoreCreator) =>

StoreCreator

The above structure means that enhancer, as a function, takes the StoreCreator function as an argument and must return a StoreCreator function. Notice that his return value is also a StoreCreator function, that is, if we take out his return value and continue to execute, we should get the same return structure as the previous createStore, that is, what structure our previous createStore returns, he must also return the structure, that is, the store:

{subscribe, dispatch, getState}

CreateStore supports enhancer

According to his definition of enhancer, let's rewrite our createStore so that he supports enhancer:

Function createStore (reducer, enhancer) {/ / receives the second parameter enhancer / / first handles enhancer / / if enhancer exists and is a function / / We pass him createStore as an argument / / he should return a new createStore to me / / I will take this new createStore for execution You should get a store / / and just return this store if (enhancer & & typeof enhancer = 'function') {const newCreateStore = enhancer (createStore) Const newStore = newCreateStore (reducer); return newStore;} / / if there is no enhancer or enhancer is not a function, execute the previous logic / / the following code is the previous version / omitted n lines of code / /. Const store = {subscribe, dispatch, getState} return store;}

This part of the corresponding source code see here.

The return value of applyMiddleware is an enhancer

We already have the basic structure of enhancer. ApplyMiddleware is passed to createStore as the second parameter, that is to say, it is an enhancer. To be exact, the return value of applyMiddleware is an enhancer, because what we pass to createStore is his execution result applyMiddleware ():

The return value of function applyMiddleware (middleware) {/ / applyMiddleware should be an enhancer / / according to what we said earlier, the parameter of enhancer is createStore function enhancer (createStore) {/ / enhancer should return a new createStore function newCreateStore (reducer) {/ / We first write an empty newCreateStore, and directly return the result of createStore const store = createStore (reducer); return store} return newCreateStore } return enhancer;}

Implement applyMiddleware

We already have the basic structure of applyMiddleware above, but the function has not been implemented yet. To achieve its function, we must first figure out what a middleware does, or take the previous logger middleware as an example:

Function logger (store) {return function (next) {return function (action) {console.group (action.type); console.info ('dispatching', action); let result = next (action); console.log (' next state', store.getState ()); console.groupEnd (); return result}

The effect of this middleware is as follows:

You can see that we let result = next (action); the state has changed after the execution of this line, and as we said earlier, state can only be changed (action), so the next (action) here is dispatch (action), just with a different name. And notice the structure of the last layer's return value return function (action), whose parameter is action, which is very similar to dispatch (action). In fact, it is a new dispatch (action), which calls the original dispatch and adds its own logic before and after the call. So here the structure of a middleware is also clear:

A middleware takes store as an argument and returns a function

The returned function takes the old dispatch function as an argument and returns a new function

The new function returned is the new dispatch function, in which you can get the store and old dispatch functions passed in from the outer two layers.

So to put it bluntly, middleware is to enhance the function of dispatch and replace the old dispatch with the new dispatch. Isn't that a decorator pattern? In fact, the previous enhancer is also a decorator mode, passing in a createStore, adding some code before and after the createStore execution, and finally returning an enhanced version of createStore. It can be seen that design patterns are really widespread in these excellent frameworks, and if you are not familiar with the decorator pattern, you can read my previous article.

Following this line of thinking, our applyMiddleware can be written:

/ / directly fetch the previous structure function applyMiddleware (middleware) {function enhancer (createStore) {function newCreateStore (reducer) {const store = createStore (reducer); / / bring middleware to execute, pass store / / to get the first layer function const func = middleware (store); / / deconstruct the original dispatch const {dispatch} = store / / pass the original dispatch function to func to execute / / get the enhanced version of dispatch const newDispatch = func (dispatch); / / replace the original dispatch return {... store, dispatch: newDispatch}} return newCreateStore;} return enhancer;} with the enhanced version of dispatch return when returned.

As usual, replace the old one with our own applyMiddleware, and the running effect is the same, which means we have no problem with writing, ~

Support for multiple middleware

One more thing missing from our applyMiddleware is that it supports multiple middleware, such as this:

ApplyMiddleware (rafScheduler, timeoutScheduler, thunk, vanillaPromise, readyStatePromise, logger, crashReporter)

In fact, it is also simple to support this. In the newDispatch we return, you can take out the incoming middleware in turn and execute it. For serial execution of multiple functions, you can use the auxiliary function compose, which is defined as follows. It's just important to note that our compose here can't just take the method and execute it. It should return a method that wraps all the methods.

Function compose (... func) {return funcs.reduce ((a, b) = > (... args) = > a (b (.args);}

This compose may be confusing, so let me explain it here. For example, we have three functions, all of which are the methods we used to return the new dispatch from dispatch:

Const fun1 = dispatch = > newDispatch2; const fun2 = dispatch = > newDispatch3; const fun3 = dispatch = > newDispatch4

What is the execution order when we use compose (fun1, fun2, fun3)?

/ / the first time we actually execute (func1, func2) = > (... args) = > func1 (fun2 (.args)) / / the return value of this execution is the following. Save it with a variable, const temp = (... args) = > func1 (fun2 (.. ARGs)) / / We actually execute (temp, func3) = > (. Args) = > temp (func3 (.args)) the next time we recycle. / / this return value is the following, that is, the final return value, that is, all functions have been executed from right to left from func3. / / the return value in front of it will be used as a later parameter (. Args) = > temp (func3 (.. ARGs)); / / take a look at the above method. What will happen if you pass dispatch as an argument (dispatch) = > temp (dispatch)) / / then func3 (dispatch) returns newDispatch4, and this is passed to temp (newDispatch4), that is, the following one will execute (newDispatch4) = > func1 (fun2 (newDispatch4)) / / the above one will execute fun2 (newDispatch4) with newDispatch4 to get newDispatch3 / / and then func1 (newDispatch3) will get newDispatch2 / / Note that newDispatch2 at this time actually contains the logic of newDispatch4 and newDispatch3, take it out and execute all three methods

More details on how compose works can be found in my previous article.

So our code that supports multiple middleware is like this:

/ / parameters support multiple middleware function applyMiddleware (... middlewares) {function enhancer (createStore) {function newCreateStore (reducer) {const store = createStore (reducer); / / multiple middleware, first deconstruct the structure const chain = middlewares.map (middleware = > middleware (store)) of dispatch = > newDispatch; const {dispatch} = store / / use compose to get a function const newDispatchGen = compose (.. chain) that combines all newDispatch; / / execute this function to get newDispatch const newDispatch = newDispatchGen (dispatch); return {... store, dispatch: newDispatch}} return newCreateStore;} return enhancer;}

Finally, we add a logger2 middleware to achieve the effect:

Function logger2 (store) {return function (next) {return function (action) {let result = next (action); console.log ('logger2'); return result} let store = createStore (reducer, applyMiddleware (logger, logger2))

You can see that the logger2 has also been printed out, and it is done.

Now we can also know why his middleware wraps several layers of functions:

Layer 1: the purpose is to pass in store parameters

The second layer: the structure of the second layer is dispatch = > newDispatch. This layer function of multiple middleware can be compose to form a large dispatch = > newDispatch.

The third layer: this layer is the final return value, which is actually newDispatch, the enhanced dispatch, and the true logic of middleware.

At this point, the study of "how to understand the principle of Redux" is over. I hope to be able to solve your doubts. The collocation of theory and practice can better help you learn, go and try it! If you want to continue to learn more related knowledge, please continue to follow the website, the editor will continue to work hard to bring you more practical articles!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 278

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report