摘要:的核心思想就是維護一個單向數據流,數據的流向永遠是單向的,所以每個步驟便是可預測的,程序的健壯性得到了保證。另外,還有一點比較重要的是,因為沒有了一個一直保存更新的狀態對象,所以在中的也就沒有意義了,通過可以完全實現一個順暢的數據流。
1 Redux
Redux is a predictable state container for JavaScript apps
簡單來說,Redux是一個管理應用狀態的框架
2 解決的問題 2.1 前端開發中的普遍問題前端開發中,本質的問題就是將 server -> client 的輸入,變成 client -> user 輸入;再將 user -> client 的輸入,變成 client -> server 的輸入。
在 client 中,前端的角色其實大概可以當做一個"轉換器"。
舉個簡單的例子,后端傳過來的是一個 json 格式的數據,這個 json 格式,其實是在計算機范疇內的,真正的終端用戶并不知道什么是json,更不知道要如何修改json,保存自己的信息。所以,這個時候就需要像上面說的把 json 轉換為頁面上的內容和元素;另外,隨著用戶的一系列操作,數據需要隨時更新保存到服務端。整個的這個過程,可能會很復雜,數據和數據之間會存在聯動關系。
這個時候,就需要有一個東西,從更高的層面來管理所有的這些狀態,于是有了 mvc,狀態保存在model,數據展示在view,controller來串聯用戶的輸入和數據的更新。但是這個時候就會有個問題,理想情況下,我們默認所有的狀態更新都是由用戶的操作(也可以理解為用戶的輸入)來觸發的,但實際情況中,會觸發狀態更新的不僅僅是單純的用戶操作,還有可能是用戶操作帶來的后果,在舉個例子:
頁面上有個異步獲取信息的按鈕,用戶可以點擊這個按鈕,那么用戶點擊這個按鈕之后,會發生:
按鈕狀態變為 pending --> 獲取成功,按鈕狀態變成 success | |--> 獲取失敗,按鈕狀態變成 error
這里改變success/error狀態的并不是用戶輸入,而是服務端的返回,這個時候,就需要在 controller里面 handle 服務端的返回。這只是個簡單的例子,如果類似的情況發生了很多之后,每次輸入和輸出將變得難以預測,難以預測的后果就是很容易出現 bug,程序的健壯性下降。
讓每一步輸入和輸出可預測,可預測才能可測試,可測試才能保證健壯性。
2.2 React 和 Flux于是,這個時候出現了React和Flux。
Flux的核心思想就是維護一個單向數據流,數據的流向永遠是單向的,所以每個步驟便是可預測的,程序的健壯性得到了保證。
React的 jsx 可以將前端的 UI 部分變成了一層層套用的方法,再舉個例子,之前寫 html 是這樣的
foo
如果狀態改變之后,大部分情況下我們是將某個片段的 html 用改變的狀態重新拼一遍,然后替換到原有的 dom 結構里。
但是,用了 jsx 之后,你的代碼將變成這樣:
div(span("foo"))
變成了一個函數,這個函數的輸出就是上面的那段 html,所以整個 UI 變成了一個可輸入輸出的結構,有了輸入和輸出,就是一個完整的可預測的結構了,可預測,也就是代表可測試了。
2.3 使用 Redux在使用Flux的過程里,當應用的結構變得復雜之后,會顯得力不從心,雖然數據流還是單向,但是Flux的整體流程有兩個比較關鍵的點:
store 更新完數據之后,需要emit
component 中需要 handle emit
當數據結構和輸入輸出變得復雜的時候,往往會定義很多個 store,但是往往 store 之間還是會有依賴和關聯。
這個時候,handle 的過程會變得很臃腫,難以理解。
然后,Redux就出場了。
Flux的思路可以理解為多個store組成了一個完整的 App;Redux的思路則是一個完整的store對應一個完整的 App。
Redux相比Flux,多抽象出了一個reducer的概念。這個reducer只負責狀態的更新,并且會返回一個新的狀態對象,整個 App 從結構上看起來,沒有一個一直保存/更新的狀態(使用Flux每個store都是一直保存住的,然后在此基礎上進行更新),Redux中的數據更像是一個流程。
另外,還有一點比較重要的是,因為沒有了一個一直保存/更新的狀態對象,所以在 component 中的 handle 也就沒有意義了,通過react-redux可以完全實現一個順暢的數據流。
這里舉個簡單的例子,如果我們更新一個訂單,訂單里有這么幾項:
地址
運費
商品數量
總價
其中地址影響運費,運費影響總價;另外,商品數量也會影響總價
使用Flux的話,我們通常會分解成這樣幾個store:
address
items
deal
其中 address和items的更新會觸發deal.amount的更新,完整的交易信息會同步到deal中。
在component里,我們會handel所有這些store的emit,然后再進行setState以更新 UI 部分。
使用Redux的話,我們會分解成這樣幾個reducer:
address
items
deal
其中address只負責address的更新,item只負責items的更新,deal會響應address和item中跟交易相關的更新,實現改變價格和訂單地址的操作。
但是并不需要在component中再hanle每個部分更新之后的emit。數據更新了,頁面就會自己變化。
接下來,我們看看Redux是如何實現的。
3 實現原理查看Redux的github,會發現Redux的代碼異常的精簡,僅僅包含了這幾個部分:
utils/
applyMiddlewares.js
bindActionCreators.js
combineReducers.js
compose.js
createStore.js
index
其中的utils/和index.js我們并不需要關心,只要看接下來的幾部分就可以。
另外,因為我們的大部分場景還是搭配React來使用Redux,所以這里我們順便搭配 react-redux來看下
react-redux/src
在react-redux中,我們關心的更少,只有:
Provider.js
connect.js
這兩部分而已。
3.1 一個真實世界中的例子拿一個真正的實例來看,我們要做一個簡單的訂單,目錄結構是這樣的:
|- 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"))
這個部分比較簡單,首先是調用了dealStore中的方法,生成了一個store,然后調用了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) } }
其實最核心就是getChildContext方法,這個方法在每次props和state被調用時會被觸發,這里更新了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的調用,以及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中,實現了在Flux中需要人肉實現的setState方法。
3.1.4 dealStore.js既然在上面的connect中,用到了store,那么就來看看dealStore的內容:
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方法。
通過調用可以看到,先是通過applyMiddleware方法調用了一些middleware,然后再用compose將對middleware的調用串聯起來,返回一個方法,先簡單列為f(createStore),然后這個調用再次返回了一個方法,這里被定義為creator。通過調用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作為參數的方法,這個方法中會遍歷傳入的middleware,并使用compose 調用store.dispatch,接下來看一下compose方法的具體實現。
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按照從右到左的順序,逐個執行c傳入的參數。
為什么要按照從右到左的順序執行,我們先按下不表,接下來看 createStore 的源碼。
3.1.7 createStorecreateStore的源碼比較長,這里就不貼了,詳情可以見這里。
我們這里只看下這個方法的輸入和輸出既可:
export default function createStore(reducer, preloadedState, enhancer) { // code return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
輸入有三個,reducer和preloadState我們都屬性,但是這個enhancer是什么呢?
再來看下相關代碼:
if (typeof enhancer !== "undefined") { if (typeof enhancer !== "function") { throw new Error("Expected the enhancer to be a function.") } return enhancer(createStore)(reducer, preloadedState) }
enhancer可以當做是預先設定的,對createStore返回對象執行的方法,比如可以給返回的對象添加一些新的屬性或者方法之類的操作,就可以放到enhancer中做。
看到這里,我們再來看下compose中為什么調用reducerRight,將方法從右至左執行。
首先,是applyMiddleware方法獲取到傳入的createStore,返回了:
{ ...store, dispatch }
但是這里的dispatch已經不是creatStore中返回的store.dispatch了。這個dispatch是通過調用compose將store.dispatch傳入middlewares中執行的結果。
再回到主線上來,applyMiddleware返回了一個增強的store,如果有多個applyMiddleware的調用,如下所示:
compose( applyMiddleware(A), applyMiddleware(B), applyMiddleware(C) )
我們的期望的執行順序當然是A,B,C這樣,所以轉換成方法的話,應該是這樣
C(B(A()))
使用reducerRight的話,最先被調用的方法(也就是上面的C)就會是執行鏈的最外層的方法,所以要按照從右到左的順序執行。
至此,Redux的介紹就先到這里,之后會再寫一些關于Redux周邊組件的使用。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/86724.html
摘要:正則學起來說真的,不去正兒八經的學正則,對付一般的工作是沒啥問題的,雖然我們可能會經常用到,但畢竟度娘能提供大多時候你想要的可當我看一些框架的源碼,總會被里面一長串一長串的正則給嚇到之前一篇博客里有關于簡單的爬蟲實踐,其實離達到我預期的效果 正則學起來 說真的,不去正兒八經的學正則,對付一般的工作是沒啥問題的,雖然我們可能會經常用到replace,但畢竟度娘能提供大多時候你想要的;可當...
摘要:走近可以膚淺地理解成為靈活的數組,我們在定義數組的時候,是要確定數組的大小的。在內部,向量使用一個動態分配的數組來存儲它們的元素。當插入新元素時,為了增加數組的大小,可能需要重新分配數組,這意味著分配一個新數組并將所有元素移動到該數組中。 ...
摘要:作為一名前端開發者,也了解中的很多特性借鑒自比如默認參數解構賦值等,同時本文會對的一些用法與進行類比。函數接收一個函數和一個,這個函數的作用是對每個元素進行判斷,返回或,根據判斷結果自動過濾掉不符合條件的元素,返回由符合條件元素組成的新。 showImg(https://segmentfault.com/img/remote/1460000011857550); 本文首發在 個人博客 ...
摘要:網絡黑白一書所抄襲的文章列表這本書實在是垃圾,一是因為它的互聯網上的文章拼湊而成的,二是因為拼湊水平太差,連表述都一模一樣,還抄得前言不搭后語,三是因為內容全都是大量的科普,不涉及技術也沒有干貨。 《網絡黑白》一書所抄襲的文章列表 這本書實在是垃圾,一是因為它的互聯網上的文章拼湊而成的,二是因為拼湊水平太差,連表述都一模一樣,還抄得前言不搭后語,三是因為內容全都是大量的科普,不涉及技術...
摘要:對的描述如下將會給數組里的每一個元素執行一遍回調函數,直到回調函數返回。的運行原理和類似,但回調函數是返回而不是?;卣{函數只會對已經指定值的數組項調用。 在JavaScript中,創建數組可以使用Array構造函數,或者使用數組直接量[],后者是首選方法。Array對象繼承自Object.prototype,對數組執行typeof操作符返回object而不是array。然而,[] in...
閱讀 2643·2021-11-11 16:55
閱讀 680·2021-09-04 16:40
閱讀 3078·2019-08-30 15:54
閱讀 2615·2019-08-30 15:54
閱讀 2403·2019-08-30 15:46
閱讀 404·2019-08-30 15:43
閱讀 3227·2019-08-30 11:11
閱讀 2983·2019-08-28 18:17