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>
}
上面的逻辑可以简述如下:
- 新建一个订阅模型subscription供子组件订阅
- 在useEffect中subscription调用
subscription.trySubscribe()
订阅父组件传来的store。 - 当store由于dispatch触发订阅函数时,执行
subscription.notifyNestedSubs
,由于子组件订阅的是父组件的subscription,子组件触发订阅函数................
- 将改造后的store通过contextAPI传入子组件。
2. Connect
connect可以将store中的数据和方法进行处理后通过props传入其包裹的组件中。
2.1 mapStateToProps: (state, ownProps) => stateProps
mapStateToProps方法主要用来对store中数据进行reshape。因为每次store变化都会触发所有connect中的mapStateToProps函数执行,所以该函数应该运行的足够快以免影响性能。必要的时候可以使用selector库来避免不必要的计算(相同输入的情况下不进行再次计算,而是使用上一次的缓存值)。
react-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有多种用法:
- 不传递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()
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。