zustand-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,299 @@
1
+ # zustand-kit
2
+
3
+ [English](./README_EN.md) | 简体中文
4
+
5
+ 一个基于 Zustand 构建的轻量级、灵活的 React 状态管理库。
6
+
7
+ ## ✨ 特性
8
+
9
+ - 🚀 **简单易用** - 最小化的 API 设计,易于上手
10
+ - 🎯 **类型安全** - 完整的 TypeScript 支持
11
+ - 💾 **持久化** - 内置 localStorage/sessionStorage 支持
12
+ - ⚡ **高性能** - 基于 Zustand,性能卓越
13
+ - 🔄 **灵活更新** - 支持对象部分更新和函数式更新
14
+ - 🎨 **选择器支持** - 细粒度订阅,避免不必要的重渲染
15
+ - 🌐 **非 React 环境支持** - 提供独立的 API 用于非组件场景
16
+
17
+ ## 📦 安装
18
+
19
+ yarn add zustand-x zustand
20
+ ```bash
21
+ npm install zustand-kit zustand
22
+ # 或
23
+ yarn add zustand-kit zustand
24
+ # 或
25
+ pnpm add zustand-kit zustand
26
+ ```
27
+
28
+ ## 🎯 快速开始
29
+
30
+ ### 基础用法
31
+
32
+ ```tsx
33
+ import { useGlobalState } from 'zustand-kit';
34
+
35
+ function Counter() {
36
+ const [count, setCount, resetCount] = useGlobalState('counter', 0);
37
+
38
+ return (
39
+ <div>
40
+ <p>Count: {count}</p>
41
+ <button onClick={() => setCount(count + 1)}>增加</button>
42
+ <button onClick={() => setCount(prev => prev - 1)}>减少</button>
43
+ <button onClick={resetCount}>重置</button>
44
+ </div>
45
+ );
46
+ }
47
+ ```
48
+
49
+ ### 对象状态(支持部分更新)
50
+
51
+ ```tsx
52
+ import { useGlobalState } from 'zustand-x';
53
+
54
+ function UserProfile() {
55
+ const [user, setUser, resetUser] = useGlobalState('user', {
56
+ name: 'John',
57
+ email: 'john@example.com',
58
+ age: 30
59
+ });
60
+
61
+ return (
62
+ <div>
63
+ <p>名称: {user.name}</p>
64
+ <p>邮箱: {user.email}</p>
65
+ {/* 部分更新 - 只更新 name,其他字段保持不变 */}
66
+ <button onClick={() => setUser({ name: 'Jane' })}>
67
+ 更改名称
68
+ </button>
69
+ <button onClick={resetUser}>重置</button>
70
+ </div>
71
+ );
72
+ }
73
+ ```
74
+
75
+ ### 持久化状态
76
+
77
+ ```tsx
78
+ import { useGlobalState } from 'zustand-x';
79
+
80
+ function Settings() {
81
+ // 使用 localStorage 持久化
82
+ const [settings, setSettings] = useGlobalState(
83
+ 'settings',
84
+ { theme: 'dark', lang: 'zh-CN' },
85
+ { storage: 'localStorage' }
86
+ );
87
+
88
+ // 使用 sessionStorage 持久化
89
+ const [tempData, setTempData] = useGlobalState(
90
+ 'temp',
91
+ { foo: 'bar' },
92
+ {
93
+ storage: 'sessionStorage',
94
+ storageKey: 'my-app' // 自定义存储键前缀
95
+ }
96
+ );
97
+
98
+ return (
99
+ <div>
100
+ <p>主题: {settings.theme}</p>
101
+ <button onClick={() => setSettings({ theme: 'light' })}>
102
+ 切换主题
103
+ </button>
104
+ </div>
105
+ );
106
+ }
107
+ ```
108
+
109
+ ### 选择器模式(性能优化)
110
+
111
+ ```tsx
112
+ import { useGlobalSelector } from 'zustand-x';
113
+
114
+ function UserName() {
115
+ // 仅订阅 user.name,其他字段变化不会触发重渲染
116
+ const userName = useGlobalSelector('user', (state) => state.name);
117
+
118
+ return <p>用户名: {userName}</p>;
119
+ }
120
+
121
+ function UserEmail() {
122
+ // 仅订阅 user.email
123
+ const userEmail = useGlobalSelector('user', (state) => state.email);
124
+
125
+ return <p>邮箱: {userEmail}</p>;
126
+ }
127
+ ```
128
+
129
+ ### 仅获取 Setter(不订阅状态)
130
+
131
+ ```tsx
132
+ import { useGlobalSetter } from 'zustand-x';
133
+
134
+ function IncrementButton() {
135
+ // 只获取 setter,不订阅状态变化(不会重渲染)
136
+ const setCount = useGlobalSetter<number>('counter');
137
+
138
+ return (
139
+ <button onClick={() => setCount(prev => prev + 1)}>
140
+ 增加
141
+ </button>
142
+ );
143
+ }
144
+ ```
145
+
146
+ ## 🔧 非 React 环境使用
147
+
148
+ zustand-x 提供了独立的 API,可以在非 React 组件中使用:
149
+
150
+ ```typescript
151
+ import {
152
+ getGlobalState,
153
+ setGlobalState,
154
+ subscribeGlobalState,
155
+ resetGlobalState
156
+ } from 'zustand-x';
157
+
158
+ // 获取状态
159
+ const count = getGlobalState<number>('counter');
160
+
161
+ // 设置状态
162
+ setGlobalState('counter', 5);
163
+ setGlobalState('counter', prev => prev + 1);
164
+
165
+ // 订阅状态变化
166
+ const unsubscribe = subscribeGlobalState('counter', (newValue, prevValue) => {
167
+ console.log(`Counter 从 ${prevValue} 变为 ${newValue}`);
168
+ });
169
+
170
+ // 取消订阅
171
+ unsubscribe();
172
+
173
+ // 重置状态
174
+ resetGlobalState('counter');
175
+ ```
176
+
177
+ ## 📖 API 参考
178
+
179
+ ### `useGlobalState<T>(key, initialState, options?)`
180
+
181
+ 创建或连接到全局状态。
182
+
183
+ **参数:**
184
+ - `key: string` - 状态的唯一标识符
185
+ - `initialState: T` - 初始状态值
186
+ - `options?: UseGlobalStateOptions` - 可选配置
187
+ - `storage?: 'localStorage' | 'sessionStorage' | 'none'` - 持久化类型(默认 'none')
188
+ - `storageKey?: string` - 存储键前缀(默认 'global-state')
189
+
190
+ **返回:** `[state, setState, resetState]`
191
+
192
+ ### `useGlobalSelector<T, R>(key, selector)`
193
+
194
+ 使用选择器订阅状态的特定部分。
195
+
196
+ **参数:**
197
+ - `key: string` - 状态键
198
+ - `selector: (state: T) => R` - 选择器函数
199
+
200
+ **返回:** 选择的值
201
+
202
+ ### `useGlobalSetter<T>(key)`
203
+
204
+ 仅获取 setter 函数,不订阅状态变化。
205
+
206
+ **参数:**
207
+ - `key: string` - 状态键
208
+
209
+ **返回:** setter 函数
210
+
211
+ ### `getGlobalState<T>(key)`
212
+
213
+ 获取全局状态值(非 React 环境)。
214
+
215
+ ### `setGlobalState<T>(key, value)`
216
+
217
+ 设置全局状态值(非 React 环境)。
218
+
219
+ ### `subscribeGlobalState<T>(key, callback)`
220
+
221
+ 订阅全局状态变化(非 React 环境)。返回取消订阅函数。
222
+
223
+ ### `resetGlobalState(key)`
224
+
225
+ 重置全局状态为初始值(非 React 环境)。
226
+
227
+ ## 🎨 TypeScript 支持
228
+
229
+ zustand-x 使用 TypeScript 编写,提供完整的类型推断:
230
+
231
+ ```typescript
232
+ // 自动推断类型
233
+ const [count, setCount] = useGlobalState('counter', 0);
234
+ // count: number
235
+ // setCount: (value: number | ((prev: number) => number)) => void
236
+
237
+ // 对象状态支持部分更新
238
+ const [user, setUser] = useGlobalState('user', {
239
+ name: 'John',
240
+ age: 30
241
+ });
242
+ // user: { name: string; age: number }
243
+ // setUser: (value: Partial<{name: string; age: number}> | ((prev) => ...)) => void
244
+
245
+ // 显式类型声明
246
+ interface User {
247
+ name: string;
248
+ email: string;
249
+ }
250
+ const [user, setUser] = useGlobalState<User>('user', {
251
+ name: 'John',
252
+ email: 'john@example.com'
253
+ });
254
+ ```
255
+
256
+ ## 🤝 对比其他方案
257
+
258
+ | 特性 | zustand-x | Zustand | Redux | Context API |
259
+ |------|-----------|---------|-------|-------------|
260
+ | 学习曲线 | ⭐️ 简单 | ⭐️⭐️ 较简单 | ⭐️⭐️⭐️ 复杂 | ⭐️⭐️ 中等 |
261
+ | 包体积 | 极小 | 小 | 大 | 无 |
262
+ | 性能 | 优秀 | 优秀 | 优秀 | 较差 |
263
+ | TypeScript | ✅ 完整 | ✅ 完整 | ✅ 完整 | ⚠️ 基础 |
264
+ | 持久化 | ✅ 内置 | ✅ 中间件 | 需要插件 | ❌ |
265
+ | 选择器 | ✅ | ✅ | ✅ | ❌ |
266
+ | 易用性 | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️ | ⭐️⭐️ | ⭐️⭐️⭐️ |
267
+
268
+ ## 🧪 测试
269
+
270
+ ```bash
271
+ # 运行测试
272
+ npm test
273
+
274
+ # 监听模式运行测试
275
+ npm run test:watch
276
+
277
+ # 生成覆盖率报告
278
+ npm run test:coverage
279
+ ```
280
+
281
+ 测试覆盖率:93%+ (语句、分支、函数覆盖率)
282
+
283
+ ## 📄 许可证
284
+
285
+ MIT
286
+
287
+ ## 🔗 链接
288
+
289
+ - [GitHub](https://github.com/leonwgc/zustand-x)
290
+ - [Issues](https://github.com/leonwgc/zustand-x/issues)
291
+ - [Zustand](https://github.com/pmndrs/zustand)
292
+
293
+ ## 👨‍💻 作者
294
+
295
+ leon.wang
296
+
297
+ ---
298
+
299
+ 如果这个项目对你有帮助,欢迎给个 ⭐️!
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});var V=Object.defineProperty;var d=Object.getOwnPropertySymbols;var x=Object.prototype.hasOwnProperty,A=Object.prototype.propertyIsEnumerable;var g=(t,e,o)=>e in t?V(t,e,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[e]=o,T=(t,e)=>{for(var o in e||(e={}))x.call(e,o)&&g(t,o,e[o]);if(d)for(var o of d(e))A.call(e,o)&&g(t,o,e[o]);return t};var _zustand = require('zustand');var _middleware = require('zustand/middleware');var _react = require('react');var r=new Map;function _(){r.clear()}function B(t,e,o){let{storage:a="none",storageKey:i="global-state"}=o||{};if(!r.has(t)){let u=typeof e=="object"&&e!==null&&!Array.isArray(e),v=s=>({value:e,setValue:l=>{s(typeof l=="function"?p=>({value:l(p.value)}):u&&typeof l=="object"&&l!==null?p=>({value:T(T({},p.value),l)}):{value:l})},reset:()=>s({value:e})}),S;if(a!=="none"){let s=a==="localStorage"?localStorage:sessionStorage;S=_zustand.create.call(void 0, )(_middleware.persist.call(void 0, v,{name:`${i}-${t}`,storage:_middleware.createJSONStorage.call(void 0, ()=>s)}))}else S=_zustand.create.call(void 0, v);r.set(t,S)}let n=r.get(t),w=n(u=>u.value),b=_react.useMemo.call(void 0, ()=>n.getState().setValue,[n]),G=_react.useMemo.call(void 0, ()=>n.getState().reset,[n]);return[w,b,G]}function $(t,e){let o=r.get(t);if(!o)throw new Error(`Global state with key "${t}" not found. Initialize it with useGlobalState first.`);return o(a=>e(a.value))}function I(t){let e=r.get(t);if(!e)throw new Error(`Global state with key "${t}" not found. Initialize it with useGlobalState first.`);return _react.useMemo.call(void 0, ()=>e.getState().setValue,[e])}function z(t){let e=r.get(t);return e==null?void 0:e.getState().value}function P(t,e){let o=r.get(t);if(!o){process.env.NODE_ENV!=="production"&&console.warn(`Global state with key "${t}" not found. Initialize it with useGlobalState first.`);return}o.getState().setValue(e)}function j(t,e){let o=r.get(t);if(!o)return process.env.NODE_ENV!=="production"&&console.warn(`Global state with key "${t}" not found. Initialize it with useGlobalState first.`),()=>{};let a=o.getState().value;return o.subscribe(i=>{let n=i.value;n!==a&&(e(n,a),a=n)})}function C(t){let e=r.get(t);if(!e){process.env.NODE_ENV!=="production"&&console.warn(`Global state with key "${t}" not found. Initialize it with useGlobalState first.`);return}e.getState().reset()}var D=B;exports.__clearAllStates__ = _; exports.default = D; exports.getGlobalState = z; exports.resetGlobalState = C; exports.setGlobalState = P; exports.subscribeGlobalState = j; exports.useGlobalSelector = $; exports.useGlobalSetter = I; exports.useGlobalState = B;
@@ -0,0 +1 @@
1
+ var V=Object.defineProperty;var d=Object.getOwnPropertySymbols;var x=Object.prototype.hasOwnProperty,A=Object.prototype.propertyIsEnumerable;var g=(t,e,o)=>e in t?V(t,e,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[e]=o,T=(t,e)=>{for(var o in e||(e={}))x.call(e,o)&&g(t,o,e[o]);if(d)for(var o of d(e))A.call(e,o)&&g(t,o,e[o]);return t};import{create as f}from"zustand";import{persist as h,createJSONStorage as U}from"zustand/middleware";import{useMemo as c}from"react";var r=new Map;function _(){r.clear()}function B(t,e,o){let{storage:a="none",storageKey:i="global-state"}=o||{};if(!r.has(t)){let u=typeof e=="object"&&e!==null&&!Array.isArray(e),v=s=>({value:e,setValue:l=>{s(typeof l=="function"?p=>({value:l(p.value)}):u&&typeof l=="object"&&l!==null?p=>({value:T(T({},p.value),l)}):{value:l})},reset:()=>s({value:e})}),S;if(a!=="none"){let s=a==="localStorage"?localStorage:sessionStorage;S=f()(h(v,{name:`${i}-${t}`,storage:U(()=>s)}))}else S=f(v);r.set(t,S)}let n=r.get(t),w=n(u=>u.value),b=c(()=>n.getState().setValue,[n]),G=c(()=>n.getState().reset,[n]);return[w,b,G]}function $(t,e){let o=r.get(t);if(!o)throw new Error(`Global state with key "${t}" not found. Initialize it with useGlobalState first.`);return o(a=>e(a.value))}function I(t){let e=r.get(t);if(!e)throw new Error(`Global state with key "${t}" not found. Initialize it with useGlobalState first.`);return c(()=>e.getState().setValue,[e])}function z(t){let e=r.get(t);return e==null?void 0:e.getState().value}function P(t,e){let o=r.get(t);if(!o){process.env.NODE_ENV!=="production"&&console.warn(`Global state with key "${t}" not found. Initialize it with useGlobalState first.`);return}o.getState().setValue(e)}function j(t,e){let o=r.get(t);if(!o)return process.env.NODE_ENV!=="production"&&console.warn(`Global state with key "${t}" not found. Initialize it with useGlobalState first.`),()=>{};let a=o.getState().value;return o.subscribe(i=>{let n=i.value;n!==a&&(e(n,a),a=n)})}function C(t){let e=r.get(t);if(!e){process.env.NODE_ENV!=="production"&&console.warn(`Global state with key "${t}" not found. Initialize it with useGlobalState first.`);return}e.getState().reset()}var D=B;export{_ as __clearAllStates__,D as default,z as getGlobalState,C as resetGlobalState,P as setGlobalState,j as subscribeGlobalState,$ as useGlobalSelector,I as useGlobalSetter,B as useGlobalState};
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "zustand-kit",
3
+ "version": "0.1.0",
4
+ "description": "⚡️ A minimalist React global state management library based on Zustand with persistence, TypeScript and fine-grained subscriptions",
5
+ "keywords": [
6
+ "zustand",
7
+ "react",
8
+ "state-management",
9
+ "hooks"
10
+ ],
11
+ "license": "MIT",
12
+ "repository": "https://github.com/leonwgc/zustand-kit",
13
+ "bugs": {
14
+ "url": "https://github.com/leonwgc/zustand-kit/issues"
15
+ },
16
+ "main": "dist/index.cjs.js",
17
+ "module": "dist/index.esm.js",
18
+ "typings": "types/index.d.ts",
19
+ "scripts": {
20
+ "build": "tsup && tsc",
21
+ "dev": "tsup --watch",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "test:coverage": "vitest run --coverage",
25
+ "prepublishOnly": "npm run build",
26
+ "publish--patch": "./publish.sh patch",
27
+ "publish--minor": "./publish.sh minor",
28
+ "publish--major": "./publish.sh major"
29
+ },
30
+ "peerDependencies": {
31
+ "react": "^18.0.0",
32
+ "react-dom": "^18.0.0",
33
+ "zustand": "^5.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@testing-library/react": "^14.3.1",
37
+ "@types/node": "^25.0.3",
38
+ "@types/react": "^18.0.0",
39
+ "@types/react-dom": "^18.0.10",
40
+ "@typescript-eslint/eslint-plugin": "^5.6.0",
41
+ "@typescript-eslint/parser": "^5.6.0",
42
+ "@vitejs/plugin-react": "^4.7.0",
43
+ "@vitest/coverage-v8": "^1.6.1",
44
+ "antd": "^6.1.4",
45
+ "eslint": "^8.34.0",
46
+ "eslint-plugin-react": "^7.19.0",
47
+ "eslint-plugin-react-hooks": "^2.5.0",
48
+ "jsdom": "^23.2.0",
49
+ "packrs": "^1.0.1",
50
+ "prettier": "^2.2.1",
51
+ "react": "^18.2.0",
52
+ "react-dom": "^18.2.0",
53
+ "tsup": "^8.3.6",
54
+ "typescript": "^5.7.3",
55
+ "vitest": "^1.6.1",
56
+ "yargs": "^15.3.1",
57
+ "zustand": "^5.0.9"
58
+ },
59
+ "files": [
60
+ "dist",
61
+ "types"
62
+ ]
63
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @file src/index.tsx
3
+ * @author leon.wang
4
+ */
5
+ /**
6
+ * Clear all global states (for testing purposes)
7
+ * @internal
8
+ */
9
+ export declare function __clearAllStates__(): void;
10
+ /**
11
+ * Storage type for persistence
12
+ */
13
+ export type StorageType = 'localStorage' | 'sessionStorage' | 'none';
14
+ /**
15
+ * Options for useGlobalState
16
+ */
17
+ export interface UseGlobalStateOptions {
18
+ /**
19
+ * Enable persistence and specify storage type
20
+ * @default 'none'
21
+ */
22
+ storage?: StorageType;
23
+ /**
24
+ * Custom storage key prefix
25
+ * @default 'global-state'
26
+ */
27
+ storageKey?: string;
28
+ }
29
+ /**
30
+ * Universal global state hook - supports both simple values and objects
31
+ * Performance optimized with selector pattern
32
+ *
33
+ * @param key - Unique key for the state
34
+ * @param initialState - Initial state (any type)
35
+ * @param options - Configuration options including persistence
36
+ * @returns [state, setState, resetState]
37
+ *
38
+ * @example
39
+ * // Simple value (number, string, boolean)
40
+ * const [count, setCount, resetCount] = useGlobalState('counter', 0);
41
+ * setCount(5);
42
+ * setCount(prev => prev + 1);
43
+ *
44
+ * // Object state - supports partial updates
45
+ * const [user, setUser, resetUser] = useGlobalState('user', {
46
+ * name: 'John',
47
+ * email: 'john@example.com',
48
+ * });
49
+ * setUser({ name: 'Jane' }); // Partial update
50
+ *
51
+ * // With localStorage persistence
52
+ * const [settings, setSettings] = useGlobalState('settings', { theme: 'dark' }, {
53
+ * storage: 'localStorage'
54
+ * });
55
+ *
56
+ * // With sessionStorage persistence
57
+ * const [tempData, setTempData] = useGlobalState('temp', { foo: 'bar' }, {
58
+ * storage: 'sessionStorage',
59
+ * storageKey: 'my-app'
60
+ * });
61
+ *
62
+ * // For non-React usage, see: getGlobalState, setGlobalState, subscribeGlobalState, resetGlobalState
63
+ */
64
+ export declare function useGlobalState<T>(key: string, initialState: T, options?: UseGlobalStateOptions): [
65
+ T,
66
+ (value: T extends Record<string, unknown> ? Partial<T> | ((prev: T) => T) : T | ((prev: T) => T)) => void,
67
+ () => void
68
+ ];
69
+ /**
70
+ * Advanced hook with custom selector for fine-grained subscriptions
71
+ * Only re-renders when selected value changes
72
+ *
73
+ * @example
74
+ * // Only subscribe to user name, not the whole user object
75
+ * const userName = useGlobalSelector('user', (state) => state.name);
76
+ *
77
+ * // Multiple values
78
+ * const { name, email } = useGlobalSelector(
79
+ * 'user',
80
+ * (state) => ({ name: state.name, email: state.email })
81
+ * );
82
+ */
83
+ export declare function useGlobalSelector<T, R>(key: string, selector: (state: T) => R): R;
84
+ /**
85
+ * Hook to get setter function only (doesn't subscribe to state changes)
86
+ * Useful when you only need to update state without reading it
87
+ *
88
+ * @example
89
+ * const setCount = useGlobalSetter<number>('counter');
90
+ * setCount(5);
91
+ * setCount(prev => prev + 1);
92
+ */
93
+ export declare function useGlobalSetter<T>(key: string): (value: T extends Record<string, unknown> ? Partial<T> | ((prev: T) => T) : T | ((prev: T) => T)) => void;
94
+ /**
95
+ * Get global state value (for non-React usage)
96
+ * @example
97
+ * const count = getGlobalState<number>('counter');
98
+ */
99
+ export declare function getGlobalState<T>(key: string): T | undefined;
100
+ /**
101
+ * Set global state value (for non-React usage)
102
+ * @example
103
+ * setGlobalState('counter', 5);
104
+ * setGlobalState('counter', prev => prev + 1);
105
+ * setGlobalState('user', { name: 'Jane' }); // Partial update for objects
106
+ */
107
+ export declare function setGlobalState<T>(key: string, value: T extends Record<string, unknown> ? Partial<T> | ((prev: T) => T) : T | ((prev: T) => T)): void;
108
+ /**
109
+ * Subscribe to global state changes (for non-React usage)
110
+ * Returns unsubscribe function
111
+ * @example
112
+ * const unsubscribe = subscribeGlobalState('counter', (newValue, prevValue) => {
113
+ * console.log('Counter changed from', prevValue, 'to', newValue);
114
+ * });
115
+ * // Later: unsubscribe();
116
+ */
117
+ export declare function subscribeGlobalState<T>(key: string, callback: (newValue: T, prevValue: T) => void): () => void;
118
+ /**
119
+ * Reset global state to initial value (for non-React usage)
120
+ * @example
121
+ * resetGlobalState('counter');
122
+ */
123
+ export declare function resetGlobalState(key: string): void;
124
+ export default useGlobalState;