diff --git a/package.json b/package.json index 2a57237..61aedd6 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,25 @@ { "name": "@misaka17535/create-typed-event", - "version": "1.0.17", + "version": "1.0.18", "description": "Function-based and directive tiny typed event, with current value and optional react hook. Proven and well-tested in private projects. 全功能、函数式调用的微型事件库,可读取当前值且带有可选 react hook。在内部项目中久经考验。", "scripts": { "build": "tsup", + "prepare:dist": "tsx ./scripts/copyRequiredFilesToDist.ts", "test:link": "pnpm link ./dist", - "release": "tsx ./scripts/release.ts", - "postversion": "tsx ./scripts/copyRequiredFilesToDist.ts" + "release": "pnpm build && pnpm prepare:dist && np --contents=./dist --no-tests" }, "main": "index.js", "module": "index.mjs", "types": "index.d.ts", "exports": { ".": { + "types": "./index.d.ts", "import": "./index.mjs", "require": "./index.js", "default": "./index.js" }, "./react": { + "types": "./react.d.ts", "import": "./react.mjs", "require": "./react.js", "default": "./react.js" @@ -40,7 +42,7 @@ "publishConfig": { "access": "public" }, - "packageManager": "pnpm", + "packageManager": "pnpm@9.0.0", "devDependencies": { "@types/lodash-es": "^4.17.12", "@types/node": "^22.15.29", @@ -55,10 +57,12 @@ "react": "^19.1.0", "tsup": "^8.5.0", "tsx": "^4.19.4", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "use-sync-external-store": "^1.6.0" }, "peerDependencies": { - "react": ">=16" + "react": ">=16", + "use-sync-external-store": ">=1" }, "peerDependenciesMeta": { "react": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3292b5d..2333611 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,9 +4,6 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -overrides: - brace-expansion@>=2.0.0 <=2.0.1: '>=2.0.2' - importers: .: @@ -53,6 +50,9 @@ importers: typescript: specifier: ^5.8.3 version: 5.8.3 + use-sync-external-store: + specifier: ^1.6.0 + version: 1.6.0(react@19.1.0) packages: @@ -599,17 +599,15 @@ packages: atomically@2.0.3: resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==} - balanced-match@3.0.1: - resolution: {integrity: sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==} - engines: {node: '>= 16'} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} boxen@8.0.1: resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} engines: {node: '>=18'} - brace-expansion@4.0.1: - resolution: {integrity: sha512-YClrbvTCXGe70pU2JiEiPLYXO9gQkyxYeKpJIQHVS/gOs6EWMQP2RYBwjFLNT322Ji8TOC3IMPfsYCedNpzKfA==} - engines: {node: '>= 18'} + brace-expansion@2.0.3: + resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -1804,6 +1802,11 @@ packages: resolution: {integrity: sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==} engines: {node: '>=18'} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -2259,7 +2262,7 @@ snapshots: stubborn-fs: 1.2.5 when-exit: 2.1.4 - balanced-match@3.0.1: {} + balanced-match@1.0.2: {} boxen@8.0.1: dependencies: @@ -2272,9 +2275,9 @@ snapshots: widest-line: 5.0.0 wrap-ansi: 9.0.0 - brace-expansion@4.0.1: + brace-expansion@2.0.3: dependencies: - balanced-match: 3.0.1 + balanced-match: 1.0.2 braces@3.0.3: dependencies: @@ -2912,7 +2915,7 @@ snapshots: minimatch@9.0.5: dependencies: - brace-expansion: 4.0.1 + brace-expansion: 2.0.3 minimist@1.2.8: {} @@ -3482,6 +3485,10 @@ snapshots: semver: 7.7.2 xdg-basedir: 5.1.0 + use-sync-external-store@1.6.0(react@19.1.0): + dependencies: + react: 19.1.0 + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e96b9a6..2e3142e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,6 @@ +packages: + - '.' + onlyBuiltDependencies: - esbuild diff --git a/scripts/release.ts b/scripts/release.ts deleted file mode 100644 index 6bf1aa0..0000000 --- a/scripts/release.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {execSync} from 'child_process'; - -const main = async () => { - console.log('构建...'); - execSync('pnpm tsup', {stdio: 'inherit'}); - console.log('发布...'); - execSync('pnpm np --contents=./dist --no-tests', {stdio: 'inherit'}); -}; - -main(); diff --git a/src/index.ts b/src/index.ts index aa62f4c..0685a9a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -70,11 +70,12 @@ export const createTypedEvent = ({ cbs.map(v => v(payload)); }, once: (cb: cb) => { - instance.sub((arg: T) => { + const wrappedCb = (arg: T) => { cb(arg); - instance.unsub(cb); - }); - return () => instance.unsub(cb); + instance.unsub(wrappedCb); + }; + instance.sub(wrappedCb); + return () => instance.unsub(wrappedCb); }, get value() { return history[0]; diff --git a/src/react.ts b/src/react.ts index 17567f7..9293dba 100644 --- a/src/react.ts +++ b/src/react.ts @@ -1,8 +1,9 @@ import {createTypedEvent, TypedEvent} from "./index"; -import {Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState} from "react"; +import {Dispatch, SetStateAction, useCallback, useMemo, useRef, useState} from "react"; +import {useSyncExternalStore} from "use-sync-external-store/shim"; -const isFunction = (obj: any): obj is (...args: any) => any => { - return !!(obj && obj.constructor && obj.call && obj.apply); +const isFunction = (value: any): value is (...args: any) => any => { + return typeof value === "function"; }; // 临时直接嵌入 useHybridState 的实现,后续发包再改 @@ -62,9 +63,23 @@ export const createTypedEventMemorized: typeof createTypedEvent = (...args) => * const [marketPrice, setMarketPrice, marketPriceRef] = useTypedEventValue(marketPriceUpdateEvent); */ export const useTypedEventValue = (event: TypedEvent) => { - const [value, setValue, valueRef] = useHybridState(event.value); - useEffect(() => { - return event.sub(setValue); - }, []); - return [value, event.dispatch, valueRef] as const; + const value = useSyncExternalStore( + useCallback((onStoreChange: () => void) => event.sub(() => onStoreChange()), [event]), + useCallback(() => event.value, [event]), + useCallback(() => event.value, [event]), + ); + const valueRef = useRef(value); + valueRef.current = value; + const refBehindProxy = useMemo( + () => + new Proxy(valueRef, { + set: () => { + throw new Error( + "Do not change value of this ref, it's readonly. Instead, use the set method.", + ); + }, + }), + [], + ); + return [value, event.dispatch, refBehindProxy] as const; }; diff --git a/use-sync-external-store-shim.d.ts b/use-sync-external-store-shim.d.ts new file mode 100644 index 0000000..719c4d3 --- /dev/null +++ b/use-sync-external-store-shim.d.ts @@ -0,0 +1,7 @@ +declare module "use-sync-external-store/shim" { + export function useSyncExternalStore( + subscribe: (onStoreChange: () => void) => () => void, + getSnapshot: () => Snapshot, + getServerSnapshot?: () => Snapshot, + ): Snapshot; +}