摘要:只要一個有,那無論用什么設備訪問,都會得到這個還原也是相當簡單把數據庫備份導入到另一臺機器,部署同樣的運行環境與代碼。純粹只是一個狀態管理庫,幾乎可以搭配任何框架使用上述例子連都沒用哦親下一章進階教程
Redux 簡明教程
§ 為什么要用 Redux原文鏈接(保持更新):https://github.com/kenberkele...
寫在前面本教程深入淺出,配套 簡明教程、進階教程(源碼精讀)以及文檔注釋豐滿的 Demo 等一條龍服務
當然還有 Flux、Reflux、Mobx 等狀態管理庫可供選擇
拋開需求講實用性都是耍流氓,因此下面由我扮演您那可親可愛的產品經理
⊙ 需求 1:在控制臺上記錄用戶的每個動作不知道您是否有后端的開發經驗,后端一般會有記錄訪問日志的中間件
例如,在 Express 中實現一個簡單的 Logger 如下:
var loggerMiddleware = function(req, res, next) { console.log("[Logger]", req.method, req.originalUrl) next() } ... app.use(loggerMiddleware)
每次訪問的時候,都會在控制臺中留下類似下面的日志便于追蹤調試:
[Logger] GET / [Logger] POST /login [Logger] GET /user?uid=10086 ...
如果我們把場景轉移到前端,請問該如何實現用戶的動作跟蹤記錄?
我們可能會這樣寫:
/** jQuery **/ $("#loginBtn").on("click", function(e) { console.log("[Logger] 用戶登錄") ... }) $("#logoutBtn").on("click", function() { console.log("[Logger] 用戶退出登錄") ... }) /** MVC / MVVM 框架(這里以純 Vue 舉例) **/ methods: { handleLogin () { console.log("[Logger] 用戶登錄") ... }, handleLogout () { console.log("[Logger] 用戶退出登錄") ... } }
上述 jQuery 與 MV* 的寫法并沒有本質上的區別
記錄用戶行為代碼的侵入性極強,可維護性與擴展性堪憂
哼!最討厭就是改需求了,這種簡單的需求難道不是應該一開始就想好的嗎?
呵呵,如果每位產品經理都能一開始就把需求完善好,我們就不用加班了好伐
顯然地,前端的童鞋又得一個一個去改(當然 編輯器 / IDE 都支持全局替換):
/** jQuery **/ $("#loginBtn").on("click", function(e) { console.log("[Logger] 用戶登錄", new Date()) ... }) $("#logoutBtn").on("click", function() { console.log("[Logger] 用戶退出登錄", new Date()) ... }) /** MVC / MVVM 框架(這里以 Vue 舉例) **/ methods: { handleLogin () { console.log("[Logger] 用戶登錄", new Date()) ... }, handleLogout () { console.log("[Logger] 用戶退出登錄", new Date()) ... } }
而后端的童鞋只需要稍微修改一下原來的中間件即可:
var loggerMiddleware = function(req, res, next) { console.log("[Logger]", new Date(), req.method, req.originalUrl) next() } ... app.use(loggerMiddleware)⊙ 需求 3:正式上線的時候,把控制臺中有關 Logger 的輸出全部去掉
難道您以為有了 UglifyJS,配置一個 drop_console: true 就好了嗎?圖樣圖森破,拿衣服!
請看清楚了,僅僅是去掉有關 Logger 的 console.log,其他的要保留哦親~~~
于是前端的童鞋又不得不乖乖地一個一個注釋掉(當然也可以設置一個環境變量判斷是否輸出,甚至可以重寫 console.log)
而我們后端的童鞋呢?只需要注釋掉一行代碼即可:// app.use(loggerMiddleware),真可謂是不費吹灰之力
⊙ 需求 4:正式上線后,自動收集 bug,并還原出當時的場景收集用戶報錯還是比較簡單的,利用 window.error 事件,然后根據 Source Map 定位到源碼(但一般查不出什么)
但要完全還原出當時的使用場景,幾乎是不可能的。因為您不知道這個報錯,用戶是怎么一步一步操作得來的
就算知道用戶是如何操作得來的,但在您的電腦上,測試永遠都是通過的(不是我寫的程序有問題,是用戶用的方式有問題)
相對地,后端的報錯的收集、定位以及還原卻是相當簡單。只要一個 API 有 bug,那無論用什么設備訪問,都會得到這個 bug
還原 bug 也是相當簡單:把數據庫備份導入到另一臺機器,部署同樣的運行環境與代碼。如無意外,bug 肯定可以完美重現
※ 小結在這個問題上拿后端跟前端對比,確實有失公允。但為了鼓吹 Redux 的優越,只能勉為其難了
實際上 jQuery / MV* 中也能實現用戶動作的跟蹤,用一個數組往里面 push 用戶動作即可
但這樣操作的意義不大,因為僅僅只有動作,無法反映動作前后,應用狀態的變動情況
為何前后端對于這類需求的處理竟然大相徑庭?后端為何可以如此優雅?
原因在于,后端具有統一的入口與統一的狀態管理(數據庫),因此可以引入中間件機制來統一實現某些功能
多年來,前端工程師忍辱負重,操著賣白粉的心,賺著買白菜的錢,一直處于程序員鄙視鏈的底層
于是有大牛就把后端 MVC 的開發思維搬到前端,將應用中所有的動作與狀態都統一管理,讓一切有據可循
使用 Redux,借助 Redux DevTools 可以實現出“華麗如時光旅行一般的調試效果”
實際上就是開發調試過程中可以撤銷與重做,并且支持應用狀態的導入和導出(就像是數據庫的備份)
而且,由于可以使用日志完整記錄下每個動作,因此做到像 Git 般,隨時隨地恢復到之前的狀態
§ Store由于可以導出和導入應用的狀態(包括路由狀態),因此還可以實現前后端同構(服務端渲染)
當然,既然有了動作日志以及動作前后的狀態備份,那么還原用戶報錯場景還會是一個難題嗎?
首先要區分 store 和 state
state 是應用的狀態,一般本質上是一個普通對象
例如,我們有一個 Web APP,包含 計數器 和 待辦事項 兩大功能
那么我們可以為該應用設計出對應的存儲數據結構(應用初始狀態):
/** 應用初始 state,本代碼塊記為 code-1 **/ { counter: 0, todos: [] }
store 是應用狀態 state 的管理者,包含下列四個函數:
getState() # 獲取整個 state
dispatch(action) # ※ 觸發 state 改變的【唯一途徑】※
subscribe(listener) # 您可以理解成是 DOM 中的 addEventListener
replaceReducer(nextReducer) # 一般在 Webpack Code-Splitting 按需加載的時候用
二者的關系是:state = store.getState()
Redux 規定,一個應用只應有一個單一的 store,其管理著唯一的應用狀態 state
Redux 還規定,不能直接修改應用的狀態 state,也就是說,下面的行為是不允許的:
var state = store.getState() state.counter = state.counter + 1 // 禁止在業務邏輯中直接修改 state
若要改變 state,必須 dispatch 一個 action,這是修改應用狀態的不二法門
現在您只需要記住 action 只是一個包含 type 屬性的普通對象即可
例如 { type: "INCREMENT" }
上面提到,state 是通過 store.getState() 獲取,那么 store 又是怎么來的呢?
想生成一個 store,我們需要調用 Redux 的 createStore:
import { createStore } from "redux" ... const store = createStore(reducer, initialState) // store 是靠傳入 reducer 生成的哦!
§ Action現在您只需要記住 reducer 是一個 函數,負責更新并返回一個新的 state
而 initialState 主要用于前后端同構的數據同步(詳情請關注 React 服務端渲染)
上面提到,action(動作)實質上是包含 type 屬性的普通對象,這個 type 是我們實現用戶行為追蹤的關鍵
例如,增加一個待辦事項 的 action 可能是像下面一樣:
/** 本代碼塊記為 code-2 **/ { type: "ADD_TODO", payload: { id: 1, content: "待辦事項1", completed: false } }
當然,action 的形式是多種多樣的,唯一的約束僅僅就是包含一個 type 屬性罷了
也就是說,下面這些 action 都是合法的:
/** 如下都是合法的,但就是不夠規范 **/ { type: "ADD_TODO", id: 1, content: "待辦事項1", completed: false } { type: "ADD_TODO", abcdefg: { id: 1, content: "待辦事項1", completed: false } }
雖說沒有約束,但最好還是遵循規范
如果需要新增一個代辦事項,實際上就是將 code-2 中的 payload “寫入” 到 state.todos 數組中(如何“寫入”?在此留個懸念):
/** 本代碼塊記為 code-3 **/ { counter: 0, todos: [{ id: 1, content: "待辦事項1", completed: false }] }
刨根問底,action 是誰生成的呢?
⊙ Action CreatorAction Creator 可以是同步的,也可以是異步的
顧名思義,Action Creator 是 action 的創造者,本質上就是一個函數,返回值是一個 action(對象)
例如下面就是一個 “新增一個待辦事項” 的 Action Creator:
/** 本代碼塊記為 code-4 **/ var id = 1 function addTodo(content) { return { type: "ADD_TODO", payload: { id: id++, content: content, // 待辦事項內容 completed: false // 是否完成的標識 } } }
將該函數應用到一個表單(假設 store 為全局變量,并引入了 jQuery ):
<--! 本代碼塊記為 code-5 -->
在輸入框中輸入 “待辦事項2” 后,點擊一下提交按鈕,我們的 state 就變成了:
/** 本代碼塊記為 code-6 **/ { counter: 0, todos: [{ id: 1, content: "待辦事項1", completed: false }, { id: 2, content: "待辦事項2", completed: false }] }
通俗點講,Action Creator 用于綁定到用戶的操作(點擊按鈕等),其返回值 action 用于之后的 dispatch(action)
剛剛提到過,action 明明就沒有強制的規范,為什么 store.dispatch(action) 之后,
Redux 會明確知道是提取 action.payload,并且是對應寫入到 state.todos 數組中?
又是誰負責“寫入”的呢?懸念即將揭曉...
Reducer 必須是同步的純函數
用戶每次 dispatch(action) 后,都會觸發 reducer 的執行
reducer 的實質是一個函數,根據 action.type 來更新 state 并返回 nextState
最后會用 reducer 的返回值 nextState 完全替換掉原來的 state
注意:上面的這個 “更新” 并不是指 reducer 可以直接對 state 進行修改
Redux 規定,須先復制一份 state,在副本 nextState 上進行修改操作
例如,可以使用 lodash 的 deepClone,也可以使用 Object.assign / map / filter/ ... 等返回副本的函數
在上面 Action Creator 中提到的 待辦事項的 reducer 大概是長這個樣子 (為了容易理解,在此不使用 ES6 / Immutable.js):
/** 本代碼塊記為 code-7 **/ var initState = { counter: 0, todos: [] } function reducer(state, action) { // ※ 應用的初始狀態是在第一次執行 reducer 時設置的(除非是服務端渲染) ※ if (!state) state = initState switch (action.type) { case "ADD_TODO": var nextState = _.deepClone(state) // 用到了 lodash 的深克隆 nextState.todos.push(action.payload) return nextState default: // 由于 nextState 會把原 state 整個替換掉 // 若無修改,必須返回原 state(否則就是 undefined) return state } }
§ 總結通俗點講,就是 reducer 返回啥,state 就被替換成啥
store 由 Redux 的 createStore(reducer) 生成
state 通過 store.getState() 獲取,本質上一般是一個存儲著整個應用狀態的對象
action 本質上是一個包含 type 屬性的普通對象,由 Action Creator (函數) 產生
改變 state 必須 dispatch 一個 action
reducer 本質上是根據 action.type 來更新 state 并返回 nextState 的函數
reducer 必須返回值,否則 nextState 即為 undefined
實際上,state 就是所有 reducer 返回值的匯總(本教程只有一個 reducer,主要是應用場景比較簡單)
⊙ Redux 與傳統后端 MVC 的對照Action Creator => action => store.dispatch(action) => reducer(state, action) =>
原 statestate = nextState
Redux | 傳統后端 MVC |
---|---|
store | 數據庫實例 |
state | 數據庫中存儲的數據 |
dispatch(action) | 用戶發起請求 |
action: { type, payload } | type 表示請求的 URL,payload 表示請求的數據 |
reducer | 路由 + 控制器(handler) |
reducer 中的 switch-case 分支 | 路由,根據 action.type 路由到對應的控制器 |
reducer 內部對 state 的處理 | 控制器對數據庫進行增刪改操作 |
reducer 返回 nextState | 將修改后的記錄寫回數據庫 |
§ 下一章:Redux 進階教程由上可知,Redux 并不一定要搭配 React 使用。Redux 純粹只是一個狀態管理庫,幾乎可以搭配任何框架使用
(上述例子連 jQuery 都沒用哦親)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80157.html
摘要:進階教程原文保持更新寫在前面相信您已經看過簡明教程,本教程是簡明教程的實戰化版本,伴隨源碼分析用的是編寫,看到有疑惑的地方的,可以復制粘貼到這里在線編譯總覽在的源碼目錄,我們可以看到如下文件結構打醬油的,負責在控制臺顯示警告信息入口文件除去 Redux 進階教程 原文(保持更新):https://github.com/kenberkele... 寫在前面 相信您已經看過 Redux ...
摘要:在函數式編程中,異步操作修改全局變量等與函數外部環境發生的交互叫做副作用通常認為這些操作是邪惡骯臟的,并且也是導致的源頭。 注:這篇是17年1月的文章,搬運自本人 blog... https://github.com/BuptStEve/... 零、前言 在上一篇中介紹了 Redux 的各項基礎 api。接著一步一步地介紹如何與 React 進行結合,并從引入過程中遇到的各個痛點引出 ...
摘要:前端每周清單半年盤點之與篇前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點分為新聞熱點開發教程工程實踐深度閱讀開源項目巔峰人生等欄目。與求同存異近日,宣布將的構建工具由遷移到,引發了很多開發者的討論。 前端每周清單半年盤點之 React 與 ReactNative 篇 前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點;分為...
摘要:這個速查表主要是分享互聯網上一些比較常用的工具和技術常用內容,如編輯器的快捷鍵的命令行的選擇器的屬性等,這個列表簡單收集了常用的工具,可以收藏用于平時的備忘錄,需要用到的時候可以及時查閱。 這個速查表主要是分享互聯網上一些比較常用的工具和技術常用內容,如編輯器的快捷鍵、git的命令行、jQuery的API選擇器、CSS的flexbox屬性等,這個列表簡單收集了常用的工具,可以收藏用于平...
摘要:這個速查表主要是分享互聯網上一些比較常用的工具和技術常用內容,如編輯器的快捷鍵的命令行的選擇器的屬性等,這個列表簡單收集了常用的工具,可以收藏用于平時的備忘錄,需要用到的時候可以及時查閱。 這個速查表主要是分享互聯網上一些比較常用的工具和技術常用內容,如編輯器的快捷鍵、git的命令行、jQuery的API選擇器、CSS的flexbox屬性等,這個列表簡單收集了常用的工具,可以收藏用于平...
閱讀 3686·2021-09-07 10:19
閱讀 3627·2021-09-03 10:42
閱讀 3584·2021-09-03 10:28
閱讀 2548·2019-08-29 14:11
閱讀 809·2019-08-29 13:54
閱讀 1594·2019-08-29 12:14
閱讀 417·2019-08-26 12:12
閱讀 3614·2019-08-26 10:45