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

相关推荐
子兮曰2 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万2 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝2 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋2 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁2 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95272 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大2 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师2 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学2 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端