在上一篇文章中,我们已经实现了一个简易版的redux,并且成功地将其应用到React中。
然而,目前存在一个问题:每次使用时都需要重复编写订阅和派发的逻辑,这不利于组件的复用。为了解决这个问题,社区提供了一些解决方案,其中最常见的方案React-redux
。那么,如果我们来实现一个这样的库,应该怎么实现呢?
我们先来看下使用
- 父组件提供
Provider
传递store
- 子组件中使用
connect
接收state
和dispatchAction
tsx
import React from 'react'
import ReactDOM from 'react-dom'
import TodoApp from './TodoApp'
import { Provider } from 'react-redux'
import store from './redux/store'
// As of React 18
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<TodoApp />
</Provider>
)
tsx
import * as actionCreators from './actionCreators'
// ... Component
const mapStateToProps = (state) => state.partOfState
export default connect(mapStateToProps, actionCreators)(Component)
需求分析:
- 全局共享状态
- 更新组件状态
我们现在看下源码怎么实现
github.com/reduxjs/rea...
类组件实现
Happy path
tsx
import React,{ useContext } from 'react'
const Context = React.createContext()
export function Provider({ store, children }) {
return <Context.Provider value={store}>{children}</Context.Provider>
}
export const connect = () => (WrappedComponent) => (props) => {
const store = useContext(Context)
const { getState, dispatch, subscribe } = store
// 触发更新
const [, forceUpdate] = useReducer((x) => x + 1, 0)
useLayoutEffect(() => {
const unsubscribe = subscribe(() => {
forceUpdate()
})
return () => {
unsubscribe()
}
}, [])
return <WrappedComponent {...props} {...stateProps} {...dispatchProps} />
}
mapStateToProps,mapDispatchToProps实现
功能如所示,一个处理pops
,一个处理dispatch
,用来获取我们需要的数据。
我们只需要在connect
中执行对应的函数,将结果传至子组件即可。
所以,我们可以对原有代码做下修改
tsx
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => (props) => {
const store = useContext(Context)
const { getState, dispatch, subscribe } = store
// mapStateToProps
const stateProps = mapStateToProps(getState())
// mapDispatchToProps
let dispatchProps = { dispatch }
if (typeof mapDispatchToProps === 'function') {
dispatchProps = mapDispatchToProps(dispatch)
} else if (typeof mapDispatchToProps === 'object') {
// bindActionCreators 将action包裹dispatch
dispatchProps = bindActionCreators(mapDispatchToProps, dispatch)
}
// 省略...
return <WrappedComponent {...props} {...stateProps} {...dispatchProps} />
}
tsx
// bindActionCreators
function bindActionCreator(creator, dispatch) {
return (...args) => dispatch(creator(...args))
}
export default function bindActionCreators(creators, dispatch) {
let obj = {}
for (const key in creators) {
obj[key] = bindActionCreator(creators[key], dispatch)
}
return obj
}
至此,我们已经可以在子组件中拿到state
和dispatch
mapDispatchToProps传入函数
tsx
// 伪代码
connect(
(state) => {
return state
},
(dispatch) => {
let creators = {
add: () => ({ type: 'ADD' }),
minus: () => ({ type: 'MINUS' })
}
creators = bindActionCreators(creators, dispatch)
return { dispatch, ...creators }
}
)(WrapComponent)
mapDispatchToProps传入对象
tsx
// 伪代码
connect(
(state) => {
return state
},
{
add: () => ({ type: 'ADD' }),
minus: () => ({ type: 'MINUS' })
}
)(WrapComponent)
函数组件实现
在函数式组件中,我们是以如下形式使用的
- useSelector
- useDispatch
tsx
export default function ReactReduxHookPage(props) {
const count = useSelector(({ count }) => count)
const dispatch = useDispatch()
const add = useCallback(() => {
dispatch({ type: 'ADD' })
}, [])
return (
<div>
<h3>ReactReduxHookPage</h3>
<button onClick={add}>{count}</button>
</div>
)
}
与类组件同理
tsx
export function useSelector(selector) {
const store = useContext(Context)
const { getState, subscribe } = store
const selectState = selector(getState())
// 触发更新
const forceUpdate = useForceUpdate()
useLayoutEffect(() => {
const unsubscribe = subscribe(() => {
forceUpdate()
})
return () => {
unsubscribe()
}
}, [])
return selectState
}
export function useDispatch() {
const store = useContext(Context)
const { dispatch } = store
return dispatch
}
function useForceUpdate() {
const [state, setState] = useState(0)
const update = useCallback(() => {
setState((prev) => prev + 1)
}, [])
return update
}
优化
在React 18 中有一个api 可以订阅外部数据流useSyncExternalStore
所以,我们可以对上述代码做一些简化
tsx
// 高阶函数
export const connect =
(mapStateToProps, mapDispatchToProps) => (WrappedComponent) => (props) => {
const store = useContext(Context)
const { getState, dispatch, subscribe } = store
let dispatchProps = { dispatch }
if (typeof mapDispatchToProps === 'function') {
dispatchProps = mapDispatchToProps(dispatch)
} else if (typeof mapDispatchToProps === 'object') {
dispatchProps = bindActionCreators(mapDispatchToProps, dispatch)
}
const forceUpdate = useForceUpdate()
// useLayoutEffect(() => {
// const unsubscribe = subscribe(() => {
// forceUpdate()
// })
// return () => {
// unsubscribe()
// }
// }, [])
// react 18
const state = useSyncExternalStore(() => {
subscribe(forceUpdate)
}, getState)
const stateProps = mapStateToProps(state)
return <WrappedComponent {...props} {...stateProps} {...dispatchProps} />
}
tsx
// hooks
export function useSelector(selector) {
const store = useContext(Context)
const { getState, subscribe } = store
// const selectState = selector(getState())
// 触发更新
const forceUpdate = useForceUpdate()
// useLayoutEffect(() => {
// const unsubscribe = subscribe(() => {
// forceUpdate()
// })
// return () => {
// unsubscribe()
// }
// }, [])
// react 18
const state = useSyncExternalStore(() => {
subscribe(forceUpdate)
}, getState)
const selectState = selector(state)
return selectState
}
通过上述的修改,我们使用了react
18的API,进一步的简化了代码。
总结
通过实现react-redux库,我们深入了解到这个库是为了简化redux在react中的使用步骤。它通过使用Context来提供全局的状态管理,并提供了Provider和connect来简化数据的传递。此外,引入mapStateToProps和mapDispatchToProps来获取所需的数据处理。