zustand-5 — скилл для Claude Code: состояние на клиенте
Скилл с паттернами управления состоянием в Zustand 5: базовые хранилища, селекторы, persist-middleware и разделение логики на слайсы. Помогает писать чистый реактивный код без лишних ре-рендеров.
npx skills add prowler-cloud/prowler --skill zustand-5 -a claude-codeЧто делает zustand-5
Zustand — лёгкая библиотека для состояния без бойлерплейта Redux. Скилл показывает, как создать хранилище с методами, подписаться на нужные поля через селекторы, сохранить данные в localStorage через persist-middleware. Включены примеры асинхронных действий (загрузка данных), работы с Immer для удобных мутаций и паттерна слайсов, когда логику разбивают на отдельные куски и потом объединяют в одно хранилище.
Важно: селекторы предотвращают лишние ре-рендеры, потому что компонент следит только за нужным куском состояния, а не за всем хранилищем сразу. Для нескольких полей используй useShallow из zustand/react/shallow. За пределами React доступны getState() для прямого доступа и subscribe() для подписки на изменения.
Скилл работает на всех основных AI-редакторах и требует установки как модуля проекта. Примеры охватывают типичные сценарии: счётчик, настройки с сохранением, загрузку юзера с обработкой ошибок, список товаров в корзине.
Какие задачи решает
- Управлять клиентским состоянием без бойлерплейта
- Оптимизировать ре-рендеры компонентов при изменении состояния
- Сохранять данные в localStorage и восстанавливать при загрузке
- Организовать асинхронные действия в хранилище
- Разбить большое состояние на логические слайсы
Примеры запросов агенту
- Создай хранилище Zustand для счётчика с методами increment и decrement
- Добавь persist-middleware, чтобы тема сохранялась в localStorage
- Напиши селектор, который загружает юзера по ID и обновляет состояние
Как установить zustand-5
1Через Skills CLI (Vercel)
npx skills add prowler-cloud/prowler --skill zustand-5 -a claude-code2Вручную: положить файлы
~/.claude/skills/zustand-5/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/zustand-5/
1Через Skills CLI (Vercel)
npx skills add prowler-cloud/prowler --skill zustand-5 -a codex2Вручную: положить файлы
.agents/skills/zustand-5/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add prowler-cloud/prowler --skill zustand-5 -a cursor2Вручную: положить файлы
.cursor/skills/zustand-5/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install prowler-cloud/prowler zustand-52Вручную: положить файлы
.github/skills/zustand-5/SKILL.md1Через Skills CLI (Vercel)
npx skills add prowler-cloud/prowler --skill zustand-5 -a gemini-cli2Командой платформы
gemini skills install https://github.com/prowler-cloud/prowler --path skills/zustand-5Вручную: положить файлы
.devin/skills/zustand-5/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add prowler-cloud/prowler --skill zustand-5 -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
Из того же набора4
Весь набор →Похожие
Вопросы о zustand-5
Как установить zustand-5 для Claude Code?
Выполни npx skills add prowler-cloud/prowler --skill zustand-5 -a claude-code, затем открой ~/.claude/skills/zustand-5/SKILL.md.
Что делает zustand-5?
Предоставляет готовые паттерны и примеры кода для Zustand 5: простые хранилища, селекторы для оптимизации, сохранение в localStorage, асинхронные действия, разделение на слайсы и работу с Immer.
Почему селекторы важны в Zustand?
Селектор следит только за нужным куском состояния, поэтому компонент не ре-рендерится, когда меняется другая часть хранилища. Используй useShallow для нескольких полей, чтобы избежать лишних обновлений.
Исходный текст
SKILL.md · Apache-2.0
---
name: zustand-5
description: >
Zustand 5 state management patterns.
Trigger: When implementing client-side state with Zustand (stores, selectors, persist middleware, slices).
license: Apache-2.0
metadata:
author: prowler-cloud
version: "1.0"
scope: [root, ui]
auto_invoke: "Using Zustand stores"
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
---
## Basic Store
```typescript
import { create } from "zustand";
interface CounterStore {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
const useCounterStore = create<CounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
// Usage
function Counter() {
const { count, increment, decrement } = useCounterStore();
return (
<div>
<span>{count}</span>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
```
## Persist Middleware
```typescript
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface SettingsStore {
theme: "light" | "dark";
language: string;
setTheme: (theme: "light" | "dark") => void;
setLanguage: (language: string) => void;
}
const useSettingsStore = create<SettingsStore>()(
persist(
(set) => ({
theme: "light",
language: "en",
setTheme: (theme) => set({ theme }),
setLanguage: (language) => set({ language }),
}),
{
name: "settings-storage", // localStorage key
}
)
);
```
## Selectors (Zustand 5)
```typescript
// ✅ Select specific fields to prevent unnecessary re-renders
function UserName() {
const name = useUserStore((state) => state.name);
return <span>{name}</span>;
}
// ✅ For multiple fields, use useShallow
import { useShallow } from "zustand/react/shallow";
function UserInfo() {
const { name, email } = useUserStore(
useShallow((state) => ({ name: state.name, email: state.email }))
);
return <div>{name} - {email}</div>;
}
// ❌ AVOID: Selecting entire store (causes re-render on any change)
const store = useUserStore(); // Re-renders on ANY state change
```
## Async Actions
```typescript
interface UserStore {
user: User | null;
loading: boolean;
error: string | null;
fetchUser: (id: string) => Promise<void>;
}
const useUserStore = create<UserStore>((set) => ({
user: null,
loading: false,
error: null,
fetchUser: async (id) => {
set({ loading: true, error: null });
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
set({ user, loading: false });
} catch (error) {
set({ error: "Failed to fetch user", loading: false });
}
},
}));
```
## Slices Pattern
```typescript
// userSlice.ts
interface UserSlice {
user: User | null;
setUser: (user: User) => void;
clearUser: () => void;
}
const createUserSlice = (set): UserSlice => ({
user: null,
setUser: (user) => set({ user }),
clearUser: () => set({ user: null }),
});
// cartSlice.ts
interface CartSlice {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (id: string) => void;
}
const createCartSlice = (set): CartSlice => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
removeItem: (id) => set((state) => ({
items: state.items.filter(i => i.id !== id)
})),
});
// store.ts
type Store = UserSlice & CartSlice;
const useStore = create<Store>()((...args) => ({
...createUserSlice(...args),
...createCartSlice(...args),
}));
```
## Immer Middleware
```typescript
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
interface TodoStore {
todos: Todo[];
addTodo: (text: string) => void;
toggleTodo: (id: string) => void;
}
const useTodoStore = create<TodoStore>()(
immer((set) => ({
todos: [],
addTodo: (text) => set((state) => {
// Mutate directly with Immer!
state.todos.push({ id: crypto.randomUUID(), text, done: false });
}),
toggleTodo: (id) => set((state) => {
const todo = state.todos.find(t => t.id === id);
if (todo) todo.done = !todo.done;
}),
}))
);
```
## DevTools
```typescript
import { create } from "zustand";
import { devtools } from "zustand/middleware";
const useStore = create<Store>()(
devtools(
(set) => ({
// store definition
}),
{ name: "MyStore" } // Name in Redux DevTools
)
);
```
## Outside React
```typescript
// Access store outside components
const { count, increment } = useCounterStore.getState();
increment();
// Subscribe to changes
const unsubscribe = useCounterStore.subscribe(
(state) => console.log("Count changed:", state.count)
);
```
© prowler-cloud · Apache-2.0 · prowler-cloud/prowler