React Redux: 从文档看源码 - Utils篇

注:这篇文章只是解说React Redux这一层,并不包括Redux部份。Redux有设计去进修,等今后进修了Redux源码今后再做剖析
注:代码基于如今(2016.12.29)React Redux的最新版本(5.0.1)

Utils篇

这一小节内里先把基本的Utils代码过一遍,今后看中心代码的时刻轻易一点。由因而Utils不触及文档,所以没有文档方面的展现

shallowEqual.js

从名字中就能看出这个的作用,实在就只是做了一个浅比较,对象中的value直接用 === 来比较,所以假如碰到庞杂的object举行比较,就会返回false。最基本的就是shallowEqual({a:{}}, {a:{}}) === false

const hasOwn = Object.prototype.hasOwnProperty

export default function shallowEqual(a, b) {
  if (a === b) return true

  let countA = 0
  let countB = 0
  
  for (let key in a) {
    if (hasOwn.call(a, key) && a[key] !== b[key]) return false
    countA++
  }

  for (let key in b) {
    if (hasOwn.call(b, key)) countB++
  }

  return countA === countB
}

代码比较简朴,基本思路就是:

  1. 比较a对象中的本身属性是不是在b中也存在而且相称,假如不存在或不想等,返回false

  2. 比较b中本身属性的数目是不是即是a中本身属性的数目,假如不相同,返回false

对代码的一点疑问:

  1. countA++之前,要不要搜检一下是不是是OwnProperty?我已经在segmentfault内里提了题目,LionKissDeer在github上发了issue,并获得复兴,确实是一个题目。

  2. 用countA, countB来纪录数目,而不是用Object.keys(a).length来举行对照,能够理解为削减操纵和不必要的内存运用。那末是不是只用一个countA,然后再第二个for…in中举行countA–,末了比较countA === 0更好?

storeShape.js

这个真的只是store的shape,不须要诠释

import { PropTypes } from 'react'

export default PropTypes.shape({
  subscribe: PropTypes.func.isRequired,
  dispatch: PropTypes.func.isRequired,
  getState: PropTypes.func.isRequired
})

warning.js

简朴的一个报错的要领,主如果搜检了console是不是存在的状况。中心有提到一点,假如console打开了’break on all exception’选项,那末就会在这个warning的处所停下

/**
 * Prints a warning in the console if it exists.
 *
 * @param {String} message The warning message.
 * @returns {void}
 */
export default function warning(message) {
  /* eslint-disable no-console */
  if (typeof console !== 'undefined' && typeof console.error === 'function') {
    console.error(message)
  }
  /* eslint-enable no-console */
  try {
    // This error was thrown as a convenience so that if you enable
    // "break on all exceptions" in your console,
    // it would pause the execution at this line.
    throw new Error(message)
    /* eslint-disable no-empty */
  } catch (e) {}
  /* eslint-enable no-empty */
}

verifyPlainObject.js

经由过程推断是不是是plainObject,运用lodash来推断,并给个warning。这个要领主如果用在非production环境下,对数据格式举行检测,并抛出非常

import isPlainObject from 'lodash/isPlainObject'
import warning from './warning'

export default function verifyPlainObject(value, displayName, methodName) {
  if (!isPlainObject(value)) {
    warning(
      `${methodName}() in ${displayName} must return a plain object. Instead received ${value}.`
    )
  }
}

wrapActionCreators.js

这里是经由过程这个要领天生一个(actionCreators)=>(dispatch)=>()=>binded actions的要领。bindActionCreators的作用是返回用dispatch绑定过的actions,具体要领能够在Redux文档中检察。
然则,我并没有在react-redux的代码里看到这个要领的挪用,文档中也没有提到过这个要领,虽然在mapDispatchToProps.js中看到了一样的代码片断…不知道是不是是老代码没有删除清洁,照样新功能须要,然则还没有上线…

import { bindActionCreators } from 'redux'

export default function wrapActionCreators(actionCreators) {
  return dispatch => bindActionCreators(actionCreators, dispatch)
}

Subscription.js

这里是用一个典范的定阅宣布形式,经由过程对store或父级subscription举行监听,来举行组件的更新(onStateChange)。

createListenerCollection function

先放一个工场形式的代码,这段主如果用来天生listener的工场:

const CLEARED = null
const nullListeners = { notify() {} }

