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

相关推荐
然我5 分钟前
别再只用 base64!HTML5 的 Blob 才是二进制处理的王者,面试常考
前端·面试·html
NanLing7 分钟前
【纯前端推理】纯端侧 AI 对象检测:用浏览器就能跑的深度学习模型
前端
呆呆的心9 分钟前
前端必学:从盒模型到定位,一篇搞定页面布局核心 🧩
前端·css
小飞悟9 分钟前
前端高手才知道的秘密:Blob 居然这么强大!
前端·javascript·html
小old弟10 分钟前
用Sass循环实现炫彩文字跑马灯效果
前端
code_YuJun10 分钟前
Promise 基础使用
前端·javascript·promise
Codebee10 分钟前
OneCode自主UI设计体系:架构解析与核心实现
前端·javascript·前端框架
GIS之路13 分钟前
GIS 空间关系:九交模型
前端
xiguolangzi20 分钟前
vue3+element-plus el-table列的显隐、列宽 持久化
前端·javascript·vue.js
用户68238060322522 分钟前
前端会用到的数据结构--堆(HEAP)
前端