前言

学习 react-redux 源码并自己实现其主要功能;学习使我快乐~ 敲代码使我快乐~

手写一个简单的 react-redux

react-redux 库将 react 和 redux 结合起来,使 react 项目中可以更加方便的使用 redux~

react-redux 主要 API 有: ProviderconnectbindActionCreatorsuseSelectoruseDispatch

Provider

Provider组件通过包裹应用根组件通过 ReactContext 将 store 数据传给子孙组件;

  1. 使用
1
2
3
4
5
6
7
8
import { Provider } from './myRedux'

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
  1. 实现
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 中的statedispatch映射到组件,以下实现仅写了最常用的mapStateToPropsmapDispatchToProps

  1. 使用
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. 实现(代码里有详细解释)
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
// connect 是一个高阶组件hoc
// conent 有两个() 第一个是 mapStateToProps, mapDispatchToProps ... 第二个是组件
export const connect = (mapStateToProps, mapDispatchToProps) => (
WrappedComponnet
) => (props) => {
// 将store的数据 传到 下面组件
const store = useContext(ReduxContext)

// store.getState()能拿到所有的state的数据,mapStateToProps则是把需要的数据解构出来,挂到组件的props上
const stateProps = mapStateToProps(store.getState())

// 如果不写 mapDispatchToProps , 默认是 dispatch 对象
let dispatchProps = { dispatch: store.dispatch }

// mapDispatchToProps 支持 Object 和 Function 两种类型
if (typeof mapDispatchToProps === 'function') {
dispatchProps = mapDispatchToProps(store.dispatch)
} else if (typeof mapDispatchToProps === 'object') {
// 对象的时候,需要自动把dispatch加上,函数的时候,需要用户在mapStateToProps里面手动bindActionCreators
dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch)
}

// 更新组件方法 useForceUpdate为自定义hook
const forceUpdate = useForceUpdate()

// useEffect__订阅 Dom更新 useEffect会在浏览器绘制后延迟执行
// useLayoutEffect-订阅-Dom更新 useLayoutEffect 没有延迟 同步
useLayoutEffect(() => {
// 副作用不能直接写到函数体,会阻塞UI渲染
// 订阅
store.subscribe(() => {
forceUpdate()
})
}, [store])

return <WrappedComponnet {...props} {...stateProps} {...dispatchProps} />
}

bindActionCreators

这个方法主要是对mapDispatchToProps做处理使用的,它会将dispatchaction组合起来;当mapDispatchToProps为函数类型的时候mapDispatchToProps函数体里面需要用到,当mapDispatchToProps为对象的时候,connect内部也需要对传入的对象做处理,这部分可查看上面connect实现代码第20行

  1. 使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...

// Fucntion 参数是dispatch与ownProps
// !慎重定义ownProps,因为你一旦定义ownProps,那么每当ownProps发生改变的时候,当前的mapStateToProps都会被调用,容易影响性能
const mapDispatchToProps = (dispatch) => {

let creators = {
add: () => ({ type: 'ADD' }),
minus: () => ({ type: 'MINUS' }),
}

creators = bindActionCreators(creators, dispatch)
return { dispatch, ...creators }
}

export default connect(mapStateToProps, mapDispatchToProps)(ReactReduxPage)
  1. 实现
    实现其实很简单,主要就是对传入的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

useSelectoruseDispatchreact-redux提供的 hook 方法,主要是针对函数组件使用 redux 的;

  1. 使用
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 'react-redux'
import { useSelector, useDispatch } from '../myRedux'

const ReactReduxHookPage = () => {
// 类似于 mapStateToProps
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. 实现
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(() => {
// 副作用不能直接写到函数体,会阻塞UI渲染
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>
}

// connect 是一个高阶组件 hoc
// conent 有两个() 第一个是 mapStateToProps, mapDispatchToProps ... 第二个是组件
export const connect = (mapStateToProps, mapDispatchToProps) => (
WrappedComponnet
) => (props) => {
// 将store的数据 传到 下面组件
const store = useContext(ReduxContext)

const stateProps = mapStateToProps(store.getState())

// 如果不写 mapDispatchToProps , 默认是 dispatch 对象
let dispatchProps = { dispatch: store.dispatch }

// mapDispatchToProps 支持 Object 和 Function 两种格式
if (typeof mapDispatchToProps === 'function') {
dispatchProps = mapDispatchToProps(store.dispatch)
} else if (typeof mapDispatchToProps === 'object') {
// 对象的时候,需要自动把dispatch加上,函数的时候,需要用户在mapStateToProps里面手动bindActionCreators
dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch)
}

// 更新hook
const forceUpdate = useForceUpdate()

// useEffect__订阅 Dom更新 useEffect会在浏览器绘制后延迟执行
// useLayoutEffect-订阅-Dom更新 useLayoutEffect 没有延迟 同步
useLayoutEffect(() => {
// 副作用不能直接写到函数体,会阻塞UI渲染
// 订阅
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(() => {
// 副作用不能直接写到函数体,会阻塞UI渲染
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'

/**
* 自定义hook 模拟类组件的forceUpdate
*/
export const useForceUpdate = function () {
const [count, setCount] = useState(0)
const update = useCallback(() => {
setCount(prev => prev + 1)
}, [])

return update
}