function createListenerCollection() {
  // the current/next pattern is copied from redux's createStore code.
  // TODO: refactor+expose that code to be reusable here?
  let current = []
  let next = []

  return {
    clear() {
      next = CLEARED
      current = CLEARED
    },

    notify() {
      const listeners = current = next
      for (let i = 0; i < listeners.length; i++) {
        listeners[i]()
      }
    },

    subscribe(listener) {
      let isSubscribed = true
      if (next === current) next = current.slice()
      next.push(listener)

      return function unsubscribe() {
        if (!isSubscribed || current === CLEARED) return
        isSubscribed = false

        if (next === current) next = current.slice()
        next.splice(next.indexOf(listener), 1)
      }
    }
  }
}

这段很简朴的完成了一个listener的工场,包括notify, subscribe, unsubscribe和clear等定阅宣布形式的基本功能。
值得注重的是,这里运用了current/next形式,这里主如果为了防备在notify中,listeners[i]()运转的时刻对current对象作出内容删除操纵,从而致使notify失足。
举个栗子,我们要运转下面这段代码:

var listener = createListenerCollection();

var helloUnsub = listener.subscribe(()=>{ console.log("Hello");  });
var worldUnsub = listener.subscribe(()=>{ console.log("world"); helloUnsub(); });
var unsub = listener.subscribe(()=>{ console.log("!!"); });

listener.notify(); // 希冀输出的是Hello world !!

listener.notify(); // 希冀输出的是world !!

然后我们用修悛改没有用next/current形式的代码运转:

function createListenerCollection() {
  let current = []

  return {
    notify() {
      const listeners = current
      for (let i = 0; i < listeners.length; i++) {
        listeners[i]()
      }
    },

    subscribe(listener) {
      let isSubscribed = true
      current.push(listener)

      return function unsubscribe() {
        if (!isSubscribed || current === CLEARED) return
        isSubscribed = false

        current.splice(current.indexOf(listener), 1)
      }
    }
  }
}

看一下输出效果:

《React Redux: 从文档看源码 - Utils篇》

发明第一次输出的时刻,少输出了一次。
在这里,我们在world的输出后,作废hello的subscribe。这里就会形成:输出完world今后,删除了hello,代码里listeners.length的推断就自动削减了1,所以致使!!没有输出。
而假如运用next/current形式的话,因为我们对unsubscribe的操纵都是对新的next举行操纵,所以不会影响listeners,就不会涌现上面的题目。

Subscription class

一个简朴的封装:

export default class Subscription {
  constructor(store, parentSub) {
    this.store = store
    this.parentSub = parentSub
    this.unsubscribe = null
    this.listeners = nullListeners
  }

  addNestedSub(listener) {
    this.trySubscribe()
    return this.listeners.subscribe(listener)
  }

  notifyNestedSubs() {
    this.listeners.notify()
  }

  isSubscribed() {
    return Boolean(this.unsubscribe)
  }

  trySubscribe() {
    if (!this.unsubscribe) {
      // this.onStateChange is set by connectAdvanced.initSubscription()
      this.unsubscribe = this.parentSub
        ? this.parentSub.addNestedSub(this.onStateChange)
        : this.store.subscribe(this.onStateChange)
 
      this.listeners = createListenerCollection()
    }
  }

  tryUnsubscribe() {
    if (this.unsubscribe) {
      this.unsubscribe()
      this.unsubscribe = null
      this.listeners.clear()
      this.listeners = nullListeners
    }
  }
}

唯一须要注重的一点是,他们的onStateChange事宜实际上是绑定在父级(parentSub)或许store的subscription上面的。至于为何要绑定在父级或许store上面,是因为父级发生了转变,就会关照下级,下级再关照下下级…所以下级须要衔接到上级上。
挪用形式大概是这模样的,由上到下由顶层store一层一层到leaf:
《React Redux: 从文档看源码 - Utils篇》

不明白的一点:
这模样绑定上级的要领,和一切都直接绑定到store上面有什么差别?已发问

一点总结

  1. shallowEqual的简朴完成,觉得完全能够不必插件本身写一个,比较简朴。在上面关于shallowEqual的github上面,React Redux的作者有提到发起运用fbjs的shallowEqual

  2. next/current形式,迥殊适用于在轮回中能够会对轮回对象举行增删的状况,能够斟酌运用这个形式。经由过程天生一个影对象,对影对象举行修正,须要轮回的时刻,再赋值给current对象

  3. 一个简朴的定阅宣布形式,多层级的状况下,能够经由过程监听上一级来举行从root到leaf的挪用

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