@dune2/tools 0.5.1 → 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dune2/tools",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "i18n"
@@ -32,6 +32,7 @@
32
32
  "./valtio": "./src/valtio/index.ts",
33
33
  "./valtio/*": "./src/valtio/*.ts",
34
34
  "./niceModal": "./src/niceModal/index.tsx",
35
+ "./zustand": "./src/zustand/index.ts",
35
36
  "./package.json": "./package.json"
36
37
  },
37
38
  "files": [
@@ -46,16 +47,17 @@
46
47
  "bignumber.js": "^9.1.2",
47
48
  "js-cookie": "^3.0.5",
48
49
  "lodash": "^4",
49
- "store2": "^2.14.3",
50
- "valtio": "^1.13.2"
50
+ "store2": "^2.14.3"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@tanstack/react-query": "5.51.11",
54
54
  "@types/js-cookie": "3.0.3",
55
55
  "@types/lodash": "^4.17.0",
56
- "@types/react": "^18.2.6",
57
56
  "axios": "^1.6.8",
58
- "react": "^18.2.0",
57
+ "immer": "^10",
58
+ "react": "^19",
59
+ "valtio": "^2",
60
+ "zustand": "^5.0.1",
59
61
  "zx": "^7.0.8"
60
62
  },
61
63
  "publishConfig": {
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import type { ComponentType, FC, PropsWithChildren } from "react";
2
3
  import React, { useContext } from "react";
3
4
 
@@ -157,7 +157,13 @@ export class DuneI18n {
157
157
  this.emit("localeChange", combinedLocale);
158
158
  // 默认需要 同步到 localStorage
159
159
  if (syncToStorage) {
160
- localStorage.setItem(this.config.storageKey, combinedLocale);
160
+ //https://www.chromium.org/for-testers/bug-reporting-guidelines/uncaught-securityerror-failed-to-read-the-localstorage-property-from-window-access-is-denied-for-this-document/
161
+ //fix: Failed to read the 'localStorage' property from 'Window': Access is denied for this document.
162
+ try {
163
+ localStorage.setItem(this.config.storageKey, combinedLocale);
164
+ } catch (error) {
165
+ console.error("set localStorage error", error);
166
+ }
161
167
  }
162
168
  };
163
169
 
@@ -1,3 +1,5 @@
1
+ import _ from "lodash";
2
+ import { useSyncExternalStore } from "react";
1
3
  import type { StoreType } from "store2";
2
4
  import baseStore from "store2";
3
5
 
@@ -31,6 +33,13 @@ class StorageHelper<V = any> {
31
33
  * 获取到的 key = "test.token"
32
34
  */
33
35
  key: string;
36
+ /**
37
+ * 用来缓存当前值
38
+ * - 防止值是 object 的时候,每次 get 都都返回新的对象,导致 react 的 重复渲染
39
+ * - 在 set 时,如果值没有发生变化,则不触发 storage 事件
40
+ * - 在 get 时,如果值没有发生变化,则直接返回缓存的值
41
+ */
42
+ private currentValue: V | undefined = undefined;
34
43
  constructor(
35
44
  public store: StoreType,
36
45
  public namespace: string,
@@ -38,22 +47,62 @@ class StorageHelper<V = any> {
38
47
  public defaultValue: V,
39
48
  ) {
40
49
  this.key = `${namespace}.${baseKey}`;
50
+ this.currentValue = this.defaultValue;
41
51
  }
42
52
 
43
53
  get(): V | undefined {
44
- return this.store.get(this.baseKey) ?? this.defaultValue;
54
+ const r = this.store.get(this.baseKey) ?? this.defaultValue;
55
+ if (!_.isEqual(r, this.currentValue)) {
56
+ this.currentValue = r;
57
+ }
58
+ return this.currentValue;
45
59
  }
46
60
 
47
61
  /**
48
62
  * 设置为 undefined 时,会删除该 key
49
63
  */
50
- set(v: V): void {
51
- v === undefined ? this.remove() : this.store.set(this.baseKey, v);
64
+ set(v: V | undefined): void {
65
+ if (_.isEqual(v, this.currentValue)) {
66
+ return;
67
+ }
68
+ this.currentValue = v;
69
+ v === undefined
70
+ ? this.store.remove(this.baseKey)
71
+ : this.store.set(this.baseKey, v);
72
+ if (typeof window !== "undefined") {
73
+ // On localStorage.setItem, the storage event is only triggered on other tabs and windows.
74
+ // So we manually dispatch a storage event to trigger the subscribe function on the current window as well.
75
+ window.dispatchEvent(
76
+ new StorageEvent("storage", {
77
+ key: this.key,
78
+ // 这里 value 不重要,在内部会使用 get 重新获取值
79
+ newValue: null,
80
+ }),
81
+ );
82
+ }
52
83
  }
53
84
 
54
85
  remove(): void {
55
- this.store.remove(this.baseKey);
86
+ this.set(undefined);
87
+ }
88
+
89
+ /**
90
+ * 这是 react hooks 的 useValue 的实现
91
+ */
92
+ useValue() {
93
+ return useSyncExternalStore(
94
+ this.useSyncExternalStoreSubscribe,
95
+ this.useSyncExternalStoreGetSnapshot,
96
+ this.useSyncExternalStoreGetSnapshot,
97
+ );
98
+ }
99
+ private useSyncExternalStoreSubscribe(listener: () => void) {
100
+ window.addEventListener("storage", listener);
101
+ return () => {
102
+ window.removeEventListener("storage", listener);
103
+ };
56
104
  }
105
+ private useSyncExternalStoreGetSnapshot = this.get.bind(this);
57
106
  }
58
107
 
59
108
  /**
@@ -0,0 +1,61 @@
1
+ import { produce } from "immer";
2
+ import { create } from "zustand";
3
+ import { useShallow } from "zustand/shallow";
4
+ type State<S extends object, A extends Record<string, Action<S, any>>> = {
5
+ state: S;
6
+ actions: A;
7
+ };
8
+
9
+ export function createStore<
10
+ S extends object,
11
+ A extends Record<string, Action<S, any>>,
12
+ >(config: State<S, A>) {
13
+ const useStore = create<S>((set) => {
14
+ return config.state;
15
+ });
16
+ type NormalActions = {
17
+ [key in keyof A]: (payload: Parameters<A[key]>[1]) => void;
18
+ };
19
+ const normalActions: NormalActions = {} as NormalActions;
20
+ Object.keys(config.actions).forEach((key) => {
21
+ // @ts-ignore
22
+ normalActions[key] = (payload) => {
23
+ useStore.setState(
24
+ produce((draft) => {
25
+ config.actions[key](draft, payload);
26
+ }),
27
+ // 尽可能复用 immer 更新的数据
28
+ true,
29
+ );
30
+ };
31
+ });
32
+
33
+ return {
34
+ /**
35
+ * 在组件内获取状态
36
+ */
37
+ useSnapshot: useStore,
38
+ /**
39
+ * 一般用于传入select,并且 selector 返回的值是对象,
40
+ * 这样就可以浅比较,减少不必要的更新
41
+ */
42
+ useShallowSnapshot: <U extends object>(select: (state: S) => U) => {
43
+ return useStore(useShallow(select));
44
+ },
45
+
46
+ /**
47
+ * 普通方法,设置状态
48
+ */
49
+ actions: normalActions,
50
+ /**
51
+ * 监听状态变化
52
+ */
53
+ subscribe: useStore.subscribe,
54
+ /**
55
+ * 获取状态
56
+ */
57
+ getState: useStore.getState,
58
+ };
59
+ }
60
+
61
+ type Action<S, T> = (state: S, payload: T) => any;