成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一文徹底搞懂 DvaJS 原理

開發 前端
dva 首先是一個基于redux[1]和redux-saga[2]的數據流方案,然后為了簡化開發體驗,dva 還額外內置了react-router[3]和fetch[4],所以也可以理解為一個輕量級的應用框架。

 [[410111]]

Dva 是什么

dva 首先是一個基于redux[1]和redux-saga[2]的數據流方案,然后為了簡化開發體驗,dva 還額外內置了react-router[3]和fetch[4],所以也可以理解為一個輕量級的應用框架。

Dva 解決的問題

經過一段時間的自學或培訓,大家應該都能理解 redux 的概念,并認可這種數據流的控制可以讓應用更可控,以及讓邏輯更清晰。但隨之而來通常會有這樣的疑問:概念太多,并且 reducer, saga, action 都是分離的(分文件)。

  •  文件切換問題。redux 的項目通常要分 reducer, action, saga, component 等等,他們的分目錄存放造成的文件切換成本較大。
  •  不便于組織業務模型 (或者叫 domain model) 。比如我們寫了一個 userlist 之后,要寫一個 productlist,需要復制很多文件。
  •  saga 創建麻煩,每監聽一個 action 都需要走 fork -> watcher -> worker 的流程
  •  entry 創建麻煩。可以看下這個redux entry[5]的例子,除了 redux store 的創建,中間件的配置,路由的初始化,Provider 的 store 的綁定,saga 的初始化,還要處理 reducer, component, saga 的 HMR 。這就是真實的項目應用 redux 的例子,看起來比較復雜。

Dva 的優勢

  •  易學易用,僅有 6 個 api,對 redux 用戶尤其友好,配合 umi 使用[6]后更是降低為 0 API
  •  elm 概念,通過 reducers, effects 和 subscriptions 組織 model
  •  插件機制,比如dva-loading[7]可以自動處理 loading 狀態,不用一遍遍地寫 showLoading 和 hideLoading
  •  支持 HMR,基于babel-plugin-dva-hmr[8]實現 components、routes 和 models 的 HMR

Dva 的劣勢

  •  未來不確定性高。dva\@3 前年提出計劃后,官方幾乎不再維護[9]。

  •  對于絕大多數不是特別復雜的場景來說,目前可以被 Hooks 取代

Dva 的適用場景

  •  業務場景:組件間通信多,業務復雜,需要引入狀態管理的項目
  •  技術場景:使用 React Class Component 寫的項目

Dva 核心概念

  •  基于 Redux 理念的數據流向。用戶的交互或瀏覽器行為通過 dispatch 發起一個 action,如果是同步行為會直接通過 Reducers 改變 State,如果是異步行為(可以稱為副作用)會先觸發 Effects 然后流向 Reducers 最終改變 State。

  •  基于 Redux 的基本概念。包括:
    •   State 數據,通常為一個 JavaScript 對象,操作的時候每次都要當作不可變數據(immutable data)來對待,保證每次都是全新對象,沒有引用關系,這樣才能保證 State 的獨立性,便于測試和追蹤變化。
    •   Action 行為,一個普通 JavaScript 對象,它是改變 State 的唯一途徑。
    •   dispatch,一個用于觸發 action 改變 State 的函數。
    •   Reducer 描述如何改變數據的純函數,接受兩個參數:已有結果和 action 傳入的數據,通過運算得到新的 state。
    •   Effects(Side Effects) 副作用,常見的表現為異步操作。dva 為了控制副作用的操作,底層引入了redux-sagas[10]做異步流程控制,由于采用了generator 的相關概念[11],所以將異步轉成同步寫法,從而將 effects 轉為純函數。
    •   Connect 一個函數,綁定 State 到 View
  •  其他概念
    •   Subscription,訂閱,從源頭獲取數據,然后根據條件 dispatch 需要的 action,概念來源于elm[12]。數據源可以是當前的時間、服務器的 websocket 連接、keyboard 輸入、geolocation 變化、history 路由變化等等。
    •   Router,前端路由,dva 實例提供了 router 方法來控制路由,使用的是react-router[13]。
    •   Route Components,跟數據邏輯無關的組件。通常需要 connect Model 的組件都是 Route Components,組織在/routes/目錄下,而/components/目錄下則是純組件(Presentational Components,詳見組件設計方法[14])

Dva 應用最簡結構

