【源码共读】| 实现React-Redux

在上一篇文章中,我们已经实现了一个简易版的redux,并且成功地将其应用到React中。

然而,目前存在一个问题:每次使用时都需要重复编写订阅和派发的逻辑,这不利于组件的复用。为了解决这个问题,社区提供了一些解决方案,其中最常见的方案React-redux。那么,如果我们来实现一个这样的库,应该怎么实现呢?


我们先来看下使用

  • 父组件提供Provider传递store
  • 子组件中使用connect接收statedispatchAction
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...

可以看到,状态共享通过Context实现的。

类组件实现

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
}

至此,我们已经可以在子组件中拿到statedispatch

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)

至此,我们已经基本实现类组件的react-redux

函数组件实现

github.com/reduxjs/rea...

在函数式组件中,我们是以如下形式使用的

  • 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-redux的全部功能

优化

在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
}

通过上述的修改,我们使用了react18的API,进一步的简化了代码。

总结

通过实现react-redux库,我们深入了解到这个库是为了简化redux在react中的使用步骤。它通过使用Context来提供全局的状态管理,并提供了Provider和connect来简化数据的传递。此外,引入mapStateToProps和mapDispatchToProps来获取所需的数据处理。

相关推荐
如若12334 分钟前
对文件内的文件名生成目录,方便查阅
java·前端·python
滚雪球~1 小时前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语1 小时前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport1 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg1 小时前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全
胡西风_foxww2 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
m0_748254882 小时前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui
星就前端叭2 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
m0_748234522 小时前
前端Vue3字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin)
前端·webpack·node.js
Web阿成2 小时前
3.学习webpack配置 尝试打包ts文件
前端·学习·webpack·typescript