bunja 0.0.3 → 0.0.4
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 +7 -7
- package/bunja.ts +57 -25
- package/package.json +1 -1
- package/tsconfig.json +2 -1
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ You can typically use jotai or something, and when lifetime management becomes n
|
|
|
25
25
|
|
|
26
26
|
## How to use
|
|
27
27
|
|
|
28
|
-
Bunja provides two functions: `bunja` and `useBunja`.\
|
|
28
|
+
Bunja basically provides two functions: `bunja` and `useBunja`.\
|
|
29
29
|
You can use `bunja` to define a state with a finite lifetime and use the `useBunja` hook to access that state.
|
|
30
30
|
|
|
31
31
|
### Defining a Bunja
|
|
@@ -33,15 +33,18 @@ You can use `bunja` to define a state with a finite lifetime and use the `useBun
|
|
|
33
33
|
You can define a bunja using the `bunja` function. When you access the defined bunja with the `useBunja` hook, a bunja instance is created.\
|
|
34
34
|
If all components in the render tree that refer to the bunja disappear, the bunja instance is automatically destroyed.
|
|
35
35
|
|
|
36
|
-
If you want to
|
|
36
|
+
If you want to trigger effects when the lifetime of a bunja starts and ends, you can use the `Bunja.effect` field.
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
|
+
import { bunja, Bunja, useBunja } from "bunja";
|
|
40
|
+
|
|
39
41
|
const countBunja = bunja([], () => {
|
|
40
42
|
const countAtom = atom(0);
|
|
41
43
|
return {
|
|
42
44
|
countAtom,
|
|
43
|
-
[
|
|
44
|
-
console.log("
|
|
45
|
+
[Bunja.effect]() {
|
|
46
|
+
console.log("mounted");
|
|
47
|
+
return () => console.log("unmounted");
|
|
45
48
|
},
|
|
46
49
|
};
|
|
47
50
|
});
|
|
@@ -53,7 +56,4 @@ function MyComponent() {
|
|
|
53
56
|
}
|
|
54
57
|
```
|
|
55
58
|
|
|
56
|
-
This code snippet defines a bunja that creates a `countAtom`.\
|
|
57
|
-
The `Symbol.dispose` method is used when the bunja instance is no longer referenced by any component in the render tree, allowing you to clean up resources appropriately.
|
|
58
|
-
|
|
59
59
|
TODO: context
|
package/bunja.ts
CHANGED
|
@@ -1,34 +1,55 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
(Symbol as any).dispose ??= Symbol("Symbol.dispose");
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
type Dep<T> = React.Context<T> | Bunja<T>;
|
|
3
|
+
export type Dep<T> = React.Context<T> | Bunja<T>;
|
|
8
4
|
|
|
9
|
-
class Bunja<T> {
|
|
5
|
+
export class Bunja<T> {
|
|
10
6
|
constructor(
|
|
11
7
|
public id: number,
|
|
12
8
|
public deps: Dep<any>[],
|
|
13
9
|
public contexts: React.Context<any>[],
|
|
14
|
-
public init: (...args: any[]) => T
|
|
10
|
+
public init: (...args: any[]) => T & BunjaValue
|
|
15
11
|
) {}
|
|
12
|
+
static readonly effect = Symbol("Bunja.effect");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class BunjaStore {
|
|
16
|
+
#bunjas: Record<string, BunjaInstance> = {};
|
|
17
|
+
get(bunja: Bunja<any>, biid: string, args: any[]) {
|
|
18
|
+
return (this.#bunjas[biid] ??= new BunjaInstance(
|
|
19
|
+
this,
|
|
20
|
+
biid,
|
|
21
|
+
bunja.init(...args)
|
|
22
|
+
));
|
|
23
|
+
}
|
|
24
|
+
delete(biid: string) {
|
|
25
|
+
delete this.#bunjas[biid];
|
|
26
|
+
}
|
|
16
27
|
}
|
|
17
28
|
|
|
18
|
-
const
|
|
19
|
-
export
|
|
20
|
-
|
|
29
|
+
export const createBunjaStore = () => new BunjaStore();
|
|
30
|
+
export const BunjaStoreContext = React.createContext(createBunjaStore());
|
|
31
|
+
|
|
32
|
+
export type BunjaEffectFn = () => () => void;
|
|
33
|
+
export interface BunjaValue {
|
|
34
|
+
[Bunja.effect]?: BunjaEffectFn;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function bunja<T>(deps: [], init: () => T & BunjaValue): Bunja<T>;
|
|
38
|
+
export function bunja<T, U>(
|
|
39
|
+
deps: [Dep<U>],
|
|
40
|
+
init: (u: U) => T & BunjaValue
|
|
41
|
+
): Bunja<T>;
|
|
21
42
|
export function bunja<T, U, V>(
|
|
22
43
|
deps: [Dep<U>, Dep<V>],
|
|
23
|
-
init: (u: U, v: V) => T
|
|
44
|
+
init: (u: U, v: V) => T & BunjaValue
|
|
24
45
|
): Bunja<T>;
|
|
25
46
|
export function bunja<T, U, V, W>(
|
|
26
47
|
deps: [Dep<U>, Dep<V>, Dep<W>],
|
|
27
|
-
init: (u: U, v: V, w: W) => T
|
|
48
|
+
init: (u: U, v: V, w: W) => T & BunjaValue
|
|
28
49
|
): Bunja<T>;
|
|
29
50
|
export function bunja<T, const U extends any[]>(
|
|
30
51
|
deps: { [K in keyof U]: Dep<U[K]> },
|
|
31
|
-
init: (...args: U) => T
|
|
52
|
+
init: (...args: U) => T & BunjaValue
|
|
32
53
|
): Bunja<T> {
|
|
33
54
|
const contexts = deps.filter(
|
|
34
55
|
(dep) => !(dep instanceof Bunja)
|
|
@@ -42,7 +63,8 @@ export function bunja<T, const U extends any[]>(
|
|
|
42
63
|
bunja.counter = 0;
|
|
43
64
|
|
|
44
65
|
export function useBunja<T>(bunja: Bunja<T>): T {
|
|
45
|
-
const { id, deps, contexts
|
|
66
|
+
const { id, deps, contexts } = bunja;
|
|
67
|
+
const store = React.useContext(BunjaStoreContext);
|
|
46
68
|
const rid = useRid();
|
|
47
69
|
const tuples = contexts.map((c) => [c, React.useContext(c)] as const);
|
|
48
70
|
const scopes = tuples.map(([context, value]) => getScope(context, value));
|
|
@@ -55,7 +77,7 @@ export function useBunja<T>(bunja: Bunja<T>): T {
|
|
|
55
77
|
.map(({ id }) => id)
|
|
56
78
|
.sort()
|
|
57
79
|
.join(",")}`;
|
|
58
|
-
const instance = (
|
|
80
|
+
const instance = store.get(bunja, biid, args);
|
|
59
81
|
React.useEffect(() => {
|
|
60
82
|
instance.reg(rid);
|
|
61
83
|
return () => instance.dereg(rid);
|
|
@@ -64,7 +86,7 @@ export function useBunja<T>(bunja: Bunja<T>): T {
|
|
|
64
86
|
scopes.forEach((scope) => scope.reg(rid));
|
|
65
87
|
return () => scopes.forEach((scope) => scope.dereg(rid));
|
|
66
88
|
}, [rid, ...scopes]);
|
|
67
|
-
return instance.value;
|
|
89
|
+
return instance.value as T;
|
|
68
90
|
}
|
|
69
91
|
|
|
70
92
|
const useRid = () => React.useState(() => useRid.counter++)[0];
|
|
@@ -79,7 +101,7 @@ function getScope(context: React.Context<any>, value: any) {
|
|
|
79
101
|
}
|
|
80
102
|
getScope.counter = 0;
|
|
81
103
|
|
|
82
|
-
class RefCounter<T = number> {
|
|
104
|
+
abstract class RefCounter<T = number> {
|
|
83
105
|
#disposed = false;
|
|
84
106
|
refs = new Set<T>();
|
|
85
107
|
reg(reference: T) {
|
|
@@ -91,30 +113,40 @@ class RefCounter<T = number> {
|
|
|
91
113
|
if (this.#disposed) return;
|
|
92
114
|
if (this.refs.size < 1) {
|
|
93
115
|
this.#disposed = true;
|
|
94
|
-
this
|
|
116
|
+
this.dispose();
|
|
95
117
|
}
|
|
96
118
|
});
|
|
97
119
|
}
|
|
120
|
+
abstract dispose: () => void;
|
|
98
121
|
}
|
|
99
122
|
|
|
100
|
-
|
|
101
|
-
|
|
123
|
+
const noop = () => {};
|
|
124
|
+
class BunjaInstance extends RefCounter {
|
|
125
|
+
#cleanup: (() => void) | undefined;
|
|
126
|
+
constructor(
|
|
127
|
+
public store: BunjaStore,
|
|
128
|
+
public biid: string,
|
|
129
|
+
public value: BunjaValue
|
|
130
|
+
) {
|
|
102
131
|
super();
|
|
103
132
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
133
|
+
reg(reference: number) {
|
|
134
|
+
this.#cleanup ??= this.value[Bunja.effect]?.() ?? noop;
|
|
135
|
+
super.reg(reference);
|
|
107
136
|
}
|
|
137
|
+
dispose = () => {
|
|
138
|
+
this.#cleanup?.();
|
|
139
|
+
this.store.delete(this.biid);
|
|
140
|
+
};
|
|
108
141
|
}
|
|
109
142
|
|
|
110
143
|
class Scope extends RefCounter {
|
|
111
144
|
constructor(
|
|
112
|
-
dispose: () => void,
|
|
145
|
+
public dispose: () => void,
|
|
113
146
|
public context: React.Context<any>,
|
|
114
147
|
public value: any,
|
|
115
148
|
public id: number
|
|
116
149
|
) {
|
|
117
150
|
super();
|
|
118
|
-
this[Symbol.dispose] = dispose;
|
|
119
151
|
}
|
|
120
152
|
}
|
package/package.json
CHANGED