前言
学习 react-redux 源码并自己实现其主要功能;学习使我快乐~ 敲代码使我快乐~
手写一个简单的 react-redux
react-redux 库将 react 和 redux 结合起来,使 react 项目中可以更加方便的使用 redux~
react-redux
主要 API 有: Provider
、connect
、bindActionCreators
、useSelector
、useDispatch
、
Provider
Provider
组件通过包裹应用根组件通过 ReactContext 将 store 数据传给子孙组件;
- 使用
1 2 3 4 5 6 7 8
| import { Provider } from './myRedux'
ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
|
- 实现
1 2 3 4 5 6 7
| import React from 'react'
const ReduxContext = React.createContext()
export const Provider = ({ store, children }) => { return <ReduxContext.Provider value={store}>{children}</ReduxContext.Provider> }
|
connect
connect
是一个高阶组件 HOC,主要作用是将 store 中的state
和dispatch
映射到组件,以下实现仅写了最常用的mapStateToProps
和mapDispatchToProps
;
- 使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @connect(({ count }) => ({ count }), { add: () => ({ type: 'ADD' }), minus: () => ({ type: 'MINUS' }), }) class ReactReduxPage extends React.Component { render() { const { count, dispatch, add, minus } = this.props
return ( <div> <h1>ReactReduxPage</h1> <p>{count}</p> <button onClick={add}>ADD</button> <button onClick={minus}>MINUS</button> <button onClick={() => { dispatch({ type: 'ADD' }) }} > dispatch </button> </div> ) } }
|
- 实现(代码里有详细解释)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
export const connect = (mapStateToProps, mapDispatchToProps) => ( WrappedComponnet ) => (props) => { const store = useContext(ReduxContext)
const stateProps = mapStateToProps(store.getState())
let dispatchProps = { dispatch: store.dispatch }
if (typeof mapDispatchToProps === 'function') { dispatchProps = mapDispatchToProps(store.dispatch) } else if (typeof mapDispatchToProps === 'object') { dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch) }
const forceUpdate = useForceUpdate()
useLayoutEffect(() => { store.subscribe(() => { forceUpdate() }) }, [store])
return <WrappedComponnet {...props} {...stateProps} {...dispatchProps} /> }
|
bindActionCreators
这个方法主要是对mapDispatchToProps
做处理使用的,它会将dispatch
和action
组合起来;当mapDispatchToProps
为函数类型的时候mapDispatchToProps
函数体里面需要用到,当mapDispatchToProps
为对象的时候,connect
内部也需要对传入的对象做处理,这部分可查看上面connect
实现代码第20行
;
- 使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ...
const mapDispatchToProps = (dispatch) => {
let creators = { add: () => ({ type: 'ADD' }), minus: () => ({ type: 'MINUS' }), }
creators = bindActionCreators(creators, dispatch) return { dispatch, ...creators } }
export default connect(mapStateToProps, mapDispatchToProps)(ReactReduxPage)
|
- 实现
实现其实很简单,主要就是对传入的creator
循环处理,给action
套上dispatch
;
1 2 3 4 5 6 7 8 9 10 11 12
| const bindActionCreator = (creator, dispatch) => { return (...agrs) => dispatch(creator(...agrs)) }
export const bindActionCreators = (creator, dispatch) => { let obj = {} for (let key in creator) { obj[key] = bindActionCreator(creator[key], dispatch) } return obj }
|
useSelector、useDispatch
useSelector
和useDispatch
是react-redux
提供的 hook 方法,主要是针对函数组件使用 redux 的;
- 使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import React from 'react'
import { useSelector, useDispatch } from '../myRedux'
const ReactReduxHookPage = () => { const count = useSelector(({ count }) => count) const dispatch = useDispatch()
const add = () => { dispatch({ type: 'ADD' }) }
const minus = () => { dispatch({ type: 'MINUS' }) }
return ( <div> <h1>ReactReduxHookPage</h1> <p>{count}</p> <button onClick={add}>ADD</button> <button onClick={minus}>MINUS</button> <button onClick={() => { dispatch({ type: 'ADD' }) }} > dispatch </button> </div> ) }
export default ReactReduxHookPage
|
- 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| export const useSelector = (selector) => { const store = useContext(ReduxContext) const selectedState = selector(store.getState())
const forceUpdate = useForceUpdate()
useLayoutEffect(() => { store.subscribe(() => { forceUpdate() }) }, [store])
return selectedState }
export const useDispatch = () => { const store = useContext(ReduxContext) return store.dispatch }
|
myReactRedux 完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| import React, { useContext, useLayoutEffect } from 'react' import { useForceUpdate } from '../hooks'
const ReduxContext = React.createContext()
export const Provider = ({ store, children }) => { return <ReduxContext.Provider value={store}>{children}</ReduxContext.Provider> }
export const connect = (mapStateToProps, mapDispatchToProps) => ( WrappedComponnet ) => (props) => { const store = useContext(ReduxContext)
const stateProps = mapStateToProps(store.getState())
let dispatchProps = { dispatch: store.dispatch }
if (typeof mapDispatchToProps === 'function') { dispatchProps = mapDispatchToProps(store.dispatch) } else if (typeof mapDispatchToProps === 'object') { dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch) }
const forceUpdate = useForceUpdate()
useLayoutEffect(() => { store.subscribe(() => { forceUpdate() }) }, [store])
return <WrappedComponnet {...props} {...stateProps} {...dispatchProps} /> }
export const useSelector = (selector) => { const store = useContext(ReduxContext) const selectedState = selector(store.getState())
const forceUpdate = useForceUpdate()
useLayoutEffect(() => { store.subscribe(() => { forceUpdate() }) }, [store])
return selectedState }
export const useDispatch = () => { const store = useContext(ReduxContext) return store.dispatch }
const bindActionCreator = (creator, dispatch) => { return (...agrs) => dispatch(creator(...agrs)) }
export const bindActionCreators = (creator, dispatch) => { let obj = {} for (let key in creator) { obj[key] = bindActionCreator(creator[key], dispatch) } return obj }
|
useForceUpdate
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { useState, useCallback } from 'react'
export const useForceUpdate = function () { const [count, setCount] = useState(0) const update = useCallback(() => { setCount(prev => prev + 1) }, [])
return update }
|