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

相关推荐
王哈哈^_^1 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie1 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic2 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿2 小时前
webWorker基本用法
前端·javascript·vue.js
cy玩具3 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
qq_390161773 小时前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test4 小时前
js下载excel示例demo
前端·javascript·excel
Yaml44 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事4 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶4 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json