• 正式学习React(四) 前序篇


    预热

    redux 函数内部包含了大量柯里化函数以及代码组合思想

    柯里化函数(curry

    通俗的来讲,可以用一句话概括柯里化函数:返回函数的函数

    // example
    const funcA = (a) => {
      return const funcB = (b) => {
        return a + b
      }
    };

    上述的funcA函数接收一个参数,并返回同样接收一个参数的funcB函数。

    柯里化函数有什么好处呢?

    • 避免了给一个函数传入大量的参数--我们可以通过柯里化来构建类似上例的函数嵌套,将参数的代入分离开,更有利于调试

    • 降低耦合度和代码冗余,便于复用

    举个栗子:

    // 已知listA, listB两个Array,都由int组成,需要筛选出两个Array的交集
    const listA = [1, 2, 3, 4, 5];
    const listB = [2, 3, 4];
    
    const checkIfDataExist = (list) => {
      return (target) => {
        return list.some(value => value === target)
      };
    };
    // 调用一次checkIfDataExist函数,并将listA作为参数传入,来构建一个新的函数。
    // 而新函数的作用则是:检查传入的参数是否存在于listA里
    const ifDataExist = checkIfDataExist(listA);
    
    // 使用新函数来对listB里的每一个元素进行筛选
    const intersectionList = listB.filter(value => ifDataExist(value));
    console.log(intersectionList); // [2, 3, 4]

    代码组合(compose

    代码组合就像是数学中的结合律:

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

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

    Redux

    combineReducers

    // 回顾一下combineReducers的使用格式
    
    // 两个reducer
    const todos = (state = INIT.todos, action) => {
      // ....
    };
    const filterStatus = (state = INIT.filterStatus, action) => {
      // ...
    };
    
    const appReducer = combineReducers({
      todos,
      filterStatus
    });

    还记得combineReducers的黑魔法吗?即:

    1. 传入的Object参数中,对象的keyvalue所代表的reducer function同名

    2. 各个reducer function的名称和需要传入该reducer的state参数同名

    源码标注解读(省略部分):

    export default function combineReducers(reducers) {
      // 第一次筛选,参数reducers为Object
      // 筛选掉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 sanityError
      try {
        // assertReducerSanity函数用于遍历finalReducers中的reducer,检查传入reducer的state是否合法
        assertReducerSanity(finalReducers)
      } catch (e) {
        sanityError = e
      }
      
      // 返回一个function。该方法接收state和action作为参数
      return function combination(state = {}, action) {
        // 如果之前的判断reducers中有不法值,则抛出错误
        if (sanityError) {
          throw sanityError
        }
        // 如果不是production环境则抛出warning
        if (process.env.NODE_ENV !== 'production') {
          var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action)
          if (warningMessage) {
            warning(warningMessage)
          }
        }
    
        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]
          // 这也就是为什么说combineReducers黑魔法--要求传入的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
      }
    }
    
    // 检查传入reducer的state是否合法
    function assertReducerSanity(reducers) {
      Object.keys(reducers).forEach(key => {
        var reducer = reducers[key]
        // 遍历全部reducer,并给它传入(undefined, action)
        // 当第一个参数传入undefined时,则为各个reducer定义的默认参数
        var initialState = reducer(undefined, { type: ActionTypes.INIT })
        
        // ActionTypes.INIT几乎不会被定义,所以会通过switch的default返回reducer的默认参数。如果没有指定默认参数,则返回undefined,抛出错误
        if (typeof initialState === 'undefined') {
          throw new Error(
            `Reducer "${key}" returned undefined during initialization. ` +
            `If the state passed to the reducer is undefined, you must ` +
            `explicitly return the initial state. The initial state may ` +
            `not be undefined.`
          )
        }
    
        var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
        if (typeof reducer(undefined, { type }) === 'undefined') {
          throw new Error(
            `Reducer "${key}" returned undefined when probed with a random type. ` +
            `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
            `namespace. They are considered private. Instead, you must return the ` +
            `current state for any unknown actions, unless it is undefined, ` +
            `in which case you must return the initial state, regardless of the ` +
            `action type. The initial state may not be undefined.`
          )
        }
      })
    }

    createStore

    // 回顾下使用方法
    const store = createStore(reducers, state, enhance);

    源码标注解读(省略部分):

    // 对于未知的action.type,reducer必须返回默认的参数state。这个ActionTypes.INIT就可以用来监测当reducer传入未知type的action时,返回的state是否合法
    export var ActionTypes = {
      INIT: '@@redux/INIT'
    }
    
    export default function createStore(reducer, initialState, enhancer) {
      // 检查你的state和enhance参数有没有传反
      if (typeof initialState === 'function' && typeof enhancer === 'undefined') {
        enhancer = initialState
        initialState = undefined
      }
      // 如果有传入合法的enhance,则通过enhancer再调用一次createStore
      if (typeof enhancer !== 'undefined') {
        if (typeof enhancer !== 'function') {
          throw new Error('Expected the enhancer to be a function.')
        }
        return enhancer(createStore)(reducer, initialState)
      }
    
      if (typeof reducer !== 'function') {
        throw new Error('Expected the reducer to be a function.')
      }
    
      var currentReducer = reducer
      var currentState = initialState
      var currentListeners = []
      var nextListeners = currentListeners
      var isDispatching = false // 是否正在分发事件
    
      function ensureCanMutateNextListeners() {
        if (nextListeners === currentListeners) {
          nextListeners = currentListeners.slice()
        }
      }
    
      // 我们在action middleware中经常使用的getState()方法,返回当前state
      function getState() {
        return currentState
      }
    
      // 注册listener,同时返回一个取消事件注册的方法。当调用store.dispatch的时候调用listener
      function subscribe(listener) {
        if (typeof listener !== 'function') {
          throw new Error('Expected listener to be a function.')
        }
    
        var isSubscribed = true
    
        ensureCanMutateNextListeners()
        nextListeners.push(listener)
    
        return function unsubscribe() {
          if (!isSubscribed) {
            return
          }
    
          isSubscribed = false
          // 从nextListeners中去除掉当前listener
          ensureCanMutateNextListeners()
          var index = nextListeners.indexOf(listener)
          nextListeners.splice(index, 1)
        }
      }
    
      // dispatch方法接收的action是个对象,而不是方法。
      // 这个对象实际上就是我们自定义action的返回值,因为dispatch的时候,已经调用过我们的自定义action了,比如 dispatch(addTodo())
      function dispatch(action) {
        if (!isPlainObject(action)) {
          throw new Error(
            'Actions must be plain objects. ' +
            'Use custom middleware for async actions.'
          )
        }
    
        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
        }
        // 遍历调用各个linster
        var listeners = currentListeners = nextListeners
        for (var i = 0; i < listeners.length; i++) {
          listeners[i]()
        }
    
        return action
      }
      // Replaces the reducer currently used by the store to calculate the state.
      function replaceReducer(nextReducer) {
        if (typeof nextReducer !== 'function') {
          throw new Error('Expected the nextReducer to be a function.')
        }
    
        currentReducer = nextReducer
        dispatch({ type: ActionTypes.INIT })
      }
      // 当create store的时候,reducer会接受一个type为ActionTypes.INIT的action,使reducer返回他们默认的state,这样可以快速的形成默认的state的结构
      dispatch({ type: ActionTypes.INIT })
    
      return {
        dispatch,
        subscribe,
        getState,
        replaceReducer
      }
    }

    thunkMiddleware

    源码及其简单简直给跪...

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

    applyMiddleware

    先复习下用法:

    // usage
    import {createStore, applyMiddleware} from 'redux';
    import thunkMiddleware from 'redux-thunk';
    
    const store = createStore(
          reducers,
          state,
          applyMiddleware(thunkMiddleware)
    );

    applyMiddleware首先接收thunkMiddleware作为参数,两者组合成为一个新的函数(enhance),之后在createStore内部,因为enhance的存在,将会变成返回enhancer(createStore)(reducer, initialState)

    源码标注解读(省略部分):

    // 定义一个代码组合的方法
    // 传入一些function作为参数,返回其链式调用的形态。例如,
    // compose(f, g, h) 最终返回 (...args) => f(g(h(...args)))
    export default function compose(...funcs) {
      if (funcs.length === 0) {
        return arg => arg
      } else {
        const last = funcs[funcs.length - 1]
        const rest = funcs.slice(0, -1)
        return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
      }
    }
    
    export default function applyMiddleware(...middlewares) {
      // 最终返回一个以createStore为参数的匿名函数
      // 这个函数返回另一个以reducer, initialState, enhancer为参数的匿名函数
      return (createStore) => (reducer, initialState, enhancer) => {
        var store = createStore(reducer, initialState, enhancer)
        var dispatch
        var chain = []
    
        var middlewareAPI = {
          getState: store.getState,
          dispatch: (action) => dispatch(action)
        }
        // 每个 middleware 都以 middlewareAPI 作为参数进行注入,返回一个新的链。此时的返回值相当于调用 thunkMiddleware 返回的函数: (next) => (action) => {} ,接收一个next作为其参数
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        // 并将链代入进 compose 组成一个函数的调用链
        // compose(...chain) 返回形如(...args) => f(g(h(...args))),f/g/h都是chain中的函数对象。
        // 在目前只有 thunkMiddleware 作为 middlewares 参数的情况下,将返回 (next) => (action) => {}
        // 之后以 store.dispatch 作为参数进行注入
        dispatch = compose(...chain)(store.dispatch)
    
        return {
          ...store,
          dispatch
        }
      }
    }

    一脸懵逼?没关系,来结合实际使用总结一下:

    当我们搭配redux-thunk这个库的时候,在redux配合components时,通常这么写

    import thunkMiddleware from 'redux-thunk';
    import { createStore, applyMiddleware, combineReducer } from 'redux';
    import * as reducers from './reducers.js';
    
    const appReducer = combineReducer(reducers);
    const store = createStore(appReducer, initialState, applyMiddleware(thunkMiddleware));

    还记得当createStore收到的参数中有enhance时会怎么做吗?

    // createStore.js
    if (typeof enhancer !== 'undefined') {
      if (typeof enhancer !== 'function') {
        throw new Error('Expected the enhancer to be a function.')
      }
      return enhancer(createStore)(reducer, initialState)
    }

    也就是说,会变成下面的情况

    applyMiddleware(thunkMiddleware)(createStore)(reducer, initialState)
    • applyMiddleware(thunkMiddleware)
      applyMiddleware接收thunkMiddleware作为参数,返回形如(createStore) => (reducer, initialState, enhancer) => {}的函数。

    • applyMiddleware(thunkMiddleware)(createStore)
      以 createStore 作为参数,调用上一步返回的函数(reducer, initialState, enhancer) => {}

    • applyMiddleware(thunkMiddleware)(createStore)(reducer, initialState)
      以(reducer, initialState)为参数进行调用。

    在这个函数内部,thunkMiddleware被调用,其作用是监测typefunctionaction

    因此,如果dispatchaction返回的是一个function,则证明是中间件,则将(dispatch, getState)作为参数代入其中,进行action 内部下一步的操作。否则的话,认为只是一个普通的action,将通过next(也就是dispatch)进一步分发。

    也就是说,applyMiddleware(thunkMiddleware)作为enhance,最终起了这样的作用:

    dispatch调用的action(例如,dispatch(addNewTodo(todo)))进行检查,如果action在第一次调用之后返回的是function,则将(dispatch, getState)作为参数注入到action返回的方法中,否则就正常对action进行分发,这样一来我们的中间件就完成喽~

    因此,当action内部需要获取state,或者需要进行异步操作,在操作完成之后进行事件调用分发的话,我们就可以让action 返回一个以(dispatch, getState)为参数的function而不是通常的Objectenhance就会对其进行检测以便正确的处理。

    bindActionCreator

    这个方法感觉比较少见,我个人也很少用到

    在传统写法下,当我们要把 state 和 action 注入到子组件中时,一般会这么做:

    import { connect } from 'react-redux';
    import {addTodo, deleteTodo} from './action.js';
    
    class TodoComponect extends Component {
      render() {
        return (
          <ChildComponent 
            deleteTodo={this.props.deleteTodo}
            addTodo={this.props.addTodo}
          />
        )
      }
    }
    
    function mapStateToProps(state) {
      return {
        state
      }
    }
    function mapDispatchToProps(dispatch) {
      return {
        deleteTodo: (id) => {
          dispatch(deleteTodo(id));
        },
        addTodo: (todo) => {
          dispatch(addTodo(todo));
        }
      }
    }
    export default connect(mapStateToProps, mapDispatchToProps)(TodoComponect);

    使用bindActionCreators可以把 action 转为同名 key 的对象,但使用 dispatch 把每个 action 包围起来调用

    惟一使用 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 Redux store 或 dispatch 传给它。

    import { bindActionCreators } from 'redux';
    import { connect } from 'react-redux';
    import {addTodo, deleteTodo} as TodoActions from './action.js';
    
    class TodoComponect extends React.Component {
      
      // 在本组件内的应用
      addTodo(todo) {
        let action = TodoActions.addTodo(todo);
        this.props.dispatch(action);
      }
      
      deleteTodo(id) {
        let action = TodoActions.deleteTodo(id);
        this.props.dispatch(action);
      }
      
      render() {
        let dispatch = this.props.dispatch;
        // 传递给子组件
        let boundActionCreators = bindActionCreators(TodoActions, dispatch);
        return (
          <ChildComponent 
            {...boundActionCreators}
          />
        )
      }
    }
    
    function mapStateToProps(state) {
      return {
        state
      }
    }
    export default connect(mapStateToProps)(TodoComponect)

    bindActionCreator源码解析

    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)
      }
    
      if (typeof actionCreators !== 'object' || actionCreators === null) {
        throw new Error(
          `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
          `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
        )
      }
    
      // 遍历并通过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
    }

    react-redux

    Provider

    export default class Provider extends Component {
      getChildContext() {
        // 将其声明为 context 的属性之一
        return { store: this.store }
      }
    
      constructor(props, context) {
        super(props, context)
        // 接收 redux 的 store 作为 props
        this.store = props.store
      }
    
      render() {
        return Children.only(this.props.children)
      }
    }
    
    if (process.env.NODE_ENV !== 'production') {
      Provider.prototype.componentWillReceiveProps = function (nextProps) {
        const { store } = this
        const { store: nextStore } = nextProps
    
        if (store !== nextStore) {
          warnAboutReceivingStore()
        }
      }
    }
    
    Provider.propTypes = {
      store: storeShape.isRequired,
      children: PropTypes.element.isRequired
    }
    Provider.childContextTypes = {
      store: storeShape.isRequired
    }

    connect

    传入mapStateToProps,mapDispatchToProps,mergeProps,options
    首先获取传入的参数,如果没有则以默认值代替

    const defaultMapStateToProps = state => ({}) // eslint-disable-line no-unused-vars
    const defaultMapDispatchToProps = dispatch => ({ dispatch })
    const { pure = true, withRef = false } = options

    之后,通过

    const finalMergeProps = mergeProps || defaultMergeProps

    选择合并stateProps,dispatchProps,parentProps的方式,默认的合并方式 defaultMergeProps 为:

    const defaultMergeProps = (stateProps, dispatchProps, parentProps) => ({
      ...parentProps,
      ...stateProps,
      ...dispatchProps
    })

    返回一个以 Component 作为参数的函数。在这个函数内部,生成了一个叫做Connect的 Component

    // ...
      return function wrapWithConnect(WrappedComponent) {
        const connectDisplayName = `Connect(${getDisplayName(WrappedComponent)})`
        // 检查参数合法性
        function checkStateShape(props, methodName) {}
        // 合并props
        function computeMergedProps(stateProps, dispatchProps, parentProps) {
          const mergedProps = finalMergeProps(stateProps, dispatchProps, parentProps)
          if (process.env.NODE_ENV !== 'production') {
            checkStateShape(mergedProps, 'mergeProps')
          }
          return mergedProps
        }
        
        // start of Connect
        class Connect extends Component {
          constructor(props, context) {
            super(props, context);
            this.store = props.store || context.store
            
            const storeState = this.store.getState()
            this.state = { storeState }
            this.clearCache()
          }
          
          computeStateProps(store, props) {
            // 调用configureFinalMapState,使用传入的mapStateToProps方法(或默认方法),将state map进props
          }
          configureFinalMapState(store, props) {}
          
          computeDispatchProps(store, props) {
            // 调用configureFinalMapDispatch,使用传入的mapDispatchToProps方法(或默认方法),将action使用dispatch封装map进props
          }
          configureFinalMapDispatch(store, props) {}
          
          // 判断是否更新props
          updateStatePropsIfNeeded() {}
          updateDispatchPropsIfNeeded() {}
          updateMergedPropsIfNeeded() {}
          
          componentDidMount() {
            // 内部调用this.store.subscribe(this.handleChange.bind(this))
            this.trySubscribe()
          }
          handleChange() {
            const storeState = this.store.getState()
            const prevStoreState = this.state.storeState
            // 对数据进行监听,发送改变时调用
            this.setState({ storeState })
          }
          
          // 取消监听,清除缓存
          componentWillUnmount() {
            this.tryUnsubscribe()
            this.clearCache()
          }
          
          render() {
            this.renderedElement = createElement(WrappedComponent,
                this.mergedProps
            )
            return this.renderedElement
          }
        }
        // end of Connect
        
        Connect.displayName = connectDisplayName
        Connect.WrappedComponent = WrappedComponent
        Connect.contextTypes = {
          store: storeShape
        }
        Connect.propTypes = {
          store: storeShape
        }
        
        return hoistStatics(Connect, WrappedComponent)
      }
    // ...

    我们看见,在connect的最后,返回了使用hoistStatics包装的ConnectWrappedComponent

    hoistStatics是什么鬼?为什么使用它?

    Copies non-react specific statics from a child component to a parent component. Similar to Object.assign, but with React static keywords blacklisted from being overridden.

    也就是说,它类似于Object.assign,作用是将子组件中的 static 方法复制进父组件,但不会覆盖组件中的关键字方法(如 componentDidMount)

    import hoistNonReactStatic from 'hoist-non-react-statics';
    
    hoistNonReactStatic(targetComponent, sourceComponent);
  • 相关阅读:
    [洛谷P1507]NASA的食物计划 以及 对背包问题的整理
    [洛谷P3811]【模板】乘法逆元
    56级信息奥赛12学子汇总
    [洛谷U63006]导函数最小系数
    [洛谷U62364]三次函数极值
    [洛谷U62358]求导函数
    Nagios 请检查HTTP服务器关于该CGI的访问权限设置
    安装Cacti-plugin
    解压安装Cacti在apache中的补充
    64位操作系统下IIS报“试图加载格式不正确的程序”错误
  • 原文地址:https://www.cnblogs.com/huenchao/p/6087753.html
Copyright © 2020-2023  润新知