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.trySubscribe()
订阅父组件传来的store。subscription.notifyNestedSubs
,因为子组件订阅的是父组件的subscription,子组件触发订阅函数................java
connect能够将store中的数据和方法进行处理后经过props传入其包裹的组件中。react
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:缓存
mapStateToProps
不会执行。connect在每次dispatch后,都会调用store.getState()获取最新的state,并使用lastState === currentState
判断是否变化。并且在 redux combineReducers API中也作了优化,即当reducer中state没有变化时,返回原来的state。mapStateToProps
执行和组件从新渲染的条件,因此能不传的时候不要传。mapDispatchToProps能够将store.dispatch
或者dispatch一个action的方法((…args) => dispatch(actionCreator(…args))
)传递到其包裹的组件中。mapDispatchToProps有多种用法:ide
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) }
dispatch => bindActionCreators(mapDispatchToProps, dispatch)
)。默认状况下,每次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()) }) } }
能够在不用connect包裹组件的状况下订阅store或dispatch action。
下面是对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
优化。
返回dispatch的引用
const dispatch = useDispatch()
返回store的引用
const store = useStore()