- redux目录。该目录下是手写redux源码实现的。对应的官方Redux版本:4.0.0
- applyMiddleware
- combineReducers
- createStore
- react-redux目录。该目录是手写react redux源码实现的。对应的官方React Redux版本:7.2.6
- Connect
- Context
- Provider
- useSelector
- useDispatch
- 如何在组件之间共享数据,而不用通过组件树逐层传递props
- 共享数据更新,如何通知组件
- React.context理论知识
- Redux基础知识
redux只是一种架构模式,它不关注我们到底用什么库,可以把它应用到React和Vue中。
redux中比较重要的几个概念:Reducers,Actions,Store
- reducers是一个纯函数,接收一个state和一个action对象,然后根据action.type更新state并返回一个新的state。
- actions是一个对象,包含一个type属性和一个payload属性。
- Store是createStore方法的返回值,提供了getState方法获取当前最新的state。dispatch方法触发state更新。subscribe方法订阅state状态变化的回调。
react redux无非就是将context和redux架构结合实现的react共享状态管理方法。
react redux最主要的就是提供了connect方法,Provider组件将context从react组件中剥离出来,使得我们所写的组件能够和context解耦。
react redux无非就是将context和redux思想结合起来,使得context能和我们的业务组件解耦并且方便状态管理。
react redux最主要的两个API就是context方法和Provider组件。
其中,Provider组件比较简单,接收一个store对象。这个store对象就是redux的createStore方法的返回值,包含getState,dispatch,subscribe方法。 然后Provider组件创建一个context,包含store的值,并在render方法原封不动的渲染子组件。
connect方法接收一个mapStateToProps和一个mapDispatchToProps方法。并返回一个函数,这个函数接收一个组件,并且返回值是一个高阶组件。 这个高阶组件主要做了以下几件事:
订阅context,读取store对象,调用store.subscribe监听状态修改,然后执行更新操作。更新操作里面调用mapStateToProps以及mapDispatchToProps 方法,并将两个方法的返回值当作Props传递给包裹的组件。
/**
* reducers是个纯函数,函数类型:(state, action) => newState
* */
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case 'counter/incremented':
return { value: state.value + 1 }
case 'counter/decremented':
return { value: state.value - 1 }
default:
return state
}
}
//createStore返回值{ subscribe, dispatch, getState }.
let store = createStore(counterReducer)
store.subscribe(() => console.log(store.getState()))
store.dispatch({ type: 'counter/incremented' })
简易版的Redux,主要实现Redux的 createStore
方法
function createStore (reducer) {
let state = null
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach((listener) => listener())
return action;
}
dispatch({}) // 初始化 state
return { getState, dispatch, subscribe }
}
简易版的React Redux
const ReactReduxContext = React.createContext(null)
class Provider extends React.Component {
render () {
return (
<ReactReduxContext.Provider value={{store: this.props.store}}>
{this.props.children}
</ReactReduxContext.Provider>
)
}
}
const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
class Connect extends React.Component {
constructor (props) {
super(props)
this.state = {
allProps: {}
}
}
componentWillMount () {
const { store } = this.context
this._updateProps()
store.subscribe(() => this._updateProps())
}
_updateProps () {
const { store } = this.context
let stateProps = mapStateToProps
? mapStateToProps(store.getState(), this.props)
: {} // 防止 mapStateToProps 没有传入
let dispatchProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, this.props)
: {} // 防止 mapDispatchToProps 没有传入
this.setState({
allProps: {
...stateProps,
...dispatchProps,
...this.props
}
})
}
render () {
return <WrappedComponent {...this.state.allProps} />
}
}
Connect.contextType = ReactReduxContext
return Connect
}
我认为applyMiddleware
是整个redux源码中比较有意思的。这个和koa的洋葱圈模型一模一样。
先来看下用法,具体用法点击这里查看
let store = createStore(counter, undefined, applyMiddleware(logger, logger2, logger3))
看下createStore源码有这么一段
// 记住 createStore的返回值一定是个包含dispatch,subscribe,getState的对象
const createStore = (reducer, preloadedState, enhancer) => {
.....
if(enhancer){
return enhancer(createStore)(reducer, preloadedState)
}
.....
return {
dispatch,
subscribe,
getState,
}
}
从这里可以推断出以下几点信息:
- 1.
createStore
的返回值一定是个store
对象,包含dispatch
,subscribe
,getState
。那么return enhancer(createStore)(reducer, preloadedState)
结果一定是返回一个store
对象 - 2.
applyMiddleware()
执行返回一个函数enhancer
- 3.
enhancer
这个函数接受createStore
作为参数,并返回一个函数,暂且称为F
。 - 4.函数
F
接受reducer
以及preloadedState
做为参数,并且最终返回一个store
对象。 - 5.然后我们又知道,
redux
中间件本质上就是拦截store.dispatch
方法
基于以上几点信息,我们可以反向勾勒出applyMiddleware
的大致实现
const applyMiddleware = (...middlewares) => {
return (createStore) => {
return (reducer, preloadedState) => {
const store = createStore(reducer, preloadedState)
const dispatch = (action) => {
// todo
}
return {
...store,
dispatch
}
}
}
}
剩下的就是如何实现 dispatch
方法。从测试例子可以看出,中间件的执行结果有点类似于洋葱圈模型:
。
假设只有三个中间件,我们可以很快实现出这个逻辑:
const applyMiddleware = (...middlewares) => {
return (createStore) => {
return (reducer, preloadedState) => {
const store = createStore(reducer, preloadedState)
const mids = middlewares.map(mid => mid({ getState: store.getState }))
const mid1 = mids[0]
const mid2 = mids[1]
const mid3 = mids[2]
const dispatch = (action) => {
const chain = mid1(mid2(mid3(store.dispatch)))
return chain(action)
}
return {
...store,
dispatch
}
}
}
}
基于上述,可以使用函数组合的概念使得这个函数可以支持无限个中间件
const applyMiddleware = (...middlewares) => {
return (createStore) => {
return (reducer, preloadedState) => {
const store = createStore(reducer, preloadedState)
const mids = middlewares.map(mid => mid({ getState: store.getState }))
const dispatch = (action) => {
const chain = mids.reverse().reduce((arg, f) => { return f(arg)}, store.dispatch)
return chain(action)
}
return {
...store,
dispatch
}
}
}
}
再换种更直观的方式:
const applyMiddleware = (...middlewares) => {
return (createStore) => {
return (reducer, preloadedState) => {
const store = createStore(reducer, preloadedState)
const mids = middlewares.map(mid => mid({ getState: store.getState }))
const dispatch = (action) => {
return next(0)(action)
function next(i){
if(i === mids.length) return store.dispatch;
const mid = mids[i]
return mid(next(i + 1))
}
}
return {
...store,
dispatch
}
}
}
}
<MyContext.Provider value={/* 某个值 */}>
Provider
接收一个 value
属性。当 value
变化时,所有订阅的组件都会强制刷新,不受限于 shouldComponentUpdate
。也就是说,只要 Provider
的value
发生改变,那么react就一定会从上至下重新渲染
但是在 react-redux
的场景中(如下:),store
的值理论上是不会发生改变的,因为store
是由redux.createStore
创建出来的固定的一个引用。那 react-redux
是如何监听状态变化并更新组件的?
const Root = () => {
console.log('Root....')
return (
<Provider store={store}>
<App step={2} />
</Provider>
)
}
ReactDOM.render(
<Root />,
document.getElementById('root')
)
react redux
状态更新需要注意几点
Provider
的store
由于在创建初期引用就已经保持不变,因此理论上Provider的store在整个应用声明周期内都不会发生改变,也就不会触发订阅的组件重新渲染
ReactDOM.render(
<Provider store={store}>
<App step={2} />
</Provider>,
document.getElementById('root')
)
- 通过
dispatch({ type: 'counter/decrementedNum' })
触发redux
状态更新,改变的是store.state
,而不是store
自身,自然也不会导致react
整个应用自上而下重渲染 react redux
通过Connect
组件以及useSelector
钩子使用store.subscribe(checkForUpdates)
订阅store.state
状态更新Connect(mapStateToProps, mapDispatchToProps)(Button)
。在checkForUpdates
中会重新执行mapStateToProps
、mapDispatchToProps
方法并且使用浅比较比较子组件,比如Button
订阅的状态是否发生改变,如果发生改变,则手动触发Button
组件重新渲染const selector = state => state.number; const state = useSelector(selector)
。在checkForUpdates
会重新执行selector
方法并使用浅比较比较状态是否发生改变,如果发生改变则触发组件重新渲染