React-redux总结

React-redux总结

1.Provider

function Provider({store, context, children}){
    //contextValue初始化
    const contextValue = useMemo(()=>{
        const subscription = new Subscription(store);
        subscription.onStateChange = subscription.notifyNestedSubs;
      //自定义订阅模型供子组件订阅
        return {
            store,
            subscription
        }
    }, [store])
    const previosState = useMemo(()=> store.getState(),[store]);
    useEffect(()=>{
        const { subscription } = contextValue;
      //根节点订阅原始store,子节点订阅父组件中的subscription
        subscription.trySubscribe();
        if(previosState !== store.getState()){
          //订阅模型造成网状结构,保证订阅函数的执行顺序为父节点到子节点的网状结构,防止子节点的订阅者先触发致使过时props问题。
            subscription.notifyNestedSubs();
        }
        return ()=>{
            subscription.tryUnsubscribe();
            subscription.onStateChange = null;
        }
    },[contextValue, previosState])
    const Context =context || ReactReduxContext;
    return <Context.Provider value={contextValue}>{children}</Context.Provider>
}

上面的逻辑能够简述以下:javascript

  • 新建一个订阅模型subscription供子组件订阅
  • 在useEffect中subscription调用subscription.trySubscribe()订阅父组件传来的store。
  • 当store因为dispatch触发订阅函数时,执行subscription.notifyNestedSubs,因为子组件订阅的是父组件的subscription,子组件触发订阅函数

    ................java

  • 将改造后的store经过contextAPI传入子组件。

2. Connect

connect能够将store中的数据和方法进行处理后经过props传入其包裹的组件中。react

2.1 mapStateToProps: (state, ownProps) => stateProps

mapStateToProps方法主要用来对store中数据进行reshape。由于每次store变化都会触发全部connect中的mapStateToProps函数执行,因此该函数应该运行的足够快以避免影响性能。必要的时候可使用selector库来避免没必要要的计算(相同输入的状况下不进行再次计算,而是使用上一次的缓存值)。ios

react-redux进行了不少优化以免没必要要的重复渲染,redux

(state) => stateProps (state, ownProps) => stateProps
mapStateToProps 执行条件: store state 变化 store state 变化或者ownProps变化
组件从新渲染条件: stateProps变化 stateProps变化或者ownProps变化

从上表能够总结一些tips:缓存

  • 判断stateProps变化是采用shallow equality checks比较的, 每次执行(state, ownProps) => stateProps即便输入值同样,若是 stateProps中的每一个field返回了新对象,也会触发从新渲染。可使用selector缓存上一次的值来避免stateProps变化。
  • 当store state没有变化时,mapStateToProps不会执行。connect在每次dispatch后,都会调用store.getState()获取最新的state,并使用lastState === currentState判断是否变化。并且在 redux combineReducers API中也作了优化,即当reducer中state没有变化时,返回原来的state。
  • ownProps也是mapStateToProps执行和组件从新渲染的条件,因此能不传的时候不要传。

2.2 mapDispatchToProps

mapDispatchToProps能够将store.dispatch或者dispatch一个action的方法((…args) => dispatch(actionCreator(…args)))传递到其包裹的组件中。mapDispatchToProps有多种用法:ide

  • 不传递mapDispatchToProps时,将会将dispatch方法传递到其包裹的组件中。
  • mapDispatchToProps定义为函数时,(dispatch,ownProps)=>dispatchProps函数

    const increment = () => ({ type: 'INCREMENT' })
    const decrement = () => ({ type: 'DECREMENT' })
    const reset = () => ({ type: 'RESET' })
    
    const mapDispatchToProps = dispatch => {
      return {
        // dispatching actions returned by action creators
        increment: () => dispatch(increment()),
        decrement: () => dispatch(decrement()),
        reset: () => dispatch(reset())
      }
    }

    redux中提供了bindActionCreators接口能够自动的进行actionCreators到相应dispatch方法的转换:性能

    import { bindActionCreators } from 'redux'
    
    const increment = () => ({ type: 'INCREMENT' })
    const decrement = () => ({ type: 'DECREMENT' })
    const reset = () => ({ type: 'RESET' })
    
    function mapDispatchToProps(dispatch) {
      return bindActionCreators({ increment, decrement, reset }, dispatch)
    }
  • mapDispatchToProps定义为action creators键值对时,connect内部会自动调用bindActionCreators将其转化为dispatching action函数形式(dispatch => bindActionCreators(mapDispatchToProps, dispatch))。

3. batch

默认状况下,每次dispatch都会执行connect函数,并执行接下来可能的重复渲染过程,使用batchAPI,能够将屡次dispatch合并,相似setState的合并过程。优化

import { batch } from 'react-redux'

function myThunk() {
  return (dispatch, getState) => {
    // should only result in one combined re-render, not two
    batch(() => {
      dispatch(increment())
      dispatch(increment())
    })
  }
}

4. hooks

能够在不用connect包裹组件的状况下订阅store或dispatch action。

4.1 useSelector()

下面是对useSelector的简单实现:

const useSelector = selector => {
  const store = React.useContext(Context);
  const [, forceUpdate] = React.useReducer(c => c + 1, 0);
  const currentState = React.useRef();
  // 在re-render阶段更新state使得获取的props不是过时的
  currentState.current = selector(store.getState());

  React.useEffect(() => {
    return store.subscribe(() => {
      try {
        const nextState = selector(store.getState());

        if (nextState === currentState.current) {
          // Bail out updates early
          return;
        }
      } catch (err) {
        // Ignore errors
        //忽略因为过时props带来的计算错误
      }
            //state变化须要从新渲染
      // Either way we want to force a re-render(与其余订阅者中的forceUpdate合并执行)
      forceUpdate();
    });
  }, [store, forceUpdate, selector, currentState]);

  return currentState.current;
};

useSelector中新旧state的对比使用===,而不是connect中的浅比较,因此selector返回一个新的对象会致使每次从新渲染。对于这个问题,可使用多个selector返回基本类型的值来解决;或者使用reselect库缓存计算结果;最后还能够传递useSelector的第二个参数,自定义新旧state的比较函数。

connect中会对新旧state和props值进行比较来决定是否执行mapStateToProps,可是useSelector中没有这种机制,因此不会阻止因为父组件的re-render致使的re-render(即便props不变化),这种状况下能够采用React.memo优化。

4.2 useDispatch()

返回dispatch的引用

const dispatch = useDispatch()

4.3 useStore()

返回store的引用

const store = useStore()
相关文章
相关标签/搜索