不帶 Model 

  1. import dva from 'dva';  
  2. const App = () => <div>Hello dva</div> 
  3. // 創建應用  
  4. const app = dva();  
  5. // 注冊視圖  
  6. app.router(() => <App />);  
  7. // 啟動應用  
  8. app.start('#root'); 

帶 Model 

  1. // 創建應用  
  2. const app = dva();  
  3. app.use(createLoading()) // 使用插件  
  4. // 注冊 Model  
  5. app.model({  
  6.   namespace: 'count',  
  7.   state: 0,  
  8.   reducers: {  
  9.     add(state) { return state + 1 },  
  10.   },  
  11.   effects: {  
  12.     *addAfter1Second(action, { call, put }) {  
  13.       yield call(delay, 1000);  
  14.       yield put({ type: 'add' });  
  15.     },  
  16.   },  
  17. });  
  18. // 注冊視圖  
  19. app.router(() => <ConnectedApp />);  
  20. // 啟動應用  
  21. app.start('#root'); 

Dva底層原理和部分關鍵實現

背景介紹

  1.  整個 dva 項目使用 lerna 管理的,在每個 package 的 package.json 中找到模塊對應的入口文件,然后查看對應源碼。
  2.  dva 是個函數,返回一了個 app 的對象。
  3.  目前 dva 的源碼核心部分包含兩部分,dva 和 dva-core。前者用高階組件 React-redux 實現了 view 層,后者是用 redux-saga 解決了 model 層。

dva[15]

