DeanPaul / blog

MIT License
2 stars 1 forks source link

Redux Source Code #7

Open DeanPaul opened 6 years ago

DeanPaul commented 6 years ago

柯里化函数(curry) 代码组合(compose)

combineReducers bindActionCreator

createStore thunkMiddleware applyMiddleware

react-redux Provider connect

DeanPaul commented 6 years ago

redux 函数内部包含了大量柯里化函数以及代码组合思想 柯里化函数(curry) 通俗的来讲,可以用一句话概括柯里化函数:返回函数的函数

柯里化函数有什么好处呢? 避免了给一个函数传入大量的参数--我们可以通过柯里化来构建类似上例的函数嵌套,将参数的代入分离开,更有利于调试 降低耦合度和代码冗余,便于复用

DeanPaul commented 6 years ago

找出两个Array中的不同entity

let listA = [1,2,3,null,"20"]
let listB = [1,2,3,5,6,7,10,20,"200"]
const checkDataInArrayFunction = (list) => {
  return (target) => {
    return list.some(value => value === target)
  };
};
const ifDataExistInListA = checkDataInArrayFunction(listA);
const ifDataExistInListB = checkDataInArrayFunction(listB);
listA.filter(value => !ifDataExistInListB(value)) .concat ( listB.filter(value => !ifDataExistInListA(value)) );
DeanPaul commented 6 years ago

https://github.com/ecmadao/Coding-Guide/blob/master/Notes/React/Redux/Redux%E5%85%A5%E5%9D%91%E8%BF%9B%E9%98%B6-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.md

DeanPaul commented 6 years ago

代码组合(compose)

const compose = (f, g) => {
  return (x) => {
    return f(g(x));
  };
};
// 还可以再简洁点
const compose = (f, g) => (x) => f(g(x));

通过这样函数之间的组合,可以大大增加可读性,效果远大于嵌套一大堆的函数调用,并且我们可以随意更改函数的调用顺序

思考一下怎样写才能支持动态参数? reduce?

DeanPaul commented 6 years ago

combineReducers

它是一个工具类,所以先说明它

//参数reducers为Object
export default function combineReducers(reducers) {
  // 第一次筛选,筛选掉reducers中不是function的键值对
  var reducerKeys = Object.keys(reducers);
  var finalReducers = {}
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i];
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }

  var finalReducerKeys = Object.keys(finalReducers)

  // 二次筛选,判断reducer中传入的值是否合法(!== undefined)
  // 获取筛选完之后的所有key
  var shapeAssertionError
  try {
    // assertReducerSanity函数用于遍历finalReducers中的reducer,检查传入reducer的state是否合法
    assertReducerSanity(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  // 返回一个function。该方法接收state和action作为参数
  return function combination(state = {}, action) {
    // 如果之前的判断reducers中有不法值,则抛出错误
    if (shapeAssertionError) {
      throw shapeAssertionError
    }
    var hasChanged = false
    var nextState = {}
    // 遍历所有的key和reducer,分别将reducer对应的key所代表的state,代入到reducer中进行函数调用
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      // 要求传入的Object参数中,reducer function的名称和要和state同名的原因
      var previousStateForKey = state[key]
      var nextStateForKey = reducer(previousStateForKey, action)
      // 如果reducer返回undefined则抛出错误
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      // 将reducer返回的值填入nextState
      nextState[key] = nextStateForKey
      // 如果任一state有更新则hasChanged为true
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}
  })
}
DeanPaul commented 6 years ago

bindActionCreators

使用场景是什么? 当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 Redux store 或 dispatch 传给它 实际是封装Action成普通props的样子(让组件无法感知到dispatch)

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

// bindActionCreators期待一个Object作为actionCreators传入,里面是 key: action
export default function bindActionCreators(actionCreators, dispatch) {
  // 如果只是传入一个action,则通过bindActionCreator返回被绑定到dispatch的函数
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  // 遍历并通过bindActionCreator分发绑定至dispatch
  var keys = Object.keys(actionCreators)
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
DeanPaul commented 6 years ago

createStore

提供一套规则的数据库 getState是获取数据库的接口 dispatch是更新数据库的接口 subscribe是注册在store上listener的接口,listener会在任何dispatch之后执行 reducer像是一组暖气片,当dispatch发生时,必然会携带一个Action,Action是固定格式{type:''} ,当Action的type 匹配到某个暖气片时,执行该暖气片携带的函数,此函数更新store,也就是数据库

然后我们看源代码

export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
//可以不传初始的state,第二个参数就是enhancer的
    enhancer = preloadedState
    preloadedState = undefined
  }
//如果有传入合法的enhance,则通过enhancer再调用一次createStore
if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
//实际就是把createStore这件事在applyMiddleware里面做,转移了锅
    return enhancer(createStore)(reducer, preloadedState)
  }
  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false //是否正在dispatch
//返回当前state
function getState() {
    return currentState
}
//浅拷贝currentListeners
function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }
//// 注册listener,同时返回一个取消事件注册的方法。当调用store.dispatch的时候调用listener
function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    let isSubscribed = true
    ensureCanMutateNextListeners()
    nextListeners.push(listener)
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }
      isSubscribed = false
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
}
 function dispatch(action) {
//dispatch方法接收的action必须是个对象,而不是方法
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }
//Action必须有type属性
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }
// 调用dispatch的时候只能一个个调用,通过dispatch判断调用的状态
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }
//用处:动态注入reducer
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
// the initial state tree. 初始化每个reducer的init state
    dispatch({ type: ActionTypes.INIT })
  }
  function observable() {
    const outerSubscribe = subscribe
    return {
        subscribe(observer) {
        if (typeof observer !== 'object') {
          throw new TypeError('Expected the observer to be an object.')
        }

        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }
        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      }
    }
  }
DeanPaul commented 6 years ago

thunkMiddleware

// 返回以 dispatch 和 getState 作为参数的action
export default function thunkMiddleware({ dispatch, getState }) {
  return next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState);
    }

    return next(action);
  };
}
DeanPaul commented 6 years ago

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))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}