Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commiteefe25a

Browse files
authored
Merge pull request#117 from udecode/react/decouple
Decouple react
2 parents3dc3fed +9389b32 commiteefe25a

19 files changed

+876
-410
lines changed

‎.changeset/pink-guests-jump.md‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
'zustand-x':minor
3+
---
4+
5+
- Introduced a React-free`createVanillaStore` in`zustand-x/vanilla` using`zustand/vanilla`.
6+
- Extracted shared internal logic (`middleware`,`options parsing`,`selector/action helpers`) to`src/internal` for reuse.
7+
- Refactored React entry (`createStore`, hooks) to use shared internal logic without breaking existing API.
8+
- Split types: base hook-free definitions vs React-specific types for compatibility with both entries.
9+
- Updated package exports to include`./vanilla` while keeping`.` for React.
10+
- Added vanilla-focused tests to ensure store functionality works without React.
11+
- Ensured middleware, selector/action extensions, and mutative utilities work in both vanilla and React contexts.
12+
-**Example usage (vanilla, no React):**
13+
14+
```ts
15+
import {createVanillaStore }from'zustand-x/vanilla';
16+
17+
const counterStore=createVanillaStore(
18+
{ count:0 },
19+
{ name:'vanilla-counter', persist:true }
20+
)
21+
.extendSelectors(({get })=> ({
22+
doubled: ()=>get('count')*2,
23+
}))
24+
.extendActions(({set })=> ({
25+
increment: ()=>set('count', (value)=>value+1),
26+
}));
27+
28+
counterStore.actions.increment();
29+
console.log(counterStore.get('doubled'));// 2
30+
```

‎config/tsup.config.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,18 @@ const INPUT_FILE = fs.existsSync(INPUT_FILE_PATH)
1111
?INPUT_FILE_PATH
1212
:path.join(PACKAGE_ROOT_PATH,'src/index.tsx');
1313

