自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

手寫一Redux,深入理解其原理

開發(fā) 前端
Redux可是一個大名鼎鼎的庫,很多地方都在用,我也用了幾年了,今天這篇文章就是自己來實現(xiàn)一個Redux,以便于深入理解他的原理。

手寫一Redux,深入理解其原理

Redux可是一個大名鼎鼎的庫,很多地方都在用,我也用了幾年了,今天這篇文章就是自己來實現(xiàn)一個Redux,以便于深入理解他的原理。我們還是老套路,從基本的用法入手,然后自己實現(xiàn)一個Redux來替代源碼的NPM包,但是功能保持不變。本文只會實現(xiàn)Redux的核心庫,跟其他庫的配合使用,比如React-Redux準備后面單獨寫一篇文章來講。有時候我們過于關注使用,只記住了各種使用方式,反而忽略了他們的核心原理,但是如果我們想真正的提高技術,最好還是一個一個搞清楚,比如Redux和React-Redux看起來很像,但是他們的核心理念和關注點是不同的,Redux其實只是一個單純狀態(tài)管理庫,沒有任何界面相關的東西,React-Redux關注的是怎么將Redux跟React結合起來,用到了一些React的API。

本文全部代碼已經(jīng)上傳到GitHub,大家可以拿下來玩下:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/redux

基本概念

Redux的概念有很多文章都講過,想必大家都看過很多了,我這里不再展開,只是簡單提一下。Redux基本概念主要有以下幾個:

Store

人如其名,Store就是一個倉庫,它存儲了所有的狀態(tài)(State),還提供了一些操作他的API,我們后續(xù)的操作其實都是在操作這個倉庫。假如我們的倉庫是用來放牛奶的,初始情況下,我們的倉庫里面一箱牛奶都沒有,那Store的狀態(tài)(State)就是: 

  1.  
  2.     milk: 0  

Actions

一個Action就是一個動作,這個動作的目的是更改Store中的某個狀態(tài),Store還是上面的那個倉庫,現(xiàn)在我想往倉庫放一箱牛奶,那"我想往倉庫放一箱牛奶"就是一個Action,代碼就是這樣: 

  1.  
  2.   type: "PUT_MILK",  
  3.   count: 1  

Reducers

