概述
在 react 我的项目中,redux 常常用来治理利用的数据,react-redux 用来绑定 redux, 这样你的组件能够从 store 中读取数据,并且能够 dispatch actions 更新 store, redux 次要思维让零碎中的数据依照对立的规定流动,即 单项数据流:
如图:对于组件的批改,通过 action 被 dispatch 到 store, store 依据 action 和以后的 state 计算出下一次的 state, component 拿到更新后的 state 从新渲染组件;能够看到数据只是单项流动
疑难
- redux 中间件 redux-thunk,redux-lodder 在上图的单向数据流中其实做了什么?
- react 组件如何订阅 store 中 state 的变动,通过 store.subscribe?那么如果保障子组件的订阅更新产生在父组件之后呢?(试想一下:父子组件都订阅了 store 中某一个 state 的变动,如果父组件响应订阅更新在子组件之后,子组件可能反复渲染)
- react-redux 中的 hooks, useSelector, useDispatch, useStore 等, 用 useSelector 替换 connect 办法?
问题剖析
入手实现一个简略的 redux, react-redux, 中间件,而后比照一下咱们的实现有哪些潜在的问题,实际上怎么解决的
实现一个简略的 redux, react-redux
如何应用 redux 和 react-redux, redux.createStore 办法创立一个 store, store 注入到 Provider 组件上,而后通过 connect 办法拿到 store 中的 state 和 dispatcher,而后通过组件的 props 拿到这些办法和属性
createStore 创立 store,store 对象具备 dispatch 办法,subscribe 办法,getState 办法, replaceReducer 办法,那么通过观察者模式能够实现
function createStore(reducer, preloadedState) {let listeners = []
let state = preloadedState
let currentReducer = reducer
let subscribe = (fn) => {listeners.push(fn)
return () => {const index = nextListeners.indexOf(listener)
listeners.splice(index, 1)
}
}
let dispatch = (action) => {state = currentReducer(state, action)
for (let i = 0; i< listeners.length; i++) {const listener = listeners[i]
listener()}
}
let getState = () => {return state}
let repalceReducer = (nextReducer) => {
currentReducer = nextReducer
dispatch({type:'@@redux/REPLACE'})
}
return {
dispatch
subscribe,
getState,
replaceReducer
}
}
redux 理论实现要比这个简单,比如说 listeners 存在两个变量中, currentListeners 和 nextListeners,dispatch 执行的总是 currentListeners 中的函数,subscribe 和 unsubscibe 总是在 nextListeners 中减少或者移除 listener,这样能够防止在 dispatching 过程中,listeners 数组产生扭转,下面还能够看到replaceReducer 其实更新了 state, 触发了订阅,replaceReducer 用在须要按需加载的 reducer 场景中,看下 combiceReducers 的实现,你会发现一次 combine 几百个 reducer 并不是一件坏事,replaceReducer 动静替换 reducer 晋升效率
接下来的问题如何在组件中订阅 state 的更新,并且能够 dispatch action,以更新 state;redux-redux 应用了 Context, redux 中的 Provider 组件就是对 Context.Provider 做了封装
export const ReactReduxContext = React.createContext(null)
function Provider({store, children, context}) {const [provider, setProvider] = useState({state: store.getState(),
dispatch: store.dispatch
})
useEffect(() => {store.subcribe(() => {
setProvider({state: store.getState(),
dispatch: store.dispatch
})
})
}, [store])
const Context = context || ReactReduxContext
return <Context>{props.children}</Context>
}
export default Provider
有了 Provider 组件,咱们还须要一个 connect 函数,connect 函数接管 mapStateToProps 和 mapDispatchToProps,返回一个高阶组件,这个高阶组件接管 React 组件,返回一个 PureComponent;
import ReactReduxContext from './Provider'
export default function connect (mapStateToProps, mapDispatchToProps) {return function (WrappedComment) {return function (props) {
return (
<ReactReduxContext>
{({state, dispatch}) => {<WrappedComment {...mapStateToProps(state, props)} {...mapDispatchToProps(dispatch, props)} />
}
}
</ReactReduxContext>
)
}
}
}
其实这个返回的组件,react-redux 又用 React.memo 进行包装,保障只在 props 发生变化的时候才会从新渲染。react-redux 对于 connect 的实现比这里要简单得多,如结尾提出的问题: react-redux 须要保障父组件的更新在子组件之前,react 的 connect 办法其实是对 connectAdvanced 办法的封装(参见官网),connectAdvanced 办法放回一个高阶组件,如下面所封装的 connect 办法,返回的组件又应用 React.memo 变为 PureComponent, react-redux 如何保障父组件订阅 store 的更新,产生在子组件之前呢?也是通过 subscirpe 办法;每一个 connect 办法返回的高阶组件外部都用了一个 Context.Provider 包装 WrappedComponent, 她的 value 为 store 和 Subscription 对象, 这样每一个子组件的 Subscription 对象能够拿到离它最近的父组件的 Subscription 对象,这样造成了一个 Subscription 对象树,通过 Subvcription 管制更新程序,间接上图
中间件实现
什么是中间件?中间件就是对 dispatch 办法做了封装,比如说每次 dispatch 一个 action 前,我须要发送一条日志,如果不应用中间件,你的做法是