成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

走近 Redux

fevin / 3223人閱讀

摘要:的核心思想就是維護(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)了ReactFlux。

Flux的核心思想就是維護(hù)一個單向數(shù)據(jù)流,數(shù)據(jù)的流向永遠(yuǎn)是單向的,所以每個步驟便是可預(yù)測的,程序的健壯性得到了保證。

Reactjsx 可以將前端的 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

其中 addressitems的更新會觸發(fā)deal.amount的更新,完整的交易信息會同步到deal中。

component里,我們會handel所有這些storeemit,然后再進(jìn)行setState以更新 UI 部分。

使用Redux的話,我們會分解成這樣幾個reducer:

address

items

deal

其中address只負(fù)責(zé)address的更新,item只負(fù)責(zé)items的更新,deal會響應(yīng)addressitem中跟交易相關(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.js
3.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方法,這個方法在每次propsstate被調(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)用,以及mapStateToPropsmapDispatchToProps兩個方法。

所以,接下來看下這個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中的getStatesubscribe這幾個方法。并且在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 , composeapplyMiddleware方法。
通過調(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 createStore

createStore的源碼比較長,這里就不貼了,詳情可以見這里。

我們這里只看下這個方法的輸入和輸出既可:

export default function createStore(reducer, preloadedState, enhancer) {

    // code
    
    return {
           dispatch,
           subscribe,
           getState,
           replaceReducer,
          [$$observable]: observable
  }
}

輸入有三個,reducerpreloadState我們都屬性,但是這個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)用composestore.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

相關(guān)文章

  • 走近正則:仿Nodejs的Url模塊到前端

    摘要:正則學(xué)起來說真的,不去正兒八經(jīng)的學(xué)正則,對付一般的工作是沒啥問題的,雖然我們可能會經(jīng)常用到,但畢竟度娘能提供大多時候你想要的可當(dāng)我看一些框架的源碼,總會被里面一長串一長串的正則給嚇到之前一篇博客里有關(guān)于簡單的爬蟲實(shí)踐,其實(shí)離達(dá)到我預(yù)期的效果 正則學(xué)起來 說真的,不去正兒八經(jīng)的學(xué)正則,對付一般的工作是沒啥問題的,雖然我們可能會經(jīng)常用到replace,但畢竟度娘能提供大多時候你想要的;可當(dāng)...

    HitenDev 評論0 收藏0
  • C++之vector:靈活的數(shù)組

    摘要:走近可以膚淺地理解成為靈活的數(shù)組,我們在定義數(shù)組的時候,是要確定數(shù)組的大小的。在內(nèi)部,向量使用一個動態(tài)分配的數(shù)組來存儲它們的元素。當(dāng)插入新元素時,為了增加數(shù)組的大小,可能需要重新分配數(shù)組,這意味著分配一個新數(shù)組并將所有元素移動到該數(shù)組中。 ...

    mj 評論0 收藏0
  • 走近 Python (類比 JS)

    摘要:作為一名前端開發(fā)者,也了解中的很多特性借鑒自比如默認(rèn)參數(shù)解構(gòu)賦值等,同時本文會對的一些用法與進(jìn)行類比。函數(shù)接收一個函數(shù)和一個,這個函數(shù)的作用是對每個元素進(jìn)行判斷,返回或,根據(jù)判斷結(jié)果自動過濾掉不符合條件的元素,返回由符合條件元素組成的新。 showImg(https://segmentfault.com/img/remote/1460000011857550); 本文首發(fā)在 個人博客 ...

    shadajin 評論0 收藏0
  • 《網(wǎng)絡(luò)黑白》一書所抄襲的文章列表

    摘要:網(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ù)...

    zlyBear 評論0 收藏0
  • 有趣的JavaScript原生數(shù)組函數(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...

    mo0n1andin 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<