前面"我想往倉庫放一箱牛奶"只是想了,還沒操作,具體操作要靠Reducer,Reducer就是根據(jù)接收的Action來改變Store中的狀態(tài),比如我接收了一個PUT_MILK,同時數(shù)量count是1,那放進去的結果就是milk增加了1,從0變成了1,代碼就是這樣: 

  1. const initState = {  
  2.   milk: 0  
  3.  
  4. function reducer(state = initState, action) {  
  5.   switch (action.type) {  
  6.     case 'PUT_MILK':  
  7.       return {...state, milk: state.milk + action.count}  
  8.     default:  
  9.       return state  
  10.   }  

可以看到Redux本身就是一個單純的狀態(tài)機,Store存放了所有的狀態(tài),Action是一個改變狀態(tài)的通知,Reducer接收到通知就更改Store中對應的狀態(tài)。

簡單例子

下面我們來看一個簡單的例子,包含了前面提到的Store,Action和Reducer這幾個概念: 

  1. import { createStore } from 'redux';  
  2. const initState = {  
  3.   milk: 0  
  4. };  
  5. function reducer(state = initState, action) {  
  6.   switch (action.type) {  
  7.     case 'PUT_MILK': 
  8.        return {...state, milk: state.milk + action.count};  
  9.     case 'TAKE_MILK':  
  10.       return {...state, milk: state.milk - action.count};  
  11.     default:  
  12.       return state;  
  13.   }  
  14.  
  15. let store = createStore(reducer);  
  16. // subscribe其實就是訂閱store的變化,一旦store發(fā)生了變化,傳入的回調(diào)函數(shù)就會被調(diào)用  
  17. // 如果是結合頁面更新,更新的操作就是在這里執(zhí)行  
  18. store.subscribe(() => console.log(store.getState()));  
  19. // 將action發(fā)出去要用dispatch  
  20. store.dispatch({ type: 'PUT_MILK' });    // milk: 1  
  21. store.dispatch({ type: 'PUT_MILK' });    // milk: 2  
  22. store.dispatch({ type: 'TAKE_MILK' });   // milk: 1 

自己實現(xiàn)

前面我們那個例子雖然短小,但是已經(jīng)包含了Redux的核心功能了,所以我們手寫的第一個目標就是替換這個例子中的Redux。要替換這個Redux,我們得先知道他里面都有什么東西,仔細一看,我們好像只用到了他的一個API:

createStore:這個API接受reducer方法作為參數(shù),返回一個store,主要功能都在這個store上。

看看store上我們都用到了啥:

store.subscribe: 訂閱state的變化,當state變化的時候執(zhí)行回調(diào),可以有多個subscribe,里面的回調(diào)會依次執(zhí)行。

store.dispatch: 發(fā)出action的方法,每次dispatch action都會執(zhí)行reducer生成新的state,然后執(zhí)行subscribe注冊的回調(diào)。

store.getState:一個簡單的方法,返回當前的state。

看到subscribe注冊回調(diào),dispatch觸發(fā)回調(diào),想到了什么,這不就是發(fā)布訂閱模式嗎?我之前有一篇文章詳細講過發(fā)布訂閱模式了,這里直接仿寫一個。 

  1. function createStore() {  
  2.   let state;              // state記錄所有狀態(tài)  
  3.   let listeners = [];     // 保存所有注冊的回調(diào)  
  4.   function subscribe(callback) {  
  5.     listeners.push(callback);       // subscribe就是將回調(diào)保存下來  
  6.   }  
  7.   // dispatch就是將所有的回調(diào)拿出來依次執(zhí)行就行  
  8.   function dispatch() {  
  9.     for (let i = 0; i < listeners.length; i++) {  
  10.       const listener = listeners[i];  
  11.       listener();  
  12.     }  
  13.   }  
  14.   // getState直接返回state  
  15.   function getState() {  
  16.     return state;  
  17.   }  
  18.   // store包裝一下前面的方法直接返回  
  19.   const store = {  
  20.     subscribe,  
  21.     dispatch,  
  22.     getState  
  23.   }  
  24.   return store;  

上述代碼是不是很簡單嘛,Redux核心也是一個發(fā)布訂閱模式,就是這么簡單!等等,好像漏了啥,reducer呢?reducer的作用是在發(fā)布事件的時候改變state,所以我們的dispatch在執(zhí)行回調(diào)前應該先執(zhí)行reducer,用reducer的返回值重新給state賦值,dispatch改寫如下: 

  1. function dispatch(action) {  
  2.   state = reducer(state, action);  
  3.   for (let i = 0; i < listeners.length; i++) {  
  4.     const listener = listeners[i];  
  5.     listener();  
  6.   }  

到這里,前面例子用到的所有API我們都自己實現(xiàn)了,我們用自己的Redux來替換下官方的Redux試試: 

  1. // import { createStore } from 'redux';  
  2. import { createStore } from './myRedux'; 

可以看到輸出結果是一樣的,說明我們自己寫的Redux沒有問題:

了解了Redux的核心原理,我們再去看他的源碼應該就沒有問題了,createStore的源碼傳送門。

最后我們再來梳理下Redux的核心流程,注意單純的Redux只是個狀態(tài)機,是沒有View層的哦。

除了這個核心邏輯外,Redux里面還有些API也很有意思,我們也來手寫下。

手寫combineReducers

combineReducers也是使用非常廣泛的API,當我們應用越來越復雜,如果將所有邏輯都寫在一個reducer里面,最終這個文件可能會有成千上萬行,所以Redux提供了combineReducers,可以讓我們?yōu)椴煌哪K寫自己的reducer,最終將他們組合起來。比如我們最開始那個牛奶倉庫,由于我們的業(yè)務發(fā)展很好,我們又增加了一個放大米的倉庫,我們可以為這兩個倉庫創(chuàng)建自己的reducer,然后將他們組合起來,使用方法如下: 

  1. import { createStore, combineReducers } from 'redux';  
  2. const initMilkState = {  
  3.   milk: 0  
  4. };  
  5. function milkReducer(state = initMilkState, action) {  
  6.   switch (action.type) {  
  7.     case 'PUT_MILK':  
  8.       return {...state, milk: state.milk + action.count};  
  9.     case 'TAKE_MILK':  
  10.       return {...state, milk: state.milk - action.count};  
  11.     default:  
  12.       return state;  
  13.   }  
  14.  
  15. const initRiceState = {  
  16.   rice: 0  
  17. };  
  18. function riceReducer(state = initRiceState, action) {  
  19.   switch (action.type) {  
  20.     case 'PUT_RICE':  
  21.       return {...state, rice: state.rice + action.count};  
  22.     case 'TAKE_RICE':  
  23.       return {...state, rice: state.rice - action.count};  
  24.     default:  
  25.       return state;  
  26.   }  
  27.  
  28. // 使用combineReducers組合兩個reducer  
  29. const reducer = combineReducers({milkState: milkReducer, riceState: riceReducer});  
  30. let store = createStore(reducer);  
  31. store.subscribe(() => console.log(store.getState()));  
  32. // 操作🥛的action  
  33. store.dispatch({ type: 'PUT_MILK', count: 1 });    // milk: 1  
  34. store.dispatch({ type: 'PUT_MILK', count: 1 });    // milk: 2  
  35. store.dispatch({ type: 'TAKE_MILK', count: 1 });   // milk: 1  
  36. // 操作大米的action  
  37. store.dispatch({ type: 'PUT_RICE', count: 1 });    // rice: 1  
  38. store.dispatch({ type: 'PUT_RICE', count: 1 });    // rice: 2  
  39. store.dispatch({ type: 'TAKE_RICE', count: 1 });   // rice: 1 

上面代碼我們將大的state分成了兩個小的milkState和riceState,最終運行結果如下:

知道了用法,我們嘗試自己來寫下呢!要手寫combineReducers,我們先來分析下他干了啥,首先它的返回值是一個reducer,這個reducer同樣會作為createStore的參數(shù)傳進去,說明這個返回值是一個跟我們之前普通reducer結構一樣的函數(shù)。這個函數(shù)同樣接收state和action然后返回新的state,只是這個新的state要符合combineReducers參數(shù)的數(shù)據(jù)結構。我們嘗試來寫下: 

  1. function combineReducers(reducerMap) {  
  2.   const reducerKeys = Object.keys(reducerMap);    // 先把參數(shù)里面所有的鍵值拿出來  
  3.   // 返回值是一個普通結構的reducer函數(shù)  
  4.   const reducer = (state = {}, action) => {  
  5.     const newState = {};    
  6.     for(let i = 0; i < reducerKeys.length; i++) {  
  7.       // reducerMap里面每個鍵的值都是一個reducer,我們把它拿出來運行下就可以得到對應鍵新的state值  
  8.       // 然后將所有reducer返回的state按照參數(shù)里面的key組裝好  
  9.       // 最后再返回組裝好的newState就行  
  10.       const key = reducerKeys[i];  
  11.       const currentReducer = reducerMap[key];  
  12.       const prevState = state[key]; 
  13.        newState[key] = currentReducer(prevState, action);  
  14.     }  
  15.      return newState;  
  16.   };   
  17.   return reducer;  

官方源碼的實現(xiàn)原理跟我們的一樣,只是他有更多的錯誤處理,大家可以對照著看下。

手寫applyMiddleware

middleware是Redux里面很重要的一個概念,Redux的生態(tài)主要靠這個API接入,比如我們想寫一個logger的中間件可以這樣寫(這個中間件來自于官方文檔): 

  1. // logger是一個中間件,注意返回值嵌了好幾層函數(shù)  
  2. // 我們后面來看看為什么這么設計  
  3. function logger(store) {  
  4.   return function(next) {  
  5.     return function(action) {  
  6.       console.group(action.type);  
  7.       console.info('dispatching', action);  
  8.       let result = next(action);  
  9.       console.log('next state', store.getState());  
  10.       console.groupEnd();  
  11.       return result  
  12.     }  
  13.   }  
  14. // 在createStore的時候?qū)pplyMiddleware作為第二個參數(shù)傳進去  
  15. const store = createStore 
  16.   reducer,  
  17.   applyMiddleware(logger)  

可以看到上述代碼為了支持中間件,createStore支持了第二個參數(shù),這個參數(shù)官方稱為enhancer,顧名思義他是一個增強器,用來增強store的能力的。官方對于enhancer的定義如下: 

  1. type StoreEnhancer = (next: StoreCreator) => StoreCreator 

上面的結構的意思是說enhancer作為一個函數(shù),他接收StoreCreator函數(shù)作為參數(shù),同時返回的也必須是一個StoreCreator函數(shù)。注意他的返回值也是一個StoreCreator函數(shù),也就是我們把他的返回值拿出來繼續(xù)執(zhí)行應該得到跟之前的createStore一樣的返回結構,也就是說我們之前的createStore返回啥結構,他也必須返回結構,也就是這個store: 

  1.  
  2.   subscribe,  
  3.   dispatch,  
  4.   getState  

createStore支持enhancer

根據(jù)他關于enhancer的定義,我們來改寫下自己的createStore,讓他支持enhancer: 

  1. function createStore(reducer, enhancer) {   // 接收第二個參數(shù)enhancer  
  2.   // 先處理enhancer  
  3.   // 如果enhancer存在并且是函數(shù)  
  4.   // 我們將createStore作為參數(shù)傳給他  
  5.   // 他應該返回一個新的createStore給我  
  6.   // 我再拿這個新的createStore執(zhí)行,應該得到一個store  
  7.   // 直接返回這個store就行  
  8.   if(enhancer && typeof enhancer === 'function'){  
  9.     const newCreateStore = enhancer(createStore);  
  10.     const newStore = newCreateStore(reducer);  
  11.     return newStore;  
  12.   }  
  13.   // 如果沒有enhancer或者enhancer不是函數(shù),直接執(zhí)行之前的邏輯  
  14.   // 下面這些代碼都是之前那版  
  15.   // 省略n行代碼  
  16.     // .......  
  17.   const store = {  
  18.     subscribe,  
  19.     dispatch,  
  20.     getState  
  21.   }  
  22.   return store;  

這部分對應的源碼看這里。

applyMiddleware返回值是一個enhancer

前面我們已經(jīng)有了enhancer的基本結構,applyMiddleware是作為第二個參數(shù)傳給createStore的,也就是說他是一個enhancer,準確的說是applyMiddleware的返回值是一個enhancer,因為我們傳給createStore的是他的執(zhí)行結果applyMiddleware(): 

  1. function applyMiddleware(middleware) {  
  2.   // applyMiddleware的返回值應該是一個enhancer  
  3.   // 按照我們前面說的enhancer的參數(shù)是createStore  
  4.   function enhancer(createStore) { 
  5.      // enhancer應該返回一個新的createStore  
  6.     function newCreateStore(reducer) {  
  7.       // 我們先寫個空的newCreateStore,直接返回createStore的結果  
  8.       const store = createStore(reducer);  
  9.       return store  
  10.     }    
  11.     return newCreateStore;  
  12.   } 
  13.    return enhancer;  

實現(xiàn)applyMiddleware

上面我們已經(jīng)有了applyMiddleware的基本結構了,但是功能還沒實現(xiàn),要實現(xiàn)他的功能,我們必須先搞清楚一個中間件到底有什么功能,還是以前面的logger中間件為例: 

  1. function logger(store) {  
  2.   return function(next) {  
  3.     return function(action) {  
  4.       console.group(action.type);  
  5.       console.info('dispatching', action);  
  6.       let result = next(action);  
  7.       console.log('next state', store.getState());  
  8.       console.groupEnd();  
  9.       return result  
  10.     }  
  11.   }  

這個中間件運行效果如下:

可以看到我們let result = next(action);這行執(zhí)行之后state改變了,前面我們說了要改變state只能dispatch(action),所以這里的next(action)就是dispatch(action),只是換了一個名字而已。而且注意最后一層返回值return function(action)的結構,他的參數(shù)是action,是不是很像dispatch(action),其實他就是一個新的dispatch(action),這個新的dispatch(action)會調(diào)用原始的dispatch,并且在調(diào)用的前后加上自己的邏輯。所以到這里一個中間件的結構也清楚了:

  1.   一個中間件接收store作為參數(shù),會返回一個函數(shù)
  2.   返回的這個函數(shù)接收老的dispatch函數(shù)作為參數(shù),會返回一個新的函數(shù)
  3.   返回的新函數(shù)就是新的dispatch函數(shù),這個函數(shù)里面可以拿到外面兩層傳進來的store和老dispatch函數(shù)

所以說白了,中間件就是加強dispatch的功能,用新的dispatch替換老的dispatch,這不就是個裝飾者模式嗎?其實前面enhancer也是一個裝飾者模式,傳入一個createStore,在createStore執(zhí)行前后加上些代碼,最后又返回一個增強版的createStore。可見設計模式在這些優(yōu)秀的框架中還真是廣泛存在,如果你對裝飾者模式還不太熟悉,可以看我之前這篇文章。

遵循這個思路,我們的applyMiddleware就可以寫出來了: 

  1. // 直接把前面的結構拿過來  
  2. function applyMiddleware(middleware) {  
  3.   function enhancer(createStore) {  
  4.     function newCreateStore(reducer) {  
  5.       const store = createStore(reducer);   
  6.       // 將middleware拿過來執(zhí)行下,傳入store  
  7.       // 得到第一層函數(shù) 
  8.        const func = middleware(store);     
  9.        // 解構出原始的dispatch  
  10.       const { dispatch } = store;      
  11.        // 將原始的dispatch函數(shù)傳給func執(zhí)行  
  12.       // 得到增強版的dispatch  
  13.       const newDispatch = func(dispatch);      
  14.        // 返回的時候用增強版的newDispatch替換原始的dispatch  
  15.       return {...store, dispatch: newDispatch}  
  16.     }    
  17.      return newCreateStore;  
  18.   } 
  19.    return enhancer;  

照例用我們自己的applyMiddleware替換老的,跑起來是一樣的效果,說明我們寫的沒問題,哈哈~

支持多個middleware

我們的applyMiddleware還差一個功能,就是支持多個middleware,比如像這樣: 

  1. applyMiddleware(  
  2.   rafScheduler,  
  3.   timeoutScheduler,  
  4.   thunk,  
  5.   vanillaPromise,  
  6.   readyStatePromise,  
  7.   logger,  
  8.   crashReporter  

其實要支持這個也簡單,我們返回的newDispatch里面依次的將傳入的middleware拿出來執(zhí)行就行,多個函數(shù)的串行執(zhí)行可以使用輔助函數(shù)compose,這個函數(shù)定義如下。只是需要注意的是我們這里的compose不能把方法拿來執(zhí)行就完了,應該返回一個包裹了所有方法的方法。 

  1. function compose(...func){  
  2.   return funcs.reduce((a, b) => (...args) => a(b(...args)));  

這個compose可能比較讓人困惑,我這里還是講解下,比如我們有三個函數(shù),這三個函數(shù)都是我們前面接收dispatch返回新dispatch的方法: 

  1. const fun1 = dispatch => newDispatch1;  
  2. const fun2 = dispatch => newDispatch2;  
  3. const fun3 = dispatch => newDispatch3; 

當我們使用了compose(fun1, fun2, fun3)后執(zhí)行順序是什么樣的呢? 

  1. // 第一次其實執(zhí)行的是  
  2. (func1, func2) => (...args) => func1(fun2(...args))  
  3. // 這次執(zhí)行完的返回值是下面這個,用個變量存起來吧  
  4. const temp = (...args) => func1(fun2(...args))  
  5. // 我們下次再循環(huán)的時候其實執(zhí)行的是  
  6. (temp, func3) => (...args) => temp(func3(...args));  
  7. // 這個返回值是下面這個,也就是最終的返回值,其實就是從func3開始從右往左執(zhí)行完了所有函數(shù)  
  8. // 前面的返回值會作為后面參數(shù) 
  9.  (...args) => temp(func3(...args));  
  10. // 再看看上面這個方法,如果把dispatch作為參數(shù)傳進去會是什么效果  
  11. (dispatch) => temp(func3(dispatch)); 
  12. // 然后func3(dispatch)返回的是newDispatch3,這個又傳給了temp(newDispatch3),也就是下面這個會執(zhí)行  
  13. (newDispatch3) => func1(fun2(newDispatch3))  
  14. // 上面這個里面用newDispatch3執(zhí)行fun2(newDispatch3)會得到newDispatch2  
  15. // 然后func1(newDispatch2)會得到newDispatch1  
  16. // 注意這時候的newDispatch1其實已經(jīng)包含了newDispatch3和newDispatch2的邏輯了,將它拿出來執(zhí)行這三個方法就都執(zhí)行了 

更多關于compose原理的細節(jié)可以看我之前這篇文章。

所以我們支持多個middleware的代碼就是這樣: 

  1. // 參數(shù)支持多個中間件  
  2. function applyMiddleware(...middlewares) {  
  3.   function enhancer(createStore) {  
  4.     function newCreateStore(reducer) {  
  5.       const store = createStore(reducer);      
  6.       // 多個middleware,先解構出dispatch => newDispatch的結構  
  7.       const chain = middlewares.map(middleware => middleware(store));
  8.        const { dispatch } = store;       
  9.        // 用compose得到一個組合了所有newDispatch的函數(shù)  
  10.       const newDispatchGen = compose(...chain);  
  11.       // 執(zhí)行這個函數(shù)得到newDispatch  
  12.       const newDispatch = newDispatchGen(dispatch);  
  13.       return {...store, dispatch: newDispatch}  
  14.     }    
  15.      return newCreateStore;  
  16.   } 
  17.    return enhancer; 
  18.  

最后我們再加一個logger2中間件實現(xiàn)效果: 

  1. function logger2(store) {  
  2.   return function(next) {  
  3.     return function(action) {  
  4.       let result = next(action);  
  5.       console.log('logger2');  
  6.       return result  
  7.     }  
  8.   }  
  9. let store = createStore(reducer, applyMiddleware(logger, logger2)); 

可以看到logger2也已經(jīng)打印出來了,大功告成。

現(xiàn)在我們也可以知道他的中間件為什么要包裹幾層函數(shù)了:

第一層:目的是傳入store參數(shù)

第二層:第二層的結構是dispatch => newDispatch,多個中間件的這層函數(shù)可以compose起來,形成一個大的dispatch => newDispatch

第三層:這層就是最終的返回值了,其實就是newDispatch,是增強過的dispatch,是中間件的真正邏輯所在。

到這里我們的applyMiddleware就寫完了,對應的源碼可以看這里,相信看了本文再去看源碼就沒啥問題了!

本文所有代碼已經(jīng)傳到GitHub,大家可以去拿下來玩一下:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/redux

總結

  1.  單純的Redux只是一個狀態(tài)機,store里面存了所有的狀態(tài)state,要改變里面的狀態(tài)state,只能dispatch action。
  2.  對于發(fā)出來的action需要用reducer來處理,reducer會計算新的state來替代老的state。
  3.  subscribe方法可以注冊回調(diào)方法,當dispatch action的時候會執(zhí)行里面的回調(diào)。
  4.  Redux其實就是一個發(fā)布訂閱模式!
  5.  Redux還支持enhancer,enhancer其實就是一個裝飾者模式,傳入當前的createStore,返回一個增強的createStore。
  6.  Redux使用applyMiddleware支持中間件,applyMiddleware的返回值其實就是一個enhancer。
  7.  Redux的中間件也是一個裝飾者模式,傳入當前的dispatch,返回一個增強了的dispatch。
  8.  單純的Redux是沒有View層的,所以他可以跟各種UI庫結合使用,比如react-redux,計劃下一篇文章就是手寫react-redux。 

 

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2022-04-26 08:32:36

CSS前端

2021-12-01 19:32:14

原理Node代碼

2022-11-04 09:43:05

Java線程

2024-03-12 00:00:00

Sora技術數(shù)據(jù)

2021-03-10 10:55:51

SpringJava代碼

2024-11-01 08:57:07

2022-09-05 08:39:04

kubernetesk8s

2020-08-10 18:03:54

Cache存儲器CPU

2024-04-15 00:00:00

技術Attention架構

2021-07-16 07:57:34

ReduxDOM組件

2023-09-19 22:47:39

Java內(nèi)存

2020-03-26 16:40:07

MySQL索引數(shù)據(jù)庫

2022-01-14 12:28:18

架構OpenFeign遠程

2022-09-26 08:01:31

線程LIFO操作方式

2019-07-01 13:34:22

vue系統(tǒng)數(shù)據(jù)

2022-09-05 22:22:00

Stream操作對象

2020-03-17 08:36:22

數(shù)據(jù)庫存儲Mysql

2020-11-04 15:35:13

Golang內(nèi)存程序員

2023-10-13 13:30:00

MySQL鎖機制

2021-09-26 05:03:31

數(shù)據(jù)流Redux
點贊
收藏

51CTO技術棧公眾號