• 简体中文
  • 如何从 v4 迁移到 v5

    我们强烈建议在迁移到 v5 之前,先升级到 v4 的最新版本。这样可以显示所有废弃警告,而不会破坏你的应用。

    v5 中的变更

    • 移除默认导出
    • 移除已废弃的特性
    • 将 React 18 设为最低要求的版本
    • 将 use-sync-external-store 设为 peer 依赖(zustand/traditional 中的 createWithEqualityFn 和 useStoreWithEqualityFn 需要它)
    • 将 TypeScript 4.5 设为最低要求的版本
    • 移除对 UMD/SystemJS 的支持
    • 整理 package.json 中的入口点
    • 移除对 ES5 的支持
    • 设置 setState 的 replace 标志时类型更严格
    • persist 中间件的行为变更
    • 其他小幅改进(技术上属于破坏性变更)

    迁移指南

    使用 shallow 等自定义相等性函数

    v5 中的 create 函数不支持自定义相等性函数。

    如果你使用了 shallow 这类自定义相等性函数, 最简单的迁移方式是改用 createWithEqualityFn。

    // v4
    import { create } from 'zustand'
    import { shallow } from 'zustand/shallow'
    
    const useCountStore = create((set) => ({
      count: 0,
      text: 'hello',
      // ...
    }))
    
    const Component = () => {
      const { count, text } = useCountStore(
        (state) => ({
          count: state.count,
          text: state.text,
        }),
        shallow,
      )
      // ...
    }

    在 v5 中可以用 createWithEqualityFn 做到这一点:

    npm install use-sync-external-store
    // v5
    import { createWithEqualityFn as create } from 'zustand/traditional'
    
    // The rest is the same as v4

    或者,针对 shallow 的使用场景,你可以使用 useShallow Hook:

    // v5
    import { create } from 'zustand'
    import { useShallow } from 'zustand/shallow'
    
    const useCountStore = create((set) => ({
      count: 0,
      text: 'hello',
      // ...
    }))
    
    const Component = () => {
      const { count, text } = useCountStore(
        useShallow((state) => ({
          count: state.count,
          text: state.text,
        })),
      )
      // ...
    }

    要求选择器的输出保持稳定

    v5 中有一项行为变更,以与 React 的默认行为保持一致。 如果选择器返回新的引用,可能会造成无限循环。

    例如,下面的写法可能会造成无限循环。

    // v4
    const [searchValue, setSearchValue] = useStore((state) => [state.searchValue, state.setSearchValue])

    错误信息大致如下:

    Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

    要修复这个问题,请使用 useShallow Hook,它会返回稳定的引用。

    // v5
    import { useShallow } from 'zustand/shallow'
    
    const [searchValue, setSearchValue] = useStore(
      useShallow((state) => [state.searchValue, state.setSearchValue]),
    )

    再看一个可能造成无限循环的例子。

    // v4
    const action = useMainStore((state) => {
      return state.action ?? () => {}
    })

    要修复这个问题,请确保选择器函数返回稳定的引用。

    // v5
    
    const FALLBACK_ACTION = () => {}
    
    const action = useMainStore((state) => {
      return state.action ?? FALLBACK_ACTION
    })

    另外,如果你需要 v4 的行为,使用 createWithEqualityFn 即可。

    // v5
    import { createWithEqualityFn as create } from 'zustand/traditional'

    设置 setState 的 replace 标志时类型更严格(仅限 TypeScript)

    - setState:
    -   (partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: boolean | undefined) => void;
    + setState:
    +   (partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: false) => void;
    +   (state: T | ((state: T) => T), replace: true) => void;

    如果你没有使用 replace 标志,则无需迁移。

    如果你使用了 replace 标志并将其设为 true,就必须提供完整的状态对象。 这一变更确保 store.setState({}, true)(会导致无效状态)不再被视为合法。

    示例:

    // Partial state update (valid)
    store.setState({ key: 'value' })
    
    // Complete state replacement (valid)
    store.setState({ key: 'value' }, true)
    
    // Incomplete state replacement (invalid)
    store.setState({}, true) // Error

    处理动态的 replace 标志

    如果 replace 标志的值是动态的、在运行时才确定,你可能会遇到问题。为此,你可以采用一个变通方案:把参数数组标注为 setState 函数的参数类型:

    const replaceFlag = Math.random() > 0.5
    const args = [{ bears: 5 }, replaceFlag] as Parameters<typeof useBearStore.setState>
    store.setState(...args)

    persist 中间件不再在创建 store 时存储数据

    以前,persist 中间件会在创建 store 期间存储初始状态。该行为已在 v5(以及 v4.5.5)中移除。

    例如,在下面的代码中,初始状态会被写入存储。

    // v4
    import { create } from 'zustand'
    import { persist } from 'zustand/middleware'
    
    const useCountStore = create(
      persist(
        () => ({
          count: Math.floor(Math.random() * 1000),
        }),
        {
          name: 'count',
        },
      ),
    )

    在 v5 中不再如此,你需要在创建 store 之后显式设置状态。

    // v5
    import { create } from 'zustand'
    import { persist } from 'zustand/middleware'
    
    const useCountStore = create(
      persist(
        () => ({
          count: 0,
        }),
        {
          name: 'count',
        },
      ),
    )
    useCountStore.setState({
      count: Math.floor(Math.random() * 1000),
    })

    链接