14+
constVANILLA_ENTRY_PATH=path.join(PACKAGE_ROOT_PATH,'src/lib/index.ts');
15+
1416
exportdefaultdefineConfig((opts)=>{
17+
constentryPoints=[INPUT_FILE];
18+
19+
if(fs.existsSync(VANILLA_ENTRY_PATH)){
20+
entryPoints.push(VANILLA_ENTRY_PATH);
21+
}
22+
1523
return{
1624
...opts,
17-
entry:[INPUT_FILE],
25+
entry:entryPoints,
1826
format:['cjs','esm'],
1927
dts:true,
2028
sourcemap:true,

‎packages/zustand-x/README.md‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,35 @@ function AddStarButton() {
5656
}
5757
```
5858

59+
##Vanilla Usage (No React)
60+
61+
Need the same ergonomics without bundling React? Import from the`zustand-x/vanilla` entry to get a plain Zustand store augmented with actions and selectors.
62+
63+
```ts
64+
import {createVanillaStore }from'zustand-x/vanilla';
65+
66+
const counterStore=createVanillaStore(
67+
{
68+
count:0,
69+
},
70+
{
71+
name:'vanilla-counter',
72+
persist:true,
73+
}
74+
)
75+
.extendSelectors(({get })=> ({
76+
doubled: ()=>get('count')*2,
77+
}))
78+
.extendActions(({set })=> ({
79+
increment: ()=>set('count', (value)=>value+1),
80+
}));
81+
82+
counterStore.actions.increment();
83+
console.log(counterStore.get('doubled'));// 2
84+
```
85+
86+
This API exposes`get`,`set`,`subscribe`,`extendSelectors`, and`extendActions` without attaching any React hooks, so it can run in Node.js, workers, or any non-React environment.
87+
5988
##Core Concepts
6089

6190
###Store Configuration

‎packages/zustand-x/package.json‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
"import":"./dist/index.mjs",
2525
"module":"./dist/index.mjs",
2626
"require":"./dist/index.js"
27+
},
28+
"./vanilla": {
29+
"types":"./dist/lib/index.d.ts",
30+
"import":"./dist/lib/index.mjs",
31+
"module":"./dist/lib/index.mjs",
32+
"require":"./dist/lib/index.js"
2733
}
2834
},
2935
"scripts": {

‎packages/zustand-x/src/createStore.ts‎

Lines changed: 10 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
import{createTrackedSelector}from'react-tracked';
2-
import{subscribeWithSelector}from'zustand/middleware';
32
import{createWithEqualityFnascreateStoreZustand}from'zustand/traditional';
43

5-
import{
6-
devToolsMiddleware,
7-
immerMiddleware,
8-
persistMiddleware,
9-
}from'./middlewares';
10-
import{mutativeMiddleware}from'./middlewares/mutative';
4+
import{buildStateCreator}from'./internal/buildStateCreator';
5+
import{createBaseApi}from'./internal/createBaseApi';
116
import{DefaultMutators,TBaseStoreOptions,TState}from'./types';
12-
import{TMiddleware}from'./types/middleware';
13-
import{getOptions}from'./utils/helpers';
147
import{storeFactory}from'./utils/storeFactory';
158

169
importtype{TStateApiForBuilder}from'./types';
@@ -34,139 +27,19 @@ export const createStore = <
3427
options:CreateStoreOptions
3528
)=>{
3629
typeMutators=[...DefaultMutators<StateType,CreateStoreOptions>, ...Mcs];
37-
const{
38-
name,
39-
devtools:devtoolsOptions,
40-
immer:immerOptions,
41-
mutative:mutativeOptions,
42-
persist:persistOptions,
43-
isMutativeState,
44-
}=options;
45-
46-
//current middlewares order devTools(persist(immer(initiator)))
47-
constmiddlewares:TMiddleware[]=[];
48-
49-
//enable devtools
50-
const_devtoolsOptionsInternal=getOptions(devtoolsOptions);
51-
if(_devtoolsOptionsInternal.enabled){
52-
middlewares.push((config)=>
53-
devToolsMiddleware(config,{
54-
..._devtoolsOptionsInternal,
55-
name:_devtoolsOptionsInternal?.name??name,
56-
})
57-
);
58-
}
59-
60-
//enable persist
61-
const_persistOptionsInternal=getOptions(persistOptions);
62-
if(_persistOptionsInternal.enabled){
63-
middlewares.push((config)=>
64-
persistMiddleware(config,{
65-
..._persistOptionsInternal,
66-
name:_persistOptionsInternal.name??name,
67-
})
68-
);
69-
}
70-
71-
//enable immer
72-
const_immerOptionsInternal=getOptions(immerOptions);
73-
if(_immerOptionsInternal.enabled){
74-
middlewares.push((config)=>
75-
immerMiddleware(config,_immerOptionsInternal)
76-
);
77-
}
78-
79-
//enable mutative
80-
const_mutativeOptionsInternal=getOptions(mutativeOptions);
81-
if(_mutativeOptionsInternal.enabled){
82-
middlewares.push((config)=>
83-
mutativeMiddleware(config,_mutativeOptionsInternal)
84-
);
85-
}
86-
87-
conststateMutators=middlewares
88-
.reverse()
89-
.reduce(
90-
(y,fn)=>fn(y),
91-
(typeofinitializer==='function'
92-
?initializer
93-
:()=>initializer)asStateCreator<StateType>
94-
)asStateCreator<StateType,[],Mutators>;
95-
96-
conststore=createStoreZustand(subscribeWithSelector(stateMutators));
30+
constbuilder=buildStateCreator(initializer,options);
31+
conststore=createStoreZustand(builder.stateCreator);
9732

9833
constuseTrackedStore=createTrackedSelector(store);
9934

10035
constuseTracked=(key:string)=>{
10136
returnuseTrackedStore()[keyaskeyofStateType];
10237
};
10338

104-
constgetFn=(key:string)=>{
105-
if(key==='state'){
106-
returnstore.getState();
107-
}
108-
109-
returnstore.getState()[keyaskeyofStateType];
110-
};
111-
112-
constsubscribeFn=(
113-
key:string,
114-
selector:any,
115-
listener:any,
116-
subscribeOptions:any
117-
)=>{
118-
if(key==='state'){
119-
//@ts-expect-error -- typescript is unable to infer the 3 args version
120-
returnstore.subscribe(selector,listener,subscribeOptions);
121-
}
122-
123-
letwrappedSelector:any;
124-
125-
if(listener){
126-
// subscribe(selector, listener, subscribeOptions) variant
127-
wrappedSelector=(state:StateType)=>
128-
selector(state[keyaskeyofStateType]);
129-
}else{
130-
// subscribe(listener) variant
131-
listener=selector;
132-
wrappedSelector=(state:StateType)=>state[keyaskeyofStateType];
133-
}
134-
135-
//@ts-expect-error -- typescript is unable to infer the 3 args version
136-
returnstore.subscribe(wrappedSelector,listener,subscribeOptions);
137-
};
138-
139-
constisMutative=
140-
isMutativeState||
141-
_immerOptionsInternal.enabled||
142-
_mutativeOptionsInternal.enabled;
143-
144-
constsetFn=(key:string,value:any)=>{
145-
if(key==='state'){
146-
return(store.setStateasany)(value);
147-
}
148-
149-
consttypedKey=keyaskeyofStateType;
150-
constprevValue=store.getState()[typedKey];
151-
152-
if(typeofvalue==='function'){
153-
value=value(prevValue);
154-
}
155-
if(prevValue===value)return;
156-
157-
constactionKey=key.replace(/^\S/,(s)=>s.toUpperCase());
158-
constdebugLog=name ?`@@${name}/set${actionKey}` :undefined;
159-
160-
(store.setStateasany)?.(
161-
isMutative
162-
?(draft:StateType)=>{
163-
draft[typedKey]=value;
164-
}
165-
:{[typedKey]:value},
166-
undefined,
167-
debugLog
168-
);
169-
};
39+
constbaseApi=createBaseApi<StateType,Mutators,{},{}>(store,{
40+
name:builder.name,
41+
isMutative:builder.isMutative,
42+
});
17043

17144
constuseValue=(
17245
key:string,
@@ -181,22 +54,17 @@ export const createStore = <
18154
)=>{
18255
constvalue=useValue(key,equalityFn);
18356

184-
return[value,(val:any)=>setFn(key,val)];
57+
return[value,(val:any)=>baseApi.set(keyaskeyofStateType,val)];
18558
};
18659

18760
constapiInternal={
188-
get:getFn,
189-
name,
190-
set:setFn,
191-
subscribe:subscribeFn,
61+
...baseApi,
19262
store,
19363
useStore:store,
19464
useValue,
19565
useState,
19666
useTracked,
19767
useTrackedStore,
198-
actions:{},
199-
selectors:{},
20068
}asanyasTStateApiForBuilder<StateType,Mutators>;
20169

20270
returnstoreFactory(apiInternal);
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import{subscribeWithSelector}from'zustand/middleware';
2+
3+
import{
4+
devToolsMiddleware,
5+
immerMiddleware,
6+
persistMiddleware,
7+
}from'../middlewares';
8+
import{mutativeMiddleware}from'../middlewares/mutative';
9+
import{DefaultMutators,TBaseStoreOptions,TState}from'../types';
10+
import{TMiddleware}from'../types/middleware';
11+
import{getOptions}from'../utils/helpers';
12+
13+
importtype{StateCreator,StoreMutatorIdentifier}from'zustand';
14+
15+
typeBuildStateCreatorResult<
16+
StateTypeextendsTState,
17+
Mutatorsextends[StoreMutatorIdentifier,unknown][],
18+
>={
19+
stateCreator:StateCreator<StateType,[],Mutators>;
20+
isMutative:boolean;
21+
name:string;
22+
};
23+
24+
exportconstbuildStateCreator=<
25+
StateTypeextendsTState,
26+
Mpsextends[StoreMutatorIdentifier,unknown][]=[],
27+
Mcsextends[StoreMutatorIdentifier,unknown][]=[],
28+
CreateStoreOptionsextends
29+
TBaseStoreOptions<StateType>=TBaseStoreOptions<StateType>,
30+
>(
31+
initializer:StateType|StateCreator<StateType,Mps,Mcs>,
32+
options:CreateStoreOptions
33+
)=>{
34+
typeMutators=[...DefaultMutators<StateType,CreateStoreOptions>, ...Mcs];
35+
const{
36+
name,
37+
devtools:devtoolsOptions,
38+
immer:immerOptions,
39+
mutative:mutativeOptions,
40+
persist:persistOptions,
41+
isMutativeState,
42+
}=options;
43+
44+
constmiddlewares:TMiddleware[]=[];
45+
46+
constdevtoolsConfig=getOptions(devtoolsOptions);
47+
if(devtoolsConfig.enabled){
48+
middlewares.push((config)=>
49+
devToolsMiddleware(config,{
50+
...devtoolsConfig,
51+
name:devtoolsConfig?.name??name,
52+
})
53+
);
54+
}
55+
56+
constpersistConfig=getOptions(persistOptions);
57+
if(persistConfig.enabled){
58+
middlewares.push((config)=>
59+
persistMiddleware(config,{
60+
...persistConfig,
61+
name:persistConfig.name??name,
62+
})
63+
);
64+
}
65+
66+
constimmerConfig=getOptions(immerOptions);
67+
if(immerConfig.enabled){
68+
middlewares.push((config)=>immerMiddleware(config,immerConfig));
69+
}
70+
71+
constmutativeConfig=getOptions(mutativeOptions);
72+
if(mutativeConfig.enabled){
73+
middlewares.push((config)=>mutativeMiddleware(config,mutativeConfig));
74+
}
75+
76+
conststateCreator=middlewares
77+
.reverse()
78+
.reduce(
79+
(creator,middleware)=>middleware(creator),
80+
(typeofinitializer==='function'
81+
?initializer
82+
:()=>initializer)asStateCreator<StateType>
83+
)asStateCreator<StateType,[],Mutators>;
84+
85+
constisMutative=
86+
isMutativeState||immerConfig.enabled||mutativeConfig.enabled;
87+
88+
return{
89+
stateCreator:subscribeWithSelector(stateCreator),
90+
isMutative,
91+
name,
92+
}asBuildStateCreatorResult<StateType,Mutators>;
93+
};

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp