Как избежать бесконечного цикла в Zustand?

Почему при вызове функции useToDoStore и при её присвоении компонент App начинает бесконечно рендериться? Также наблюдается бесконечный рендер при деструктуризации. Я заметил, что проблема заключается именно в самом присваивании, так как если присваивать каждое свойство по отдельности, минуя массивы и объекты, рендер происходит нормально. Например, в следующем коде всё работает корректно: 

<pre><code>const tasks = useToDoStore((state) =&gt; state.tasks);
const createTask = useToDoStore((state) =&gt; state.createTask);</code></pre> 

Однако это не оптимальное решение, особенно если store достаточно большой. Что же можно сделать в этой ситуации? 

Вот код самого store: 

<pre><code class="typescript">import { create } from "zustand";

import { generateId } from "../helpers";

interface Task {
  id: string;
  title: string;
  createdAd: number;
}

interface ToDoStore {
  tasks: Task[];
  createTask: (title: string) =&gt; void;
  updateTask: (id: string, title: string) =&gt; void;
  removeTask: (id: string) =&gt; void;
}

export const useToDoStore = create&lt;ToDoStore&gt;((set, get) =&gt; ({
  tasks: [
    {
      id: "asdasd",
      title: "kekeeee",
      createdAd: 124124,
    },
  ],
  createTask: (title) =&gt; {
    const { tasks } = get();
    const newTask = {
      id: generateId(),
      title,
      createdAd: Date.now(),
    };

    set({
      tasks: [newTask].concat(tasks),
    });
  },
  updateTask: (id, title) =&gt; {},
  removeTask: (id) =&gt; {},
}));</code></pre> 

А вот код, который вызывается и приводит к бесконечному рендеру:

<pre><code>const [tasks, createTask, updateTask, removeTask] = useToDoStore((state) =&gt; [
    state.tasks,
    state.createTask,
    state.updateTask,
    state.removeTask,
  ]);</code></pre>
  • 22 апреля 2025 г. 11:55
Ответы на вопрос 2
Проблема с бесконечным рендерингом, связанная с использованием Zustand, может возникать из-за того, как Zustand управляет подписками на состояние и как будет происходить сравнение значений. Когда вы используете массив (или объект) для деструктуризации состояния, Zustand будет считать его новую ссылку на массив, что всегда приводит к изменению, а значит, к новому рендерингу компонента.

Когда вы вызываете `useToDoStore` с функцией, возвращающей массив состояния, Zustand будет создавать новый массив при каждом рендере, даже если значения внутри него не изменились. Поскольку ссылка на массив отличается, это вызывает повторный рендер компонента.

Чтобы избежать этой проблемы, вы можете использовать несколько разных подходов:

1. **Деструктуризация с помощью отдельных вызовов**: Как вы уже заметили, это работает, но не оптимально. Тем не менее, вы можете использовать `useMemo` для оптимизации:

```javascript
import { useMemo } from 'react';

const { tasks, createTask, updateTask, removeTask } = useToDoStore((state) => ({
  tasks: state.tasks,
  createTask: state.createTask,
  updateTask: state.updateTask,
  removeTask: state.removeTask,
}));
```

Это будет более эффективно, так как вы все еще будете использовать один вызов `useToDoStore`, но получите объект с постоянной ссылкой, если значения не меняются.

2. **Использовать состояние как целиком объект**: Вместо деструктуризации отдельных значений, вы можете получить всю часть состояния в одном вызове:

```javascript
const { tasks, createTask, updateTask, removeTask } = useToDoStore((state) => state);
```

В этом случае, если вы измените только одно из значений, Zustand все равно сделает новый рендер, если какие-то изменения произойдут. Но поскольку все состояние передается как один объект, это будет работать более стабильно.

3. **Использовать `shallow` из Zustand**: Если вам нужно сравнивать только основные значения, вы можете использовать `shallow` для сравнения:

```javascript
import { shallow } from 'zustand/shallow';

const [tasks, createTask, updateTask, removeTask] = useToDoStore((state) => [state.tasks, state.createTask, state.updateTask, state.removeTask], shallow);
```

Это может также помочь избежать лишних рендеров, если некоторые значения в массиве не меняются.

Таким образом, есть несколько методов, которые могут помочь решить вашу проблему с бесконечным рендерингом в Zustand. Выберите тот, который лучше всего соответствует вашим требованиям и контексту приложения.
Попробуйте использовать useShallow, createWithEqualityFn ( подробнее ) или вторым аргументом в useToDoStore передать функцию shallow из import { shallow } from 'zustand/shallow'
Похожие вопросы