JoeBBChen / Blog

:kissing_heart: Coding
0 stars 0 forks source link

Redux 小结 #1

Open JoeBBChen opened 6 years ago

JoeBBChen commented 6 years ago

1、middleware

middleware 的函数签名是 ({ getState, dispatch }) => next => action

函数签名:(或者类型签名,抑或方法签名)定义了 函数或方法的输入与输出。

问题

dispatch(actions.getOrderList(tab));

dispatch 的参数是什么?

dispatch 源码:

function dispatch(action) {
    // 这里要求我们的 action 是一个纯对象
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }
    // 略
  }

那么这个是怎么实现的呢?,其实是用到中间件 redux-thunk ,文章主要讲 Redux middleware 的实现,顺带提到解释文章开头的问题。

例子

假设现在有两个 middleware:

const log = ({getState, dispatch}) => (next) => (action) => {
    console.log('action: ', action);
    return next(action);
}

// 这里跟 `redux-thunk` 源码雷同
const thunk = ({getState, dispatch}) => (next) => (action) => {
    if( typeof action === 'function') {
        return action(dispatch, getState);
    }

    return next(action);
}

中间件的调用如下:

const store = createStore(rootReducer,applyMiddleware(thunk, log));

rootReducer 就是我们全局的 reducer。接着看 applyMiddleware 的源码。

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))

    return {
      ...store,
      dispatch
    }
  }
}

还有 createStore 的源码

export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }
// 略

经过转换,enhancer 就是 applyMiddleware。接下来看 enhancer(createStore)(reducer, preloadedState)

得到以下:

const enhancer = (createStore) => (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))

    return {
      ...store,
      dispatch
    }
  }
const store = createStore(rootReducer,enhancer);
=> const store = enhancer(createStore)(reducer, preloadedState);

这里有两个步骤:

const store_1 = enhancer(createStore);
const store_2 = store_1(reducer, preloadedState);

store_1得到:

const store = (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))

    return {
      ...store,
      dispatch
    }

store_2即:

const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []

const middlewareAPI = { 
  getState: store.getState,
  dispatch: (action) => dispatch(action)
}

//middlewares = [thunk, log]
chain = middlewares.map(middleware => middleware(middlewareAPI))

// 执行上面那句,得到
chain = [(next) => (action) => {
    console.log('action: ', action);
    return next(action);
}, (next) => (action) => {
    if( typeof action === 'function') {
        action(dispatch, getState);
    }

    return next(action);
}]

dispatch = compose(...chain)(store.dispatch) // #1

return {
  ...store,
  dispatch
}

接着看 compose

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

reduce api 自己查作用。

// 假设
func = [a, b, c];

funcs.reduce((a, b) => (...args) => a(b(...args)))

=> (...args) => a(b(c(...args)));

//所以两个中间件得到
thunk(log(args))

//compose return
return (...args) => thunk(log(args));

所以 #1 得到:


dispatch = ((...args) => thunk(log(args)))(store.dispatch)

dispatch = thunk(log(store.dispatch))

/*
log and thunk
[(next) => (action) => {
    console.log('action: ', action);
    return next(action);
}, (next) => (action) => {
    if( typeof action === 'function') {
        action(dispatch, getState);
    }

    return next(action);
}]
*/

dispatch = (action) => {
    if( typeof action === 'function') {
        action(dispatch, getState);
    }

    return ((action) => {
            console.log('action: ', action);
            return store.dispatch(action);
        })(action);
}

所以中间件的链式调用主要是通过 next 实现的。当一个中间件执行完成后,调用下一个中间件,即 next,在例子thunk(log(store.dispatch)) 中,thunk 的 next 就是 log,log 的 next 就是 store.dispatch,在 compose 已经完成 next 参数的传值,最后调用 dispatch 传进的参数为 action。

2、react-redux

React 和 Redux,一个是负责 UI,一个是负责数据管理,其实两者并没有关联,它们之间的关联是使用了 react-redux。

react-redux 最核心的两个是 Providerconnect

先讲一下 React 组件通信的方式:

  1. 父子组件通信:父组件通过 props 传递数据给子组件
  2. 子父通信: 父组件通过 props 传递事件给子组件,子组件在相应的地方调用该事件,将通信的数据通过函数参数传回到父组件函数中
  3. 观察者模式:通过在 componentDidMount 注册事件,在 componentWillUnmount 解绑事件,可在别的组件中发布该事件
  4. context 上下文

栗子:

//父组件:
class Menu extends React.Component{
  getChildContext(){
     return {name: 'bb'};
  }
}
Menu.childContextTypes = {
  name: Protypes.string
}

//子子子组件:
Child.contextTypes = {
  name: Protypes.string
}
this.context.name 可访问父组件的 context

第四种方式就是 connect 获取全局 store 的方式。

Provider

Provider 用于包含最顶层的入口组件,例如:

const Root = (
    <Provider store={store}>
        <App/>
    </Provider>
);
ReactDOM.render(Root, window.document.getElementById("app"));

store 是通过 createStore 创建的。 Providor 源码:

