【源码共读】| 实现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来获取所需的数据处理。

相关推荐
雪碧聊技术4 小时前
前端项目代码发生改变,如何重新部署到linux服务器?
前端·vue3·centos7·代码更新,重新部署
liulilittle4 小时前
C++ 浮点数封装。
linux·服务器·开发语言·前端·网络·数据库·c++
wordbaby4 小时前
Expo 进阶指南:赋予 TanStack Query “原生感知力” —— 深度解析 AppState 与 NetInfo
前端·react native
Moment5 小时前
从美团全栈化看 AI 冲击:前端转全栈,是自救还是必然 🤔🤔🤔
前端·后端·面试
天问一5 小时前
使用 Vue Router 进行路由定制和调用的示例
前端·javascript·vue.js
韩立学长6 小时前
【开题答辩实录分享】以《基于Vue的非遗文化知识分享平台的设计与实现》为例进行选题答辩实录分享
前端·javascript·vue.js
优弧6 小时前
离开舒适区100天,我后悔了吗?
前端·后端·面试
胡gh6 小时前
css的臂膀,前端动效的利器,还是布局的“隐形陷阱”?
前端·css·html
灵感菇_7 小时前
Flutter Riverpod 完整教程:从入门到实战
前端·flutter·ui·状态管理
用户21411832636027 小时前
紧急修复!Dify CVE-2025-55182 高危漏洞,手把手教你升级避坑
前端