摘要:的核心思想就是維護(hù)一個單向數(shù)據(jù)流,數(shù)據(jù)的流向永遠(yuǎn)是單向的,所以每個步驟便是可預(yù)測的,程序的健壯性得到了保證。另外,還有一點(diǎn)比較重要的是,因?yàn)闆]有了一個一直保存更新的狀態(tài)對象,所以在中的也就沒有意義了,通過可以完全實(shí)現(xiàn)一個順暢的數(shù)據(jù)流。
1 Redux
Redux is a predictable state container for JavaScript apps
簡單來說,Redux是一個管理應(yīng)用狀態(tài)的框架
2 解決的問題 2.1 前端開發(fā)中的普遍問題前端開發(fā)中,本質(zhì)的問題就是將 server -> client 的輸入,變成 client -> user 輸入;再將 user -> client 的輸入,變成 client -> server 的輸入。
在 client 中,前端的角色其實(shí)大概可以當(dāng)做一個"轉(zhuǎn)換器"。
舉個簡單的例子,后端傳過來的是一個 json 格式的數(shù)據(jù),這個 json 格式,其實(shí)是在計(jì)算機(jī)范疇內(nèi)的,真正的終端用戶并不知道什么是json,更不知道要如何修改json,保存自己的信息。所以,這個時候就需要像上面說的把 json 轉(zhuǎn)換為頁面上的內(nèi)容和元素;另外,隨著用戶的一系列操作,數(shù)據(jù)需要隨時更新保存到服務(wù)端。整個的這個過程,可能會很復(fù)雜,數(shù)據(jù)和數(shù)據(jù)之間會存在聯(lián)動關(guān)系。
這個時候,就需要有一個東西,從更高的層面來管理所有的這些狀態(tài),于是有了 mvc,狀態(tài)保存在model,數(shù)據(jù)展示在view,controller來串聯(lián)用戶的輸入和數(shù)據(jù)的更新。但是這個時候就會有個問題,理想情況下,我們默認(rèn)所有的狀態(tài)更新都是由用戶的操作(也可以理解為用戶的輸入)來觸發(fā)的,但實(shí)際情況中,會觸發(fā)狀態(tài)更新的不僅僅是單純的用戶操作,還有可能是用戶操作帶來的后果,在舉個例子:
頁面上有個異步獲取信息的按鈕,用戶可以點(diǎn)擊這個按鈕,那么用戶點(diǎn)擊這個按鈕之后,會發(fā)生:
按鈕狀態(tài)變?yōu)?pending --> 獲取成功,按鈕狀態(tài)變成 success | |--> 獲取失敗,按鈕狀態(tài)變成 error
這里改變success/error狀態(tài)的并不是用戶輸入,而是服務(wù)端的返回,這個時候,就需要在 controller里面 handle 服務(wù)端的返回。這只是個簡單的例子,如果類似的情況發(fā)生了很多之后,每次輸入和輸出將變得難以預(yù)測,難以預(yù)測的后果就是很容易出現(xiàn) bug,程序的健壯性下降。
讓每一步輸入和輸出可預(yù)測,可預(yù)測才能可測試,可測試才能保證健壯性。
2.2 React 和 Flux于是,這個時候出現(xiàn)了React和Flux。
Flux的核心思想就是維護(hù)一個單向數(shù)據(jù)流,數(shù)據(jù)的流向永遠(yuǎn)是單向的,所以每個步驟便是可預(yù)測的,程序的健壯性得到了保證。
React的 jsx 可以將前端的 UI 部分變成了一層層套用的方法,再舉個例子,之前寫 html 是這樣的
foo
如果狀態(tài)改變之后,大部分情況下我們是將某個片段的 html 用改變的狀態(tài)重新拼一遍,然后替換到原有的 dom 結(jié)構(gòu)里。
但是,用了 jsx 之后,你的代碼將變成這樣:
div(span("foo"))
變成了一個函數(shù),這個函數(shù)的輸出就是上面的那段 html,所以整個 UI 變成了一個可輸入輸出的結(jié)構(gòu),有了輸入和輸出,就是一個完整的可預(yù)測的結(jié)構(gòu)了,可預(yù)測,也就是代表可測試了。
2.3 使用 Redux在使用Flux的過程里,當(dāng)應(yīng)用的結(jié)構(gòu)變得復(fù)雜之后,會顯得力不從心,雖然數(shù)據(jù)流還是單向,但是Flux的整體流程有兩個比較關(guān)鍵的點(diǎn):
store 更新完數(shù)據(jù)之后,需要emit
component 中需要 handle emit
當(dāng)數(shù)據(jù)結(jié)構(gòu)和輸入輸出變得復(fù)雜的時候,往往會定義很多個 store,但是往往 store 之間還是會有依賴和關(guān)聯(lián)。
這個時候,handle 的過程會變得很臃腫,難以理解。
然后,Redux就出場了。
Flux的思路可以理解為多個store組成了一個完整的 App;Redux的思路則是一個完整的store對應(yīng)一個完整的 App。
Redux相比Flux,多抽象出了一個reducer的概念。這個reducer只負(fù)責(zé)狀態(tài)的更新,并且會返回一個新的狀態(tài)對象,整個 App 從結(jié)構(gòu)上看起來,沒有一個一直保存/更新的狀態(tài)(使用Flux每個store都是一直保存住的,然后在此基礎(chǔ)上進(jìn)行更新),Redux中的數(shù)據(jù)更像是一個流程。
另外,還有一點(diǎn)比較重要的是,因?yàn)闆]有了一個一直保存/更新的狀態(tài)對象,所以在 component 中的 handle 也就沒有意義了,通過react-redux可以完全實(shí)現(xiàn)一個順暢的數(shù)據(jù)流。
這里舉個簡單的例子,如果我們更新一個訂單,訂單里有這么幾項(xiàng):
地址
運(yùn)費(fèi)
商品數(shù)量
總價
其中地址影響運(yùn)費(fèi),運(yùn)費(fèi)影響總價;另外,商品數(shù)量也會影響總價
使用Flux的話,我們通常會分解成這樣幾個store:
address
items
deal
其中 address和items的更新會觸發(fā)deal.amount的更新,完整的交易信息會同步到deal中。
在component里,我們會handel所有這些store的emit,然后再進(jìn)行setState以更新 UI 部分。
使用Redux的話,我們會分解成這樣幾個reducer:
address
items
deal
其中address只負(fù)責(zé)address的更新,item只負(fù)責(zé)items的更新,deal會響應(yīng)address和item中跟交易相關(guān)的更新,實(shí)現(xiàn)改變價格和訂單地址的操作。
但是并不需要在component中再hanle每個部分更新之后的emit。數(shù)據(jù)更新了,頁面就會自己變化。
接下來,我們看看Redux是如何實(shí)現(xiàn)的。
3 實(shí)現(xiàn)原理查看Redux的github,會發(fā)現(xiàn)Redux的代碼異常的精簡,僅僅包含了這幾個部分:
utils/
applyMiddlewares.js
bindActionCreators.js
combineReducers.js
compose.js
createStore.js
index
其中的utils/和index.js我們并不需要關(guān)心,只要看接下來的幾部分就可以。
另外,因?yàn)槲覀兊拇蟛糠謭鼍斑€是搭配React來使用Redux,所以這里我們順便搭配 react-redux來看下
react-redux/src
在react-redux中,我們關(guān)心的更少,只有:
Provider.js
connect.js
這兩部分而已。
3.1 一個真實(shí)世界中的例子拿一個真正的實(shí)例來看,我們要做一個簡單的訂單,目錄結(jié)構(gòu)是這樣的:
|- dealReducer.js |- dealActions.js |- dealStore.js |- dealApp.js |- main.js3.1.1 main.js
先看代碼:
import React from "react" import ReactDom from "react-dom" import { Provider } from "react-redux" import configureStore from "./dealStore" import DealApp from "./dealApp" let store = configureStore() ReactDom.render( (), document.getElementById("app"))
這個部分比較簡單,首先是調(diào)用了dealStore中的方法,生成了一個store,然后調(diào)用了react-redux中的Provider把這個store綁定到了Provider上。
我們先看 Provider 的代碼:
3.1.2 react-redux.provider完整代碼看這里
我們只看下核心的部分:
export default class Provider extends Component { getChildContext() { return { store: this.store } } constructor(props, context) { super(props, context) this.store = props.store } render() { return Children.only(this.props.children) } }
其實(shí)最核心就是getChildContext方法,這個方法在每次props和state被調(diào)用時會被觸發(fā),這里更新了store
3.1.3 dealApp.js還是先看代碼:
import React, { Component, PropTypes } from "react" import { bindActionCreators } from "redux" import { connect } from "react-redux" import * as dealActions from "deal/actions/dealActions" import * as addressActions from "deal/actions/addressActions" class DealApp extends Component { // some code } function mapStateToProps(state) { return { "deal": state.dealReducer, "address": state.addressReducer, } } function mapDispatchToProps(dispatch) { return { "dealActions": bindActionCreators(dealActions, dispatch), "addressActions": bindActionCreators(addressActions, dispatch), } } export default connect(mapStateToProps, mapDispatchToProps)(DealApp)
從代碼可以看到,比一般的 react component 多了對connect的調(diào)用,以及mapStateToProps和mapDispatchToProps兩個方法。
所以,接下來看下這個connect是什么
3.1.3 react-redux.connect完整代碼見
來看下核心部分的代碼:
// some code componentDidMount() { this.trySubscribe() }, trySubscribe() { if (shouldSubscribe && !this.unsubscribe) { this.unsubscribe = this.store.subscribe(this.handleChange.bind(this)) this.handleChange() } }, handleChange() { if (!this.unsubscribe) { return } const storeState = this.store.getState() const prevStoreState = this.state.storeState if (pure && prevStoreState === storeState) { return } if (pure && !this.doStatePropsDependOnOwnProps) { const haveStatePropsChanged = tryCatch(this.updateStatePropsIfNeeded, this) if (!haveStatePropsChanged) { return } if (haveStatePropsChanged === errorObject) { this.statePropsPrecalculationError = errorObject.value } this.haveStatePropsBeenPrecalculated = true } this.hasStoreStateChanged = true this.setState({ storeState }) }
可以看到,這幾個方法用到了store中的getState和subscribe這幾個方法。并且在handleChange中,實(shí)現(xiàn)了在Flux中需要人肉實(shí)現(xiàn)的setState方法。
3.1.4 dealStore.js既然在上面的connect中,用到了store,那么就來看看dealStore的內(nèi)容:
import { createStore, applyMiddleware, compose } from "redux" import thunk from "redux-thunk" import dealReducers from "deal/reducers/dealReducer" let creator = compose( applyMiddleware(thunk), applyMiddleware(address), )(createStore) export default function configureStore(initState) { const store = creator(dealReducers, initState) return store }
這個文件里用到了redux中的createStore , compose和applyMiddleware方法。
通過調(diào)用可以看到,先是通過applyMiddleware方法調(diào)用了一些middleware,然后再用compose將對middleware的調(diào)用串聯(lián)起來,返回一個方法,先簡單列為f(createStore),然后這個調(diào)用再次返回了一個方法,這里被定義為creator。通過調(diào)用creator方法,最終生成了 store。
下面逐個看一下createStore,compose,applyMiddleware這幾個方法。
3.1.5 applyMiddleware直接看源碼:
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
這里直接返回了一個接收createStore作為參數(shù)的方法,這個方法中會遍歷傳入的middleware,并使用compose 調(diào)用store.dispatch,接下來看一下compose方法的具體實(shí)現(xiàn)。
3.1.6 compose還是直接貼源碼:
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) }
可以看到 compose的源碼十分精簡,整個compose的作用就是傳入一串funcs,然后返回一個方法,先暫定這個方法名為c,c將傳入的funcs按照從右到左的順序,逐個執(zhí)行c傳入的參數(shù)。
為什么要按照從右到左的順序執(zhí)行,我們先按下不表,接下來看 createStore 的源碼。
3.1.7 createStorecreateStore的源碼比較長,這里就不貼了,詳情可以見這里。
我們這里只看下這個方法的輸入和輸出既可:
export default function createStore(reducer, preloadedState, enhancer) { // code return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
輸入有三個,reducer和preloadState我們都屬性,但是這個enhancer是什么呢?
再來看下相關(guān)代碼:
if (typeof enhancer !== "undefined") { if (typeof enhancer !== "function") { throw new Error("Expected the enhancer to be a function.") } return enhancer(createStore)(reducer, preloadedState) }
enhancer可以當(dāng)做是預(yù)先設(shè)定的,對createStore返回對象執(zhí)行的方法,比如可以給返回的對象添加一些新的屬性或者方法之類的操作,就可以放到enhancer中做。
看到這里,我們再來看下compose中為什么調(diào)用reducerRight,將方法從右至左執(zhí)行。
首先,是applyMiddleware方法獲取到傳入的createStore,返回了:
{ ...store, dispatch }
但是這里的dispatch已經(jīng)不是creatStore中返回的store.dispatch了。這個dispatch是通過調(diào)用compose將store.dispatch傳入middlewares中執(zhí)行的結(jié)果。
再回到主線上來,applyMiddleware返回了一個增強(qiáng)的store,如果有多個applyMiddleware的調(diào)用,如下所示:
compose( applyMiddleware(A), applyMiddleware(B), applyMiddleware(C) )
我們的期望的執(zhí)行順序當(dāng)然是A,B,C這樣,所以轉(zhuǎn)換成方法的話,應(yīng)該是這樣
C(B(A()))
使用reducerRight的話,最先被調(diào)用的方法(也就是上面的C)就會是執(zhí)行鏈的最外層的方法,所以要按照從右到左的順序執(zhí)行。
至此,Redux的介紹就先到這里,之后會再寫一些關(guān)于Redux周邊組件的使用。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/86724.html
摘要:正則學(xué)起來說真的,不去正兒八經(jīng)的學(xué)正則,對付一般的工作是沒啥問題的,雖然我們可能會經(jīng)常用到,但畢竟度娘能提供大多時候你想要的可當(dāng)我看一些框架的源碼,總會被里面一長串一長串的正則給嚇到之前一篇博客里有關(guān)于簡單的爬蟲實(shí)踐,其實(shí)離達(dá)到我預(yù)期的效果 正則學(xué)起來 說真的,不去正兒八經(jīng)的學(xué)正則,對付一般的工作是沒啥問題的,雖然我們可能會經(jīng)常用到replace,但畢竟度娘能提供大多時候你想要的;可當(dāng)...
摘要:走近可以膚淺地理解成為靈活的數(shù)組,我們在定義數(shù)組的時候,是要確定數(shù)組的大小的。在內(nèi)部,向量使用一個動態(tài)分配的數(shù)組來存儲它們的元素。當(dāng)插入新元素時,為了增加數(shù)組的大小,可能需要重新分配數(shù)組,這意味著分配一個新數(shù)組并將所有元素移動到該數(shù)組中。 ...
摘要:作為一名前端開發(fā)者,也了解中的很多特性借鑒自比如默認(rèn)參數(shù)解構(gòu)賦值等,同時本文會對的一些用法與進(jìn)行類比。函數(shù)接收一個函數(shù)和一個,這個函數(shù)的作用是對每個元素進(jìn)行判斷,返回或,根據(jù)判斷結(jié)果自動過濾掉不符合條件的元素,返回由符合條件元素組成的新。 showImg(https://segmentfault.com/img/remote/1460000011857550); 本文首發(fā)在 個人博客 ...
摘要:網(wǎng)絡(luò)黑白一書所抄襲的文章列表這本書實(shí)在是垃圾,一是因?yàn)樗幕ヂ?lián)網(wǎng)上的文章拼湊而成的,二是因?yàn)槠礈愃教?,連表述都一模一樣,還抄得前言不搭后語,三是因?yàn)閮?nèi)容全都是大量的科普,不涉及技術(shù)也沒有干貨。 《網(wǎng)絡(luò)黑白》一書所抄襲的文章列表 這本書實(shí)在是垃圾,一是因?yàn)樗幕ヂ?lián)網(wǎng)上的文章拼湊而成的,二是因?yàn)槠礈愃教?,連表述都一模一樣,還抄得前言不搭后語,三是因?yàn)閮?nèi)容全都是大量的科普,不涉及技術(shù)...
摘要:對的描述如下將會給數(shù)組里的每一個元素執(zhí)行一遍回調(diào)函數(shù),直到回調(diào)函數(shù)返回。的運(yùn)行原理和類似,但回調(diào)函數(shù)是返回而不是?;卣{(diào)函數(shù)只會對已經(jīng)指定值的數(shù)組項(xiàng)調(diào)用。 在JavaScript中,創(chuàng)建數(shù)組可以使用Array構(gòu)造函數(shù),或者使用數(shù)組直接量[],后者是首選方法。Array對象繼承自O(shè)bject.prototype,對數(shù)組執(zhí)行typeof操作符返回object而不是array。然而,[] in...
閱讀 2654·2021-11-11 16:55
閱讀 692·2021-09-04 16:40
閱讀 3091·2019-08-30 15:54
閱讀 2631·2019-08-30 15:54
閱讀 2417·2019-08-30 15:46
閱讀 413·2019-08-30 15:43
閱讀 3240·2019-08-30 11:11
閱讀 2993·2019-08-28 18:17