- 阅读react-redux源码 - 零
- 阅读react-redux源码 - 一
- 阅读react-redux源码(二) - createConnect、match函数的实现
- 阅读react-redux源码(三) - mapStateToPropsFactories、mapDispatchToPropsFactories和mergePropsFactories
- 阅读react-redux源码(四) - connectAdvanced、wrapWithConnect、ConnectFunction和checkForUpdates
- 阅读react-redux源码(五) - connectAdvanced中store改变的事件转发、ref的处理和pure模式的处理
- 阅读react-redux源码(六) - selectorFactory处理store更新
- 阅读react-redux源码(七) - 实现一个react-redux
首先总结react-redux的实现原理,然后根据原理的指导来完成一个简易的react-redux。
- 之前说过可以引起React组件更新的元素有两种,一个是props的更新,一个是state的更新,但是props的跟新放眼整个react应用也是state的更新。所以React应用中组件重新渲染只能通过state的更新。
- 在React中需要跨层级传递消息可以使用Context。
- Redux可以通过subscribe来订阅store中state的更新。
react-redux是通过Context来向下跨组件传递store,然后后代组件通过某些方法来监听store中state的变化,后代组件监听到变化之后设置state来引起自身的更新。
在这其中需要注意的是后代组件监听的state之外的state变动需要避免,不要引起自身重新渲染,还有父组件重新render,子组件关注的props没有更新也需要避免重新render。
ReactRedux的使用方式
首先创建store:
import {
createStore } from 'redux'
const UPDATE_A = 'UPDATE_A'
export function createUpdateA (payload) {
return {
type: UPDATE_A,
payload
}
}
const initialState = {
a: 1,
b: 1
}
function reducer (state = initialState, action) {
const {
type, payload} = action
switch (type) {
case UPDATE_A:
return Object.assign({
}, state, {
a: payload})
default:
return state
}
}
export default createStore(reducer)
将store提供给react-redux:
import ReactDOM from 'react-dom'
import React from 'react'
import Provider from './Provider'
import store from './store'
import ChildComponentA from './ChildComponentA'
function App () {
return (
<Provider store={
store}>
<CildComponentA />
</Provider>
)
}
ReactDOM.render(<App />, document.querySelector('#root'))
将业务组件通过react-redux连接到store
import React from 'react'
import connect from './connect'
import {
createUpdateA } from './store'
function _ChildComponentA (props) {
console.log('ChildComponentA执行了')
return (
<div>
<p>我是来自store的a:{
props.a}</p>
<p onClick={
() => props.updateA(props.a + 1)}>a+1</p>
</div>
)
}
function mapStateToProps (state) {
return {
a: state.a
}
}
function mapDispatchToProps (dispatch) {
return {
updateA(a) {
dispatch(createUpdateA(a))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(_ChildComponentA)
上面呈现了react-redux的使用方式,主要使用了两个方法,一个是Provider,一个是connect。下面将这两个方法填起来。
react-redux的实现
import {
createContext } from 'react'
export const context = createContext(null)
export default function Provider (props) {
return (
<context.Provider value={
props.store}>
{
props.children}
</context.Provider>
)
}
这样Provider就完成了。
import React, {
useContext, useState, useEffect } from 'react'
import {
context as defaultContext } from './provider'
function mergeProps (stateProps, dispatchProps, ownProps) {
return {
...ownProps, ...stateProps, ...dispatchProps }
}
export default function connectHoc (mapStateToProps = () => ({
}), mapDispatchToProps = () => ({
})) {
return function wrapWithConnect (wrappedComponent) {
function connectFunction (props) {
const [_, setState] = useState(0)
const store = useContext(defaultContext)
useEffect(() => {
return store.subscribe(update)
}, [])
function update () {
setState(times => ++times)
}
const stateProps = mapStateToProps(store.getState())
const dispatchProps = mapDispatchToProps(store.dispatch)
const allProps = mergeProps(stateProps, dispatchProps, props)
return <wrappedComponent {
...allProps} />
}
// 为了阻止父组件render带来的不必要更新
return React.memo(ConnectFunction)
}
}
到这就完成一半了,实现了子组件订阅store变化重新渲染的功能,并且可以避免因为父组件更新导致子组件重新渲染引起的性能问题。
还缺一半是store中不关心的state的更新也会引起子组件渲染,现在即使是更新了store中的bChildComponentA也会执行。
添加代码如下:
store.js:
const UPDATE_B = 'UPDATE_B'
export function createUpdateB (payload) {
return {
type: UPDATE_B,
payload
}
}
function reducer (state = initialState, action) {
const {
type, payload} = action
switch (type) {
...
case UPDATE_B:
return Object.assign({
}, state, {
b: payload})
...
}
}
添加组件文件childComponentB.js:
import React from 'react'
import connect from '../copyReactRedux2/connect'
import {
createUpdateB } from './store.js'
function _ChildComponentB (props) {
console.log('connectFunctionB执行了')
return (
<div>
<p>我是来自store的b:{
props.b}</p>
<p onClick={
() => props.updateB(props.b + 1)}>b+1</p>
</div>
)
}
function mapStateToProps (state) {
return {
b: state.b
}
}
function mapDispatchToProps (dispatch) {
return {
updateB(b) {
dispatch(createUpdateB(b))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(_ChildComponentB)
在index.js中添加代码:
function App () {
return (
...
<CildComponentB />
...
)
}
准备就绪,点击b+1文字会发现控制台中不仅仅会打印 connectFunctionB执行了还会打印connectFunctionA执行了,这并不是我们希望的。
下面来修改connect的实现修复这个问题。
首先实现下mergeProps函数,让它具有对比记忆的特性,如果没有值改变则返回老的mergedProps。
mergeProps函数:
import {
shallowEqual, strictEqual } from './equals'
function mergeProps (stateProps, dispatchProps, ownProps) {
return {
...ownProps, ...stateProps, ...dispatchProps }
}
function mergedPropsFactory() {
let hasOnceRun = false
let stateProps = null
let dispatchProps = null
let ownProps = null
let mergedProps = null
return (newStateProps, newDispatchProps, newOwnProps) => {
debugger
if (!hasOnceRun) {
stateProps = newStateProps
dispatchProps = newDispatchProps
ownProps = newOwnProps
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
hasOnceRun = true
return mergedProps
}
if (shallowEqual(stateProps, newStateProps) && shallowEqual(ownProps, newOwnProps)) {
stateProps = newStateProps
dispatchProps = newDispatchProps
ownProps = newOwnProps
} else {
stateProps = newStateProps
dispatchProps = newDispatchProps
ownProps = newOwnProps
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
}
return mergedProps
}
}
修改wrapWithConnect如下:
function wrapWithConnect (WrappedComponent) {
function connectFunction (props) {
const [_, setState] = useState(0)
const store = useContext(defaultContext)
useEffect(() => {
return store.subscribe(update)
}, [])
function update () {
if (cacheAllProps.current === mergeProps(mapStateToProps(store.getState()), cacheDispatchProps.current, cacheOwnProps.current)) return
setState(times => ++times)
}
const mergeProps = useMemo(() => (mergedPropsFactory()), [])
const stateProps = mapStateToProps(store.getState())
const dispatchProps = mapDispatchToProps(store.dispatch)
const allProps = mergeProps(stateProps, dispatchProps, props)
const cacheAllProps = useRef(null)
const cacheOwnProps = useRef(null)
const cacheStatePros = useRef(null)
const cacheDispatchProps = useRef(null)
useEffect(() => {
cacheAllProps.current = allProps
cacheStatePros.current = stateProps
cacheDispatchProps.current = dispatchProps
cacheOwnProps.current = props
}, [allProps])
return <WrappedComponent {
...allProps} />
}
// 为了阻止父组件render带来的不必要更新
return React.memo(connectFunction)
}
function is(x, y) {
if (x === y) {
return x !== 0 || y !== 0 || 1 / x === 1 / y
} else {
return x !== x && y !== y
}
}
export function shallowEqual(objA, objB) {
if (is(objA, objB)) return true
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false
}
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
if (keysA.length !== keysB.length) return false
for (let i = 0; i < keysA.length; i++) {
if (
!Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false
}
}
return true
}
export function strictEqual (a, b) {
return a === b
}
到这里整个都完整了,上面的代码实现了将React组建连接到redux,响应store中state的变动,并且还能做到规避不必要的更新。
- 阅读react-redux源码 - 零
- 阅读react-redux源码 - 一
- 阅读react-redux源码(二) - createConnect、match函数的实现
- 阅读react-redux源码(三) - mapStateToPropsFactories、mapDispatchToPropsFactories和mergePropsFactories
- 阅读react-redux源码(四) - connectAdvanced、wrapWithConnect、ConnectFunction和checkForUpdates
- 阅读react-redux源码(五) - connectAdvanced中store改变的事件转发、ref的处理和pure模式的处理
- 阅读react-redux源码(六) - selectorFactory处理store更新