Skip to content

如何从 v4 迁移到 v5

¥How to Migrate to v5 from v4

我们强烈建议在迁移到 v5 之前更新到最新版本的 v4。它将显示所有弃用警告,而不会破坏你的应用。

¥We highly recommend to update to the latest version of v4, before migrating to v5. It will show all deprecation warnings without breaking your app.

v5 中的变化

¥Changes in v5

  • 放弃默认导出

    ¥Drop default exports

  • 放弃弃用的功能

    ¥Drop deprecated features

  • 将 React 18 设为最低要求版本

    ¥Make React 18 the minimum required version

  • 使 use-sync-external-store 成为对等依赖(zustand/traditional 中的 createWithEqualityFnuseStoreWithEqualityFn 所需)

    ¥Make use-sync-external-store a peer dependency (required for createWithEqualityFn and useStoreWithEqualityFn in zustand/traditional)

  • 将 TypeScript 4.5 设为最低要求版本

    ¥Make TypeScript 4.5 the minimum required version

  • 放弃 UMD/SystemJS 支持

    ¥Drop UMD/SystemJS support

  • 在 package.json 中组织入口点

    ¥Organize entry points in the package.json

  • 放弃 ES5 支持

    ¥Drop ES5 support

  • 设置 setState 的 replace 标志时类型更严格

    ¥Stricter types when setState's replace flag is set

  • 持久中间件行为更改

    ¥Persist middleware behavioral change

  • 其他小改进(技术上重大更改)

    ¥Other small improvements (technically breaking changes)

迁移指南

¥Migration Guide

使用自定义相等函数,例如 shallow

¥Using custom equality functions such as shallow

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

¥The create function in v5 does not support customizing equality function.

如果你使用自定义相等函数(例如 shallow),最简单的迁移是使用 createWithEqualityFn

¥If you use custom equality function such as shallow, the easiest migration is to use createWithEqualityFn.

js
// 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 来完成:

¥That can be done with createWithEqualityFn in v5:

bash
npm install use-sync-external-store
js
// v5
import { createWithEqualityFn as create } from 'zustand/traditional'

// The rest is the same as v4

或者,对于 shallow 用例,你可以使用 useShallow 钩子:

¥Alternatively, for the shallow use case, you can use useShallow hook:

js
// 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,
    })),
  )
  // ...
}

需要稳定的选择器输出

¥Requiring stable selector outputs

v5 中的行为发生变化以匹配 React 默认行为。如果选择器返回新的引用,则可能会导致无限循环。

¥There is a behavioral change in v5 to match React default behavior. If a selector returns a new reference, it may cause infinite loops.

例如,这可能会导致无限循环。

¥For example, this may cause infinite loops.

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

错误消息将是这样的:

¥The error message will be something like this:

plaintext
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 钩子,它将返回稳定的引用。

¥To fix it, use the useShallow hook, which will return a stable reference.

js
// v5
import { useShallow } from 'zustand/shallow'

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

这是另一个可能导致无限循环的示例。

¥Here's another example that may cause infinite loops.

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

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

¥To fix it, make sure the selector function returns a stable reference.

js
// v5

const FALLBACK_ACTION = () => {}

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

或者,如果你需要 v4 行为,createWithEqualityFn 就可以了。

¥Alternatively, if you need v4 behavior, createWithEqualityFn will do.

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

设置 setState 的替换标志时类型更严格(仅限 Typescript)

¥Stricter types when setState's replace flag is set (Typescript only)

diff
- 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 标志,则无需迁移。

¥If you are not using the replace flag, no migration is required.

如果你使用的是 replace 标志并且它设置为 true,则必须提供完整的状态对象。此更改确保 store.setState({}, true)(导致无效状态)不再被视为有效。

¥If you are using the replace flag and it's set to true, you must provide a complete state object. This change ensures that store.setState({}, true) (which results in an invalid state) is no longer considered valid.

示例:

¥Examples:

ts
// 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 标志

¥Handling Dynamic replace Flag

如果 replace 标志的值是动态的并且在运行时确定,你可能会遇到问题。为了解决这个问题,你可以使用一种解决方法,即用 setState 函数的参数注释 replace 参数:

¥If the value of the replace flag is dynamic and determined at runtime, you might face issues. To handle this, you can use a workaround by annotating the replace parameter with the parameters of the setState function:

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

持久化中间件不再在创建存储时存储项目

¥Persist middleware no longer stores item at store creation

以前,persist 中间件在存储创建期间存储初始状态。此行为已在 v5(和 v4.5.5)中删除。

¥Previously, the persist middleware stored the initial state during store creation. This behavior has been removed in v5 (and v4.5.5).

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

¥For example, in the following code, the initial state is stored in the storage.

js
// v4
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

const useCountStore = create(
  persist(
    () => ({
      count: Math.floor(Math.random() * 1000),
    }),
    {
      name: 'count',
    },
  ),
)

在 v5 中,情况不再如此,你需要在创建存储后明确设置状态。

¥In v5, this is no longer the case, and you need to explicitly set the state after store creation.

js
// 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),
})

¥Links

Zustand v5.0 中文网 - 粤ICP备13048890号