dva 做了三件比較重要的事情:

  1.  代理 router 和 start 方法,實例化 app 對象
  2.  調用 dva-core 的 start 方法,同時渲染視圖
  3.  使用 react-redux 完成了 react 到 redux 的連接。 
  1. // dva/src/index.js  
  2. export default function (opts = {}) {  
  3.   // 1. 使用 connect-react-router 和 history 初始化 router 和 history  
  4.   // 通過添加 redux 的中間件 react-redux-router,強化了 history 對象的功能  
  5.  const history = opts.history || createHashHistory();  
  6.   const createOpts = {  
  7.     initialReducer: {  
  8.       router: connectRouter(history),  
  9.     },  
  10.     setupMiddlewares(middlewares) {  
  11.       return [routerMiddleware(history), ...middlewares];  
  12.     },  
  13.     setupApp(app) {  
  14.       app._history = patchHistory(history);  
  15.     },  
  16.   };  
  17.   // 2. 調用 dva-core 里的 create 方法 ,函數內實例化一個 app 對象。  
  18.  const app = create(opts, createOpts);  
  19.   const oldAppStart = app.start;  
  20.   // 3. 用自定義的 router 和 start 方法代理  
  21.  app.router = router;  
  22.   app.start = start;  
  23.   return app;  
  24.   // 3.1 綁定用戶傳遞的 router 到 app._router  
  25.  function router(router) {  
  26.     invariant(  
  27.       isFunction(router),  
  28.       `[app.router] router should be function, but got ${typeof router}`,  
  29.     );  
  30.     app._router = router;  
  31.   }  
  32.   // 3.2 調用 dva-core 的 start 方法,并渲染視圖  
  33.  function start(container) {  
  34.     // 對 container 做一系列檢查,并根據 container 找到對應的DOM節點  
  35.     if (!app._store) {  
  36.       oldAppStart.call(app);  
  37.     }  
  38.     const store = app._store;  
  39.     // 為HMR暴露_getProvider接口  
  40.  // ref: https://github.com/dvajs/dva/issues/469  
  41.  app._getProvider = getProvider.bind(null, store, app);  
  42.     // 渲染視圖  
  43.  if (container) {  
  44.       render(container, store, app, app._router);  
  45.       app._plugin.apply('onHmr')(render.bind(null, container, store, app));  
  46.     } else {  
  47.       return getProvider(store, this, this._router);  
  48.     }  
  49.   }  
  50.  
  51. function getProvider(store, app, router) {  
  52.   const DvaRoot = extraProps => (  
  53.     <Provider store={store}>{router({ app, history: app._history, ...extraProps })}</Provider>  
  54.   );  
  55.   return DvaRoot;  
  56.  
  57. function render(container, store, app, router) {  
  58.   const ReactDOM = require('react-dom'); // eslint-disable-line  
  59.  ReactDOM.render(React.createElement(getProvider(store, app, router)), container);  

我們同時可以發現 app 是通過 create(opts, createOpts)進行初始化的,其中 opts 是暴露給使用者的配置,createOpts 是暴露給開發者的配置,真實的 create 方法在 dva-core 中實現

dva-core[16]

dva-core 則完成了核心功能:

  1.  通過 create 方法完成 app 實例的構造,并暴露 use、model 和 start 三個接口

  2.  通過 start 方法完成

  •   store 的初始化
  •   models 和 effects 的封裝,收集并運行 sagas
  •   運行所有的 model.subscriptions
  •   暴露 app.model、app.unmodel、app.replaceModel 三個接口

dva-core create

作用: 完成 app 實例的構造,并暴露 use、model 和 start 三個接口 

  1. // dva-core/src/index.js  
  2. const dvaModel = {  
  3.   namespace: '@@dva',  
  4.   state: 0,  
  5.   reducers: {  
  6.     UPDATE(state) {  
  7.       return state + 1;  
  8.     },  
  9.   },  
  10. };  
  11. export function create(hooksAndOpts = {}, createOpts = {}) {  
  12.   const { initialReducer, setupApp = noop } = createOpts; // 在dva/index.js中構造了createOpts對象  
  13.   const plugin = new Plugin(); // dva-core中的插件機制,每個實例化的dva對象都包含一個plugin對象  
  14.   plugin.use(filterHooks(hooksAndOpts)); // 將dva(opts)構造參數opts上與hooks相關的屬性轉換成一個插件  
  15.   const app = {  
  16.     _models: [prefixNamespace({ ...dvaModel })],  
  17.     _store: null,  
  18.     _plugin: plugin, 
  19.     use: plugin.use.bind(plugin), // 暴露的use方法,方便編寫自定義插件  
  20.     model, // 暴露的model方法,用于注冊model  
  21.     start, // 原本的start方法,在應用渲染到DOM節點時通過oldStart調用  
  22.   };  
  23.   return app;  

dva-core start

作用:

  1.  封裝models 和 effects ,收集并運行 sagas
  2.  完成store 的初始化
  3.  運行所有的model.subscriptions
  4.  暴露app.model、app.unmodel、app.replaceModel三個接口 
  1. function start() {  
  2.   const sagaMiddleware = createSagaMiddleware();  
  3.   const promiseMiddleware = createPromiseMiddleware(app);  
  4.   app._getSaga = getSaga.bind(null);  
  5.   const sagas = [];  
  6.   const reducers = { ...initialReducer };  
  7.   for (const m of app._models) {  
  8.     // 把每個 model 合并為一個reducer,key 是 namespace 的值,value 是 reducer 函數  
  9.     reducers[m.namespace] = getReducer(m.reducers, m.state, plugin._handleActions);  
  10.     if (m.effects) {  
  11.       // 收集每個 effects 到 sagas 數組  
  12.       sagas.push(app._getSaga(m.effects, m, onError, plugin.get('onEffect'), hooksAndOpts));  
  13.     }  
  14.   }  
  15.   // 初始化 Store  
  16.   app._store = createStore({  
  17.     reducers: createReducer(),  
  18.     initialState: hooksAndOpts.initialState || {},  
  19.     plugin, 
  20.     createOpts,  
  21.     sagaMiddleware, 
  22.     promiseMiddleware,  
  23.   });  
  24.   const store = app._store;  
  25.   // Extend store  
  26.   store.runSaga = sagaMiddleware.run;  
  27.   store.asyncReducers = {};  
  28.   // Execute listeners when state is changed  
  29.   const listeners = plugin.get('onStateChange');  
  30.   for (const listener of listeners) {  
  31.     store.subscribe(() => {  
  32.       listener(store.getState());  
  33.     });  
  34.   }  
  35.   // Run sagas, 調用 Redux-Saga 的 createSagaMiddleware 創建 saga中間件,調用中間件的 run 方法所有收集起來的異步方法  
  36.   // run方法監聽每一個副作用action,當action發生的時候,執行對應的 saga  
  37.   sagas.forEach(sagaMiddleware.run);  
  38.   // Setup app  
  39.   setupApp(app);  
  40.   // 運行 subscriptions 
  41.   const unlisteners = {};  
  42.   for (const model of this._models) {  
  43.     if (model.subscriptions) {  
  44.       unlisteners[model.namespace] = runSubscription(model.subscriptions, model, app, onError);  
  45.     }  
  46.   }  
  47.   // 暴露三個 Model 相關的接口,Setup app.model and app.unmodel  
  48.   app.model = injectModel.bind(app, createReducer, onError, unlisteners);  
  49.   app.unmodel = unmodel.bind(app, createReducer, reducers, unlisteners);  
  50.   app.replaceModel = replaceModel.bind(app, createReducer, reducers, unlisteners, onError);  
  51.   /**  
  52.    * Create global reducer for redux.  
  53.    *  
  54.    * @returns {Object}  
  55.    */  
  56.   function createReducer() {  
  57.     return reducerEnhancer(  
  58.       combineReducers({  
  59.         ...reducers,  
  60.         ...extraReducers,  
  61.         ...(app._store ? app._store.asyncReducers : {}),  
  62.       }),  
  63.     );  
  64.   }  
  65.  

路由

在前面的 dva.start 方法中我們看到了 createOpts,并了解到在 dva-core 的 start 中的不同時機調用了對應方法。 

  1. import * as routerRedux from 'connected-react-router';  
  2. const { connectRouter, routerMiddleware } = routerRedux;  
  3. const createOpts = {  
  4.   initialReducer: {  
  5.     router: connectRouter(history),  
  6.   },  
  7.   setupMiddlewares(middlewares) {  
  8.     return [routerMiddleware(history), ...middlewares];  
  9.   },  
  10.   setupApp(app) {  
  11.     app._history = patchHistory(history);  
  12.   },  
  13. }; 

其中 initialReducer 和 setupMiddlewares 在初始化 store 時調用,然后才調用 setupApp

可以看見針對 router 相關的 reducer 和中間件配置,其中 connectRouter 和 routerMiddleware 均使用了 connected-react-router 這個庫,其主要思路是:把路由跳轉也當做了一種特殊的 action。

Dva 與 React、React-Redux、Redux-Saga 之間的差異

原生 React

按照 React 官方指導意見, 如果多個 Component 之間要發生交互, 那么狀態(即: 數據)就維護在這些 Component 的最小公約父節點上,也即是

以及 本身不維持任何 state, 完全由父節點 傳入 props 以決定其展現, 是一個純函數的存在形式, 即: Pure Component

React-Redux

與上圖相比, 幾個明顯的改進點:

    1.  狀態及頁面邏輯從 里面抽取出來, 成為獨立的 store, 頁面邏輯就是 reducer

    2.  及都是 Pure Component, 通過 connect 方法可以很方便地給它倆加一層 wrapper 從而建立起與 store 的聯系: 可以通過 dispatch 向 store 注入 action, 促使 store 的狀態進行變化, 同時又訂閱了 store 的狀態變化, 一旦狀態變化, 被 connect 的組件也隨之刷新

    3.  使用 dispatch 往 store 發送 action 的這個過程是可以被攔截的, 自然而然地就可以在這里增加各種 Middleware, 實現各種自定義功能, eg: logging

這樣一來, 各個部分各司其職, 耦合度更低, 復用度更高, 擴展性更好。

Redux-Saga

因為我們可以使用 Middleware 攔截 action, 這樣一來異步的網絡操作也就很方便了, 做成一個 Middleware 就行了, 這里使用 redux-saga 這個類庫, 舉個栗子:

  1.  點擊創建 Todo 的按鈕, 發起一個 type == addTodo 的 action
  2.  saga 攔截這個 action, 發起 http 請求, 如果請求成功, 則繼續向 reducer 發一個 type == addTodoSucc 的 action, 提示創建成功, 反之則發送 type == addTodoFail 的 action 即可

Dva

有了前面三步的鋪墊, Dva 的出現也就水到渠成了, 正如 Dva 官網所言, Dva 是基于 React + Redux + Saga 的最佳實踐, 對于提升編碼體驗有三點貢獻:

  1.  把 store 及 saga 統一為一個 model 的概念, 寫在一個 js 文件里面
  2.  增加了一個 Subscriptions, 用于收集其他來源的 action, 比如鍵盤操作等
  3.  model 寫法很簡約, 類似于 DSL(領域特定語言),可以提升編程的沉浸感,進而提升效率

約定大于配置 

  1. app.model({  
  2.   namespace: 'count',  
  3.   state: {  
  4.     record: 0,  
  5.     current: 0,  
  6.   },  
  7.   reducers: {  
  8.     add(state) {  
  9.       const newCurrent = state.current + 1;  
  10.       return { ...state,  
  11.         record: newCurrent > state.record ? newCurrent : state.record,  
  12.         current: newCurrent,  
  13.       };  
  14.     },  
  15.     minus(state) {  
  16.       return { ...state, current: state.current - 1};  
  17.     },  
  18.   },  
  19.   effects: {  
  20.     *add(action, { call, put }) {  
  21.       yield call(delay, 1000);  
  22.       yield put({ type: 'minus' });  
  23.     },  
  24.   },  
  25.   subscriptions: {  
  26.     keyboardWatcher({ dispatch }) {  
  27.       key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });  
  28.     },  
  29.   },  
  30. }); 

Dva 背后值得學習的思想

Dva 的 api 參考了choo[17],概念來自于 elm。

  1.     Choo 的理念:編程應該是有趣且輕松的,API 要看上去簡單易用。

We believe programming should be fun and light, not stern and stressful. It's cool to be cute; using serious words without explaining them doesn't make for better results - if anything it scares people off. We don't want to be scary, we want to be nice and fun, and thencasually_be the best choice around._Real casually.

We believe frameworks should be disposable, and components recyclable. We don't want a web where walled gardens jealously compete with one another. By making the DOM the lowest common denominator, switching from one framework to another becomes frictionless. Choo is modest in its design; we don't believe it will be top of the class forever, so we've made it as easy to toss out as it is to pick up.

We don't believe that bigger is better. Big APIs, large complexities, long files - we see them as omens of impending userland complexity. We want everyone on a team, no matter the size, to fully understand how an application is laid out. And once an application is built, we want it to be small, performant and easy to reason about. All of which makes for easy to debug code, better results and super smiley faces.

    2.  來自 Elm 的概念:

  • Subscription,訂閱,從源頭獲取數據,數據源可以是當前的時間、服務器的 websocket 連接、keyboard 輸入、geolocation 變化、history 路由變化等等。

 

 

責任編輯:龐桂玉 來源: 前端大全
相關推薦

2020-12-07 06:19:50

監控前端用戶

2021-06-30 08:45:02

內存管理面試

2020-03-18 14:00:47

MySQL分區數據庫

2022-06-07 10:13:22

前端沙箱對象

2019-11-06 17:30:57

cookiesessionWeb

2023-09-08 08:20:46

ThreadLoca多線程工具

2024-10-15 17:12:38

代碼父子線程開源

2024-07-12 14:46:20

2022-04-11 10:56:43

線程安全

2021-01-13 05:21:59

參數

2024-08-08 14:57:32

2023-12-15 15:55:24

Linux線程同步

2023-11-23 06:50:08

括號

2020-12-18 09:36:01

JSONP跨域面試官

2021-07-21 05:24:32

EventBus3.0Android單例模式

2023-09-22 10:45:47

云原生云計算

2022-03-24 08:51:48

Redis互聯網NoSQL

2021-08-05 06:54:05

觀察者訂閱設計

2023-04-12 08:38:44

函數參數Context

2021-10-20 08:49:30

Vuexvue.js狀態管理模式
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久久久久91 | 日韩三区在线观看 | 久久精品| 9999国产精品欧美久久久久久 | 亚洲a视频 | 日韩国产一区二区三区 | 岛国精品| 欧美日韩国产精品激情在线播放 | 九九久久免费视频 | 黄频视频 | 久久久久久网 | 在线观看中文字幕dvd播放 | 免费在线观看成人 | 久久亚洲视频网 | 欧美一区二区另类 | 中文字幕在线看第二 | 99久久99热这里只有精品 | 色桃网 | 欧美日韩综合精品 | 天天视频一区二区三区 | 亚洲黄色片免费观看 | 精品乱码一区二区三四区 | 黄网站涩免费蜜桃网站 | 91影视 | 日本久久久久久 | 国产片侵犯亲女视频播放 | 91精品一区二区三区久久久久久 | 在线视频 亚洲 | 中文字幕日韩欧美一区二区三区 | 草久久免费视频 | 在线不卡视频 | 欧美a在线观看 | 国产一区免费 | 日韩在线成人 | 日韩在线综合网 | 国产精品一区视频 | 国产精品婷婷 | 国产一区在线看 | 国产亚洲精品久久久久动 | 国产精品a久久久久 | 亚洲视频一区在线观看 |