export function createProvider(storeKey = 'store', subKey) {
    const subscriptionKey = subKey || `${storeKey}Subscription`

    class Provider extends Component {
        constructor(props, context) {
          super(props, context)
          this[storeKey] = props.store;
        }

        // 定义子组件能读取的属性
        getChildContext() {
          return { [storeKey]: this[storeKey], [subscriptionKey]: null }
        }

        render() {
          return Children.only(this.props.children)
        }
    }

    if (process.env.NODE_ENV !== 'production') {
      Provider.prototype.componentWillReceiveProps = function (nextProps) {
        if (this[storeKey] !== nextProps.store) {
          warnAboutReceivingStore()
        }
      }
    }

    Provider.propTypes = {
        store: storeShape.isRequired,
        children: PropTypes.element.isRequired,
    }

    // 定义子组件读取的属性的类型
    Provider.childContextTypes = {
        [storeKey]: storeShape.isRequired,
        [subscriptionKey]: subscriptionShape,
    }

    return Provider
}

export default createProvider()

Provider 模块的功能很简单,从最外部封装了整个应用,并向 connect 模块传递 store

那么真正连接 React 和 Redux 的是 connect

connect

Redux 的运作是这样的,首先创建一个全局的 store 用于维护 state,如果要改变 state,则通过 dispatch 一个 action,reducer 通过相应的 action 去更新 state。

class Connect extends Component {
    constructor(props, context) {
        super(props, context)

        this.version = version
        this.state = {}
        this.renderCount = 0

        // 这里便是获取到 Provider 中的 store
        this.store = props[storeKey] || context[storeKey]
        this.propsMode = Boolean(props[storeKey])
        this.setWrappedInstance = this.setWrappedInstance.bind(this)

        invariant(this.store,
          `Could not find "${storeKey}" in either the context or props of ` +
          `"${displayName}". Either wrap the root component in a <Provider>, ` +
          `or explicitly pass "${storeKey}" as a prop to "${displayName}".`
        )

        this.initSelector()
        this.initSubscription()
    }
    // 略
}

const contextTypes = {
    [storeKey]: storeShape,
    [subscriptionKey]: subscriptionShape,
}
const childContextTypes = {
    [subscriptionKey]: subscriptionShape,
}
Connect.contextTypes = contextTypes
Connect.propTypes = contextTypes

这段代码便是通过 context 的方式,获取到 Provider 中的全局 store, 总的步骤为

  1. connect 通过 context 获取 Provider 中的 store,通过 store.getState() 获取整个 store tree 上所有 state。
  2. connect 模块的返回值 wrapWithConnect 为 function。
  3. wrapWithConnect 返回一个 ReactComponent 对象 Connect,Connect 重新 render 外部传入的原组件 WrappedComponent,并把 connect 中传入的 mapStateToProps, mapDispatchToProps 与组件上原有的 props 合并后,通过属性的方式传给 WrappedComponent。

调用方式

const ProductCounterBox = connect(state => ({
    isLogin: state['is_login']
}))(ProductCounter);

这时 ProductCounter 组件的 props 便包含 isLogin

React 响应 store

Redux 中还包含 subscribe 函数,用于注册事件,并在 dispatch 执行时发布事件。

function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()

    // 将事件 push 到容器中,在 dispatch 遍历
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
}

function dispatch(action) {
    // 略
    // 这里遍历事件并触发
    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

那在 React-Redux 中注册了什么事件?

componentDidMount() {
    //略
    this.subscription.trySubscribe()
}

trySubscribe() {
    if (!this.unsubscribe) {
      this.unsubscribe = this.parentSub
        ? this.parentSub.addNestedSub(this.onStateChange)
        : this.store.subscribe(this.onStateChange)

      this.listeners = createListenerCollection()
    }
}

this.store.subscribe(this.onStateChange) 将事件注册到全局。

onStateChange() {
    this.selector.run(this.props)

    if (!this.selector.shouldComponentUpdate) {
      this.notifyNestedSubs()
    } else {
      this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
      this.setState(dummyState)
    }
}

onStateChangesetState,便触发 render 从而重新渲染子组件

 render() {
    const selector = this.selector
    selector.shouldComponentUpdate = false

    if (selector.error) {
      throw selector.error
    } else {
      return createElement(WrappedComponent, this.addExtraProps(selector.props))
    }
}

selector.props 保存的是组件需要的 props。

function makeSelectorStateful(sourceSelector, store) {
  // wrap the selector in an object that tracks its results between runs.
  const selector = {
    run: function runComponentSelector(props) {
      try {
        const nextProps = sourceSelector(store.getState(), props)
        if (nextProps !== selector.props || selector.error) {
          selector.shouldComponentUpdate = true
          selector.props = nextProps
          selector.error = null
        }
      } catch (error) {
        selector.shouldComponentUpdate = true
        selector.error = error
      }
    }
  }

  return selector
}

onStateChange 被触发的时候,会调用 this.selector.run(this.props)。这时候会通过 store.getState() 拿到最新的 state,与旧的 state 做比较,决定是否重新渲染组件。

总结: 文章解释了以下:

End By BBChen :stuck_out_tongue:

参考

loocao commented 6 years ago

竟然不加入我大Vue,投入到react的怀抱~

JoeBBChen commented 6 years ago

@loocao 啊哈,那 Vue 带我飞吧。我写 React 要比写 Vue 要早~

loocao commented 6 years ago

vue比angular,react容易太多了啊~ 像我这种用angular2连Hello World都跑不起来的淫都能学会vue~