阅读react-redux源码(七) - 实现一个react-redux

首先总结react-redux的实现原理,然后根据原理的指导来完成一个简易的react-redux。

  1. 之前说过可以引起React组件更新的元素有两种,一个是props的更新,一个是state的更新,但是props的跟新放眼整个react应用也是state的更新。所以React应用中组件重新渲染只能通过state的更新。
  2. 在React中需要跨层级传递消息可以使用Context。
  3. 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的变动,并且还能做到规避不必要的更新。

猜你喜欢

转载自blog.csdn.net/letterTiger/article/details/107747285