手写一个简易的Hooks风格多模块Redux

在用React开发项目时,多多少少需要状态管理,各种状态管理库层出不穷。但有时候一个项目不需要太重的状态管理库。React 16.8后推出了Hooks写法,我们可以利用这一个新特性,手写一个Hooks风格的简易多模块Redux。

默认已经熟悉各种Hooks的用法

1 使用Hooks创建状态上下文

# src/redux/index.js

import React from 'react'

const StateContext = React.createContext()
const DispatchContext = React.createContext()

export const Provider = ({ children, store }) => {
  const [state, dispatch] = React.useReducer(store.reducers, store.state)

  return (
    <StateContext.Provider value={state}>
      <DispatchContext.Provider value={dispatch}>
        {children}
      </DispatchContext.Provider>
    </StateContext.Provider>
  )
}

export const useRedux = () => {
  const state = React.useContext(StateContext)
  const dispatch = React.useContext(DispatchContext)

  if (state === void 0 || dispatch === void 0) {
    throw new Error('useRedux must be used whithin a Provider')
  }

  return [state, dispatch]
}


  1. 我们创建了StateContextDispatchContext用于管理state和dispatch的状态。当然这里可以直接创建单独的storeContext进行管理。这样分开定义有助于理解。
  2. 定义Provider方法,接收两个参数childrenstore,用于接收传递的子组件和store状态。
  3. 利用useReducer的特性,可以获得类似redux的state和dispatch。
  4. 分别将statedispatch赋值给由StateContextDispatchContext提供的Provider,这样statedispatch将会在整个节点树上传递下去。
  5. 自定义HooksuseRedux,将定义好的statedispatch返回出去。

2 如何实现多模块的Redux

在项目的src/store中分别创建index.jscounter.jsuser.js

# /src/store/user.js

const UserModule = {
  state: {
    token: ''
  },
  reducer: (state, action) => {
    switch (action.type) {
      case 'SET_TOKEN':
        return { ...state, token: 'token-string' }
      case 'RESET_TOKEN':
        return { ...state, token: '' }
      default:
        return state
    }
  }
}

export default UserModule
# /src/store/counter.js

const CounterModule = {
  state: {
    count: 0,
  },
  reducer: (state, action) => {
    switch (action.type) {
      case 'increment':
        return { ...state, count: state.count + 1 }
      case 'decrement':
        return { ...state, count: state.count - 1 }
      default:
        return state
    }
  },
}

export default CounterModule

# src/store/index.js

import { combineReducer } from '../utils'
import UserModuel from './user'
import CounterModule from './counter'

const state = {
  user: UserModuel.state,
  counter: CounterModule.state
}

const reducers = combineReducer({
  user: UserModuel.reducer,
  counter: CounterModule.reducer
})

export default {
  state,
  reducers
}

很常规的分模块定义statereducer。 在index.js中,我们使用了combineReducer这个自定义方法将多个reducer合并。这里也是模仿Redux的实现方法写了一个简易的combineReducer

# /src/utils/index.js

export const combineReducer = reducers => (state = {}, action) =>
  Object.keys(reducers).reduce((newState, key) => {
    newState[key] = reducers[key](state[key], action)
    return newState
  }, {})

3 如何使用

在项目根目录的index.js中引入Provider和定义好的store

# src/index.js

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import * as serviceWorker from './serviceWorker'
import { Provider } from './redux'
import store from './store'

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
)

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister()

使用ProviderApp包裹,传入store。和使用Redux一模一样的使用方法。

# src/App.js

import React from 'react'
import logo from './logo.svg'
import './App.css'
import { useRedux } from './redux'

function App() {
  const [state, dispatch] = useRedux()

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>Count:{state.counter.count}</p>
        <button onClick={() => dispatch({ type: 'increment' })}>+</button>
        <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
        <p>Token:{state.user.token}</p>
        <button onClick={() => dispatch({ type: 'SET_TOKEN' })}>
          SET TOKEN
        </button>
        <button onClick={() => dispatch({ type: 'RESET_TOKEN' })}>
          RESET TOKEN
        </button>
      </header>
    </div>
  )
}

export default App

引入之前定义好的useRedux,开始尽情使用吧。

这只是一个简易的Redux实现,其中代码还有很多瑕疵和需要改进的地方。但是在中小型项目中已经足够使用了。

(动图中的变量名可能和代码有出入,请忽略这个细节,逃)。

本文使用 mdnice排版

原文链接:juejin.im

上一篇:JS代码简洁之道--函数
下一篇:用 shell 脚本批量拉取更新 Antd 依赖库,rc-component

相关推荐

  • 🔥手写大厂前端知识点源码系列(上)

    如今前端攻城狮的要求越来越高,会使用常见的API已经不能满足现如今前端日益快速发展的脚步。现在大厂基本都会要求面试者手写前端常见API的原理,以此来证明你对该知识点的理解程度。

    4 个月前
  • 🔥前端面试大厂手写源码系列(上)

    如今前端攻城狮的要求越来越高,会使用常见的API已经不能满足现如今前端日益快速发展的脚步。现在大厂基本都会要求面试者手写前端常见API的原理,以此来证明你对该知识点的理解程度。

    4 个月前
  • 🔥0202年了,几个基础的手写函数总得会吧

    这几天看到一个大三大佬面试字节跳动的蚊子,突然觉得自己太辣鸡了···校招的题我一半多都不会啊···赶紧潜下心来学习学习提(an)高(wei)自己,边翻掘金边谷歌,简单实现了几个常用函数···(借鉴了太...

    4 个月前
  • (转载)如何通俗易懂的理解Redux

    作者:Wang Namelos 链接:https://www.zhihu.com/questio...(https://www.zhihu.com/question/41312576/answer/9...

    2 年前
  • 面试!你真的准备好了吗?|手写API系列梳理

    "不畏惧,不讲究,未来的日子好好努力"——大家好!我是小芝麻😄 标题党,它又、又、又来了...... (/public/upload/ce1d43cfb86634a10b9c8a07be33...

    2 天前
  • 面试高频JS考查点手写实现

    原文链接 考查 this call、apply bind new 链式调用 考查原型链 instanceof 组合寄生继承 Object.create ...

    4 个月前
  • 面试题|手写JSON解析器

    这周的 Cassidoo 的每周简讯有这么一个面试题:: 写一个函数,这个函数接收一个正确的 JSON 字符串并将其转化为一个对象(或字典,映射等,这取决于你选择的语言)。

    5 个月前
  • 面试题:Hooks 与 React 生命周期的关系

    React 生命周期很多人都了解,但通常我们所了解的都是 单个组件 的生命周期,但针对 Hooks 组件、多个关联组件(父子组件和兄弟组件) 的生命周期又是怎么样的喃?你有思考和了解过吗,接下来我们将...

    1 年前
  • 面试题里的那些各种手写

    最近准备初级前端面试,发现有很多手写实现什么的,例如什么手写实现bind,promise。手写ajax,手写一些算法。 翻阅了很多书籍和博客。 这里做一个总结改进,算是对我后面大概为期一个月找工作的...

    1 年前
  • 面试还问redux?那我从头手撸源码吧(中间件篇)

    昨天的文章手写了一版redux的核心源码(https://segmentfault.com/a/1190000016799698),redux库除了数据的状态管理还有一块重要的内容那就是中间件,今天我...

    2 年前

官方社区

扫码加入 JavaScript 社区