如何用 30 行代码实现一个 React 全局状态管理工具

177 阅读1分钟

useSyncExternalStore

useSyncExternalStore 是一个 React Hooks,允许我们订阅外部 store 的 React Hook,详细请看 zh-hans.react.dev/reference/r…

一些 React 的全局状态库也是基于 useSyncExternalStore 开发的,例如 Zustand、Redux 8.0 等

实现思路

首先我们需要确认自己实现的全局状态管理的 api 调用:

  1. 首先创建一个 store
import { createStore } from '@plumbiu/react-store'

const countStore = createStore({
  count: 1,
  inc() {
    return {
      count: this.count + 1,
    }
  },
})

export default countStore
  1. 在组件中调用:
import countStore from './store'
import { useStore } from '@plumbiu/react-store'

function App() {
  const store = useStore(countStore)
  
  return <div>
      <div>count: {store.count}</div>
      <button onClick={data.inc}>inc</div>
  </div>
}
  1. 书写代码

这里直接贴核心代码,代码都注释了:

import { useSyncExternalStore } from 'react'

// createStore
export function createStore(state) {
  // 定义重新渲染的 callback
  let listeners = []
  // 执行重新渲染
  function emitChange() {
    listeners.forEach(fn => fn())
  }
  // 遍历我们定义的 state 属性,将函数重新赋值,以便执行 emitChange 方法触发重新渲染
  for (const key in state) {
    const fn = state[key]
    if (typeof fn !== 'function') {
      continue
    }
    state[key] = (action: any) => {
      // 重新赋值
      state = fn.call(state, action) ?? {}
      // 赋值后调用 emitChange 重新渲染
      emitChange()
    }
  }
  // 返回的数据
  return {
    $subscribe(listener: Function) {
      listeners = listeners.concat(listener)
      return () => (listeners = listeners.filter((l) => l !== listener))
    },
    $getSnapshot() {
        return state
    },
  }
}

// useStore 比较简单,就是 createStore 的返回值作为 useSyncExternalStore 的参数
export function useStore(store) {
  return useSyncExternalStore(store.$subscribe, store.$getSnapshot)
}

最后

上面的代码是大致的代码,还有一些小问题,例如在给 state 重新赋值的时候,应该先浅比较一下数据有没有更改,还有类似避免全局订阅的情况

上述问题解决具体可看 @plumbiu/react-store 中的代码(求一波 star