摘要:系列文章入門本文進階番外篇狀態管理,第一次聽到這個詞要追溯到去年年底。只讀的唯一改變的方法就是觸發,是一個用于描述已發生事件的普通對象。沒有特殊情況沒有副作用,沒有請求沒有變量修改,只進行單純執行計算。
系列文章:
Redux 入門(本文)
Redux 進階
番外篇: Vuex — The core of Vue application
狀態管理,第一次聽到這個詞要追溯到去年年底。那時,Flux 紅透半邊天,而 Reflux 也是風華正茂。然而,前一陣一直在忙其他的事,一直沒時間學學這兩個庫,到現在 Redux 似乎又有一統天下的趨勢。
那就來看看,Redux 是憑借什么做到異軍突起的。
What"s ReduxRedux 是一個 JavaScript 應用狀態管理的庫,它幫助你編寫行為一致,并易于測試的代碼,而且它非常迷你,只有 2KB。
Redux 有一點和別的前端庫或框架不同,它不單單是一套類庫,它更是一套方法論,告訴你如何去構建一個狀態可預測的應用。
Why using Redux隨著單頁應用變得越來越復雜,前端代碼需要管理各種各樣的狀態,它可以是服務器的響應,也可能是前端界面的狀態。當這個狀態變得任意可變,那么你就可能在某個時間點失去對整個應用狀態的控制。
Redux 就是為了解決這個問題而誕生的。
簡短地說,Redux 為整個應用創建并管理一棵狀態樹,并通過限制更新發生的時間和方式,而使得整個應用狀態的變化變得可以被預測。
除此之外,Redux 有著一整套豐富的生態圈,包括教程、中間件、開發者工具及文檔,這些都可以在官方文檔中找到。
How to use Redux 三大原則在使用 Redux 之前,你必須要謹記它的三大原則:單一數據源、state 是只讀的和使用純函數執行修改。
單一數據源
整個應用的 state 都被儲存在一棵樹中,并且這棵狀態樹只存在于唯一一個 store 中。
這使得來自服務端的 state 可以輕易地注入到客戶端中;并且,由于是單一的 state 樹,代碼調試、以及“撤銷/重做”這類功能的實現也變得輕而易舉。
只讀的 state
唯一改變 state 的方法就是觸發 action,action 是一個用于描述已發生事件的普通對象。
這就表示無論是用戶操作或是請求數據都不能直接修改 state,相反它們只能通過觸發 action 來變更當前應用狀態。其次,action 就是普通對象,因此它們可以被日志打印、序列化、儲存,以及用于調試或測試的后期回放。
使用純函數執行修改
為每個 action 用純函數編寫 reducer 來描述如何修改 state 樹
或許你是第一次聽到純函數這個概念,但它是函數話編程的基礎。
純函數在維基百科上的解釋簡單來說是滿足以下兩項:
函數在有相同的輸入值時,產生相同的輸出
函數中不包含任何會產生副作用的語句
在這里,reducer 要做到只要傳入參數相同,返回計算得到的下一個 state 就一定相同。沒有特殊情況、沒有副作用,沒有 API 請求、沒有變量修改,只進行單純執行計算。
知道了三大原則之后,那就可以開始了解如何創建一個基于 Redux 的應用。
Action就如之前提到的,action 是一個描述事件的簡單對象,它是改變 store 中 state 的唯一方法,它通過 store.dispatch() 方法來將 action 傳到 store 中。
下面就是一個 action 的例子,它表示添加一個新的 todo 項。
const ADD_TODO = "ADD_TODO" // action { type: ADD_TODO, text: "Build my first Redux app" }
可以看到 action 就是一個簡單的 JavaScript 對象。
用一個字符串類型的 type 字段來表示將要執行的動作,type 最好用常量來定義,當應用擴大時,可以使用多帶帶的模塊來存放 action。
除了 type 字段外,action 對象的結構完全由你自己決定(也可以借鑒 flux-standard-action 來構建你的 action)。
在現實場景中,action 所傳遞的值很少會是一個固定的值,都是動態產生的。所以,要為每個 action 創建它的工廠方法,工廠方法返回一個 action 對象。
上面的那個例子就會變為:
function addTodo(text) { return { type: ADD_TODO, text } }
Action 的創建工廠可以是異步非純函數。牽扯到異步的問題內容就比較多,放到下一篇再分享了。
ReducerAction 只是一個描述事件的簡單對象,并沒有告訴應用該如何更新 state,而這正是 reducer 的工作。
在 Redux 應用中,所有的 state 都被保存在一個單一對象中。所以,建議在寫代碼前先確定這個對象的結構。如何才能以最簡的形式把應用的 state 用對象描述出來?
在設計過程中,你會發現你有時需要在 state 中存儲一些如 UI 的 state,盡量將應用數據和 UI state 分開存放。
{ todos: [ { text: "Consider using Redux", completed: true, }, { text: "Keep all state in a single tree", completed: false } ] }
注意:在處理復雜應用時,建議盡可能地把 state 范式化,把所有數據放到一個對象里,每個數據以 ID 為主鍵,不同實體或列表間通過 ID 相互引用數據,這種方法在 normalizr 文檔里有詳細闡述。
現在我們已經確定了 state 對象的結構,就可以開始開發 reducer。reducer 是一個純函數,它接收舊的 state 和 action,返回新的 state,就像這樣
(previousState, action) => newState
還記不記得三大原則?
沒錯,最后一點使用純函數進行修改,所以,永遠不要在 reducer 里做這些操作:
修改傳入的參數(即之前的 state 或 action 對象)
執行有副作用的操作,如 API 請求或路由跳轉
調用非純函數,如 Date.now() 或 Math.random() 等
將這些銘記于心后,就能創建對應之前 action 的 reducer 了。
const initialState = { todos: [] } function todoApp(state = initialState, action) { switch (action.type) { case ADD_TODO: return { ...state, todos: [ ...state.todos, { text: action.text, completed: false } ] } default: return state } }
注意:
不要修改傳入的 state,否則它就不是個純函數
在遇到未知 action type 的時候,默認返回之前的 state
這樣一個 reducer 就創建好了,是不是很簡單?多個 action 也是如此,我們再來添加一個
case TOGGLE_TODO: return { ...state, todos: state.todos.map((todo, index) => { if (index === action.index) { return { ...todo, completed: !todo.completed } // 時刻謹記不要修改 state,保證 reducer 是純函數 } return todo }) }
從例子中可以發現,當對 state 的一部分進行操作時,不會影響 state 的其他部分,但仍需復制 state 樹的其他部分。當項目的規模成長時,state 樹的層次也會隨之增長,對樹深層節點的操作將會帶來大量的復制。
此時,我們就可以將這些相互獨立的 reducer 拆分開來,我們之前的例子就可以改成這樣(官網的例子更能體現這一點,為了縮減篇幅我這里省略了另一個 reducer)。
// todos reducer function todos(state = [], action) { switch (action.type) { case ADD_TODO: return [ ...state, { text: action.text, completed: false } ] case TOGGLE_TODO: return state.map((todo, index) => { if (index === action.index) { return { ...todo, completed: !todo.completed } // 時刻謹記不要修改 state,保證 reducer 是純函數 } return todo }) default: return state } } // main reducer function todoApp(state = initialState, action) { switch (action.type) { case ADD_TODO: case TOGGLE_TODO: return { ...state, todos: todos(state.todos, action) } default: return state } }
這就是所謂的 reducer 合成,它是開發 Redux 應用的基礎。
注意:每個 reducer 應當只負責管理全局 state 中它負責的一部分;并且,每個 reducer 的 state 參數分別對應它管理的那部分 state。
由于,每個 reducer 應當只負責管理全局 state 中它負責的一部分,那么上面的 main reducer 就能改為
// main reducer function todoApp(state = initialState, action) { return { todos: todos(state.todos, action) } }
最后,Redux 提供了 combineReducers() 工具類,它能幫我們減少很多重復的模板代碼。
combineReducers() 就像一個工廠,它根據傳入對象的 key 來篩選出 state 中 key 所對應的值傳給對應的 reducer,最終它返回一個符合規范的 reducer 函數。
最終,我們的 main reducer 就變為
// main reducer const todoApp = combineReducers({ todos // 等價于 todos: todos(state.todos, action) })
隨著應用的膨脹,你可以將拆分后的 reducer 放到不同的文件中, 以保持其獨立性。然后,你的代碼就可以變成這樣...
import { combineReducers } from "redux" import * as reducers from "./reducers" const todoApp = combineReducers(reducers) export default todoAppStore
Store 用來存放整個應用的 state,并將 action 和 reducer 聯系起來。它主要有以下幾個職能:
存儲整個應用的 state
提供 getState() 方法獲取 state
提供 dispatch(action) 方法更新 state
提供 subscribe(listener) 來注冊、取消監聽器
根據已有的 reducer 來創建 store 非常容易,只需將 reducer 作為參數傳遞給 createStore() 方法。
import { createStore } from "redux" import todoApp from "./reducers" let store = createStore(todoApp)
這樣,整個應用的 store 就創建完成了。雖然還沒有界面,但我們已經可以測試數據處理邏輯了。
import { addTodo, toggleTodo } from "./actions" // 打印初始狀態 console.log(store.getState()) // 注冊監聽器,在每次 state 更新時,打印日志 const unsubscribe = store.subscribe(() => console.log(store.getState()) ) // 發起 actions store.dispatch(addTodo("Learn about actions")) store.dispatch(addTodo("Learn about reducers")) store.dispatch(addTodo("Learn about store")) store.dispatch(actions.toggleTodo(0)) store.dispatch(actions.toggleTodo(1)) // 停止監聽 unsubscribe();
運行代碼,控制臺中就能看到下面的輸出。
Data flow時刻謹記一點:嚴格的單向數據流是 Redux 架構的設計核心。
也就是說,對 state 樹的任何修改都該通過 action 發起,然后經過一系列 reducer 組合的處理,最后返回一個新的 state 對象。
Take a try with Angular之前的舉例已經將 redux 最基本的一套生命周期處理展示完畢了,但沒有個界面顯示總是不那么令人信服。Redux 官網的例子是將 Redux 同 React 一起使用,但如同一開始說的,Redux 更是一套方法論,它不單可以和 React 一同使用,也可以和 Angular 等其他框架一同使用。
雖然,同官網用的是不同的框架,但概念是相通的。
首先,頁面都是由組件構成,組件又分為兩大類:容器組件(Smart/Container Components)和展示組件(Dumb/Presentational Components)。
容器組件 | 展示組件 | |
---|---|---|
目的 | 數據處理,state 更新 | 界面展示 |
受 redux 影響 | 是 | 否 |
數據來源 | store.subscribe() | 組件屬性傳遞 |
修改數據 | store.dispatch() | 調用通過組件屬性傳遞的方法 |
簡單來說,容器組件就是通過 store.subscribe() 這個方法監聽 store 中 state 的變化,而展示組件,就是平常使用的普通的組件,只有一點需要注意的是,所有數據修改都是通過父組件中傳遞下來的 store.dispatch() 方法來修改。
可以說,容器組件是整個界面顯示的核心。
// todos/index.js import angular from "angular" import template from "./todos.html" import controller from "./todos" const todoContainer = { controller, template } export default angular.module("todoContainer", []) .component("todoContainer", todoContainer) .name // todos/todos.js import store from "../../store" import actions from "../../actions" export default class TodosContainController { $onInit() { // 注冊監聽器,在每次 state 更新時,更新頁面綁定內容 this.unsubscribe = store.subscribe(() => { console.log(store.getState()) this.todos = store.getState().todos }) } addTodoItem(text) { store.dispatch(actions.addTodo(text)) } toggleTodoItem(index) { store.dispatch(actions.toggleTodo(index)) } $onDistory() { // 銷毀監聽器 this.unsubscribe() } } // todos/todos.html
Redux 官網并不建議直接這樣使用 store.subscribe() 來監聽數據的變化,而是調用 React Redux 庫的 connect() 方法,因為 connect 方法做了許多性能上的優化。相對于 Angular,也有 ng-redux 和 ng2-redux 提供了相同的方法。
鑒于展示組件與 redux 并沒有太大的相關,就不在這里贅述了,有興趣可以去 github 上查看。
至此,一個簡單的基于 Angular 并運用 Redux 的 todo MVC 應用就完成了。
最后如果你熟悉 Flux,那么這篇圖文并茂的文章獲取會對你有很大的幫助。
如果你是和我一樣直接接觸 Redux,那官方文檔是你的首選。
當然,你一定得看看 Redux 作者 Dan Abramov 自己錄制的視頻,它會對你理解 Redux 有極大的幫助。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/79978.html
摘要:描述這個插件可以讓我們的代碼更加的簡潔和美觀。安裝使用提供了兩個重要的接口使用了這個插件,的和就可以忘記來,它們就用不著了。現在有美女個。 可先查看我的redux簡單入門 react-redux簡介 react-redux是使用redux開發react時使用的一個插件,另外插一句,redux不是react的產品,vue和angular中也可以使用redux;下面簡單講解,如何使用rea...
摘要:概述之前寫的所有關于的文章都是純粹的,是和框架無關環境無關的,所以我沒有將和一起講,為的是吧和分開,作為獨立的個體來分析,提現的是一種思想,而不是一個思維定式。而現在我們可以嘗試在中來使用了。 0x000 概述 之前寫的所有關于redux的文章都是純粹的redux,是和框架無關、環境無關的redux,所以我沒有將redux和react一起講,為的是吧redux和react分開,作為獨立...
摘要:我的入門到放棄之路最近看到很多相關的問題跟討論,越來越多的小伙伴喜歡這個框架了,同時也在看到了有些入門的小伙伴遇到了各種各樣的問題,本人也是框架使用都一枚,公司是騰訊阿里平安三巨頭合資的一家公司,分別上海深圳杭州北京廣州等多個分部,前端人員 showImg(https://segmentfault.com/img/bVbhonB?w=1278&h=722); 我的react入門到放棄之...
摘要:具體了解此方法可以請戳這里最后把對象暴露給在主入口進行調用我們通過提供的頂層組件傳入然后把要展示的寫入頂層組件就行了,提供了整個全局的供所有的子組件進行調用具體代碼實現請 項目目錄 showImg(https://segmentfault.com/img/bVTGs8?w=214&h=571); 整個項目目錄分為圖中所示: Redux分為{Action,Reducer,Store} 入...
閱讀 2588·2021-11-22 12:01
閱讀 1113·2021-11-15 11:37
閱讀 3696·2021-09-22 14:59
閱讀 1761·2021-09-04 16:45
閱讀 1392·2021-09-03 10:30
閱讀 1026·2021-08-11 11:18
閱讀 2467·2019-08-30 10:53
閱讀 2023·2019-08-29 15:13