走近 Redux

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,数据展现在viewcontroller来串连用户的输入和数据的更新。然则这个时刻就会有个题目,抱负状况下,我们默许一切的状况更新都是由用户的操纵(也能够明白为用户的输入)来触发的,但实际状况中,会触发状况更新的不仅仅是纯真的用户操纵,另有多是用户操纵带来的效果,在举个例子:

页面上有个异步猎取信息的按钮,用户能够点击这个按钮,那末用户点击这个按钮以后,会发作:

按钮状况变成 pending --> 猎取胜利,按钮状况变成 success
                   |
                   |--> 猎取失利,按钮状况变成 error

这里转变success/error状况的并非用户输入,而是服务端的返回,这个时刻,就须要在 controller内里 handle 服务端的返回。这只是个简朴的例子,假如相似的状况发作了许多以后,每次输入和输出将变得难以展望,难以展望的效果就是很轻易涌现 bug,递次的健壮性下落。

让每一步输入和输出可展望,可展望才可测试,可测试才保证健壮性。

2.2 React 和 Flux

因而,这个时刻涌现了ReactFlux

Flux的中心头脑就是保护一个单向数据流,数据的流向永远是单向的,所以每一个步骤就是可展望的,递次的健壮性得到了保证。

Reactjsx 能够将前端的 UI 部份变成了一层层套用的要领,再举个例子,之前写 html 是如许的

<div>
    <span>foo</span>
</div>

假如状况转变以后,大部份状况下我们是将某个片断的 html 用转变的状况从新拼一遍,然后替换到原有的 dom 构造里。

然则,用了 jsx 以后,你的代码将变成如许:

div(span('foo'))

变成了一个函数,这个函数的输出就是上面的那段 html,所以全部 UI 变成了一个可输入输出的构造,有了输入和输出,就是一个完整的可展望的构造了,可展望,也就是代表可测试了。

2.3 运用 Redux

在运用Flux的历程里,当运用的构造变得庞杂以后,会显得力不从心,虽然数据流照样单向,然则Flux的团体流程有两个比较症结的点:

  1. store 更新完数据以后,须要emit
  2. 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

个中 addressitems的更新会触发deal.amount的更新,完整的生意业务信息会同步到deal中。

component里,我们会handel一切这些storeemit,然后再举行setState以更新 UI 部份。

运用Redux的话,我们会分解成如许几个reducer:

  • address
  • items
  • deal

个中address只担任address的更新,item只担任items的更新,deal会相应addressitem中跟生意业务相干的更新,完成转变价钱和定单地点的操纵。

然则并不须要在component中再hanle每一个部份更新以后的emit。数据更新了,页面就会本身变化。

接下来,我们看看Redux是怎样完成的。

3 完成道理

检察Reduxgithub,会发明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.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(
  (
    <Provider store={ store }>
      <DealApp  />
    </Provider>
  ), 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要领,这个要领在每次propsstate被挪用时会被触发,这里更新了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的挪用,以及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中,完成了在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 , composeapplyMiddleware要领。
经由过程挪用能够看到,先是经由过程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,然后返回一个要领,先暂定这个要领名为cc将传入的funcs根据从右到左的递次,逐一实行c传入的参数。

为何要根据从右到左的递次实行,我们先按下不表,接下来看 createStore 的源码。

3.1.7 createStore

createStore的源码比较长,这里就不贴了,概况能够见这里

我们这里只看下这个要领的输入和输出既可:

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

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

输入有三个,reducerpreloadState我们都属性,然则这个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是经由过程挪用composestore.dispatch传入middlewares中实行的效果。

再回到主线上来,applyMiddleware返回了一个加强store,假如有多个applyMiddleware的挪用,以下所示:

compose(
    applyMiddleware(A),
    applyMiddleware(B),
    applyMiddleware(C)
)

我们的希冀的实行递次当然是A,B,C如许,所以转换成要领的话,应该是如许

C(B(A()))

运用reducerRight的话,最早被挪用的要领(也就是上面的C)就会是实行链的最外层的要领,所以要根据从右到左的递次实行。

至此,Redux的引见就先到这里,以后会再写一些关于Redux周边组件的运用。

    原文作者:tobeyouth
    原文地址: https://segmentfault.com/a/1190000007966031
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