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