手写一个 Zustand:从 0 到 1 理解轻量级状态管理的核心原理

13 阅读4分钟

在 React 面试中,状态管理几乎是必问的一个点。

很多人会说 Redux、Zustand、MobX,但一旦被问:

“zustand 为什么这么轻?它是怎么做到按需更新的?”

就容易卡住。

这篇文章,我们手写一个简易版 zustand,带你彻底搞懂它的核心设计。


一、为什么需要 Zustand?

先从本质讲清楚,不然源码是死的。

在 React 中,我们常见问题是:

1. 组件通信复杂

A -> B -> C -> D 传 props

层级一深:

  • props drilling(层层传递)
  • 维护成本高
  • 可读性差

2. 状态无法统一管理

比如:

  • 用户信息
  • 登录状态
  • 主题配置

这些都是全局共享状态


3. Redux 太重

Redux 的问题:

  • action
  • reducer
  • dispatch
  • store

学习成本高,样板代码多


Zustand 的优势:

  • API 极简
  • 无 Provider
  • 支持按需更新(性能好)
  • 代码量少

一句话总结:

Zustand = 一个“带订阅能力的全局 store + React Hook”


二、核心设计思想

Zustand 本质只有三件事:

1. 存状态(store)

state = {
  count: 0
}

2. 改状态(setState)

setState({ count: 1 })

3. 响应更新(subscribe)

listeners.forEach(fn => fn())

核心模式:发布-订阅

可以类比:

  • 抖音博主(store)
  • 粉丝(组件)
  • 发布视频(state 更新)

一旦更新:

所有订阅者收到通知


三、实现一个最小 Store

先不管 React,写一个纯 JS 版本。

1. createStore

const createStore = (createState) => {
  let state
  const listeners = new Set()

  const getState = () => state

  const setState = (partial, replace = false) => {
    const nextState =
      typeof partial === 'function' ? partial(state) : partial

    if (!Object.is(nextState, state)) {
      const previousState = state

      state = replace
        ? nextState
        : typeof nextState !== 'object' || nextState === null
        ? nextState
        : Object.assign({}, state, nextState)

      listeners.forEach(listener => listener(state, previousState))
    }
  }

  const subscribe = (listener) => {
    listeners.add(listener)
    return () => listeners.delete(listener)
  }

  const destroy = () => {
    listeners.clear()
  }

  const api = {
    setState,
    getState,
    subscribe,
    destroy
  }

  state = createState(setState, getState, api)

  return api
}

面试重点讲解

1. 为什么用 Set?

const listeners = new Set()

优点:

  • 自动去重
  • 删除方便
  • 性能好

2. 为什么支持函数式 setState?

set((state) => ({ count: state.count + 1 }))

原因:

避免闭包拿到旧状态(类似 React setState)


3. 为什么要 Object.assign?

Object.assign({}, state, nextState)

实现“局部更新”


四、接入 React:实现 useStore

现在 store 有了,但 React 还用不了。

我们需要一个 Hook:

useStore

import { useEffect, useState } from 'react'

const useStore = (api, selector) => {
  const [, forceRender] = useState(0)

  useEffect(() => {
    const unsubscribe = api.subscribe((state, prevState) => {
      const newSelected = selector(state)
      const oldSelected = selector(prevState)

      if (newSelected !== oldSelected) {
        forceRender(Math.random())
      }
    })

    return unsubscribe
  }, [])

  return selector(api.getState())
}

面试重点

1. selector 是核心!

const count = useStore(store, state => state.count)

只订阅你需要的部分


2. 为什么要对比新旧值?

if (newSelected !== oldSelected)

避免无意义渲染(zustand 性能核心)


3. 为什么强制渲染?

forceRender(Math.random())

React 没有“手动刷新”,只能:

改 state 触发更新


五、实现 create(Zustand 精髓)

Zustand 最经典的 API:

const useStore = create(...)

我们来实现:

export const create = (createState) => {
  const api = createStore(createState)

  const useBoundStore = (selector = state => state) => {
    return useStore(api, selector)
  }

  Object.assign(useBoundStore, api)

  return useBoundStore
}

这里是灵魂设计(面试必讲)

1. Hook + Store 合体

useCounterStore()
useCounterStore.setState()

一个函数,两种能力:

  • Hook(组件用)
  • API(外部用)

2. Object.assign 的作用

Object.assign(useBoundStore, api)

把 store 方法挂到 hook 上


六、完整使用示例

创建 Store

const useCounterStore = create((set, get) => ({
  count: 0,
  text: '初始文本',

  increment: () => set(state => ({ count: state.count + 1 })),
  updateText: (text) => set({ text })
}))

组件 A:只订阅 count

const count = useCounterStore(state => state.count)

text 变化不会触发渲染


组件 B:只订阅 text

const text = useCounterStore(state => state.text)

count 变化不会影响它


组件 C:直接操作 API

useCounterStore.setState({
  count: 100
})

七、Zustand 为什么性能好?

核心就一句话:

按需订阅 + 精准更新


对比 Redux

Redux:

state 变了 → 所有 connect 组件可能更新

Zustand:

只更新 selector 命中的组件

本质原因

selector(state)

每个组件只关心一小部分数据


八、你可以在面试中这样说

如果面试官问:

Q:zustand 原理是什么?

你可以这样答:


第一层(基础版)

zustand 本质是一个基于发布订阅的状态管理库,通过 store 存储状态,通过 subscribe 监听变化,然后用 hook 绑定 React,实现响应式更新。


第二层(进阶版)

它的核心优化在于 selector,组件只订阅自己关心的状态,通过新旧值对比来避免不必要渲染。


第三层(源码级)

内部通过 createStore 创建 store,维护 listeners 集合,setState 时触发订阅,useStore 通过 useEffect 自动订阅,实现 React 组件更新。


九、总结

我们手写 zustand,其实只做了 4 件事:

1. 存状态

let state

2. 改状态

setState()

3. 订阅更新

subscribe()

4. 绑定 React

useStore()

最后一句话总结

Zustand 并不神秘,它只是把“发布订阅 + Hook”做到了极致简洁。