Skip to content

Commit e6c343e

Browse files
authored
Merge pull request #365 from y0na24/feat/createStoreUpgrade
Корректный createStore с сохранением неизменяемого состояния, если не примитив 🧊
2 parents d2f2e29 + cf300de commit e6c343e

File tree

3 files changed

+52
-23
lines changed

3 files changed

+52
-23
lines changed

packages/core/src/bundle/helpers/createStore/createStore.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
import { useSyncExternalStore } from 'react';
2+
function isStateCreator(fn) {
3+
return typeof fn === 'function';
4+
}
5+
function isActionFunction(fn) {
6+
return typeof fn === 'function';
7+
}
28
/**
39
* @name createStore
410
* @description - Creates a store with state management capabilities
@@ -18,30 +24,34 @@ export const createStore = (createState) => {
1824
let state;
1925
const listeners = new Set();
2026
const setState = (action) => {
21-
const nextState = typeof action === 'function' ? action(state) : action;
27+
const nextState = isActionFunction(action) ? action(state) : action;
2228
if (!Object.is(nextState, state)) {
2329
const prevState = state;
24-
state = nextState;
30+
state =
31+
typeof nextState !== 'object' || nextState === null
32+
? nextState
33+
: Object.assign({}, state, nextState);
2534
listeners.forEach((listener) => listener(state, prevState));
2635
}
2736
};
28-
const getState = () => state;
29-
const getInitialState = () => state;
3037
const subscribe = (listener) => {
3138
listeners.add(listener);
3239
return () => listeners.delete(listener);
3340
};
34-
if (typeof createState === 'function') {
41+
const getState = () => state;
42+
const getInitialState = () => state;
43+
if (isStateCreator(createState)) {
3544
state = createState(setState, getState);
3645
} else {
3746
state = createState;
3847
}
39-
const useStore = (selector) =>
40-
useSyncExternalStore(
48+
function useStore(selector) {
49+
return useSyncExternalStore(
4150
subscribe,
4251
() => (selector ? selector(getState()) : getState()),
4352
() => (selector ? selector(getInitialState()) : getInitialState())
4453
);
54+
}
4555
return {
4656
set: setState,
4757
get: getState,

packages/core/src/helpers/createContext/createContext.demo.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const profileContext = createContext(DEFAULT_PROFILE);
66
const App = () => {
77
const name = profileContext.useSelect((state) => state.name);
88
const age = profileContext.useSelect((state) => state.age);
9-
const profile = profileContext.useSelect()!;
9+
const profile = profileContext.useSelect();
1010

1111
const nameField = useField({
1212
initialValue: DEFAULT_PROFILE.name

packages/core/src/helpers/createStore/createStore.ts

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
import { useSyncExternalStore } from 'react';
22

3-
type SetStateAction<Value> = ((prev: Value) => Value) | Value;
3+
type SetStateAction<Value> = ((prev: Value) => Partial<Value>) | Partial<Value>;
4+
5+
type Listener<Value> = (state: Value, prevState: Value) => void;
6+
47
type StateCreator<Value> = (
58
set: (action: SetStateAction<Value>) => void,
69
get: () => Value
710
) => Value;
811

12+
function isStateCreator<Value>(fn: unknown): fn is StateCreator<Value> {
13+
return typeof fn === 'function';
14+
}
15+
16+
function isActionFunction<Value>(fn: unknown): fn is (prev: Value) => Partial<Value> {
17+
return typeof fn === 'function';
18+
}
19+
920
export interface StoreApi<Value> {
1021
getInitialState: () => Value;
1122
getState: () => Value;
@@ -29,40 +40,48 @@ export interface StoreApi<Value> {
2940
* }));
3041
*/
3142
export const createStore = <Value>(createState: StateCreator<Value> | Value) => {
32-
type Listener = (state: Value, prevState: Value) => void;
3343
let state: Value;
34-
const listeners: Set<Listener> = new Set();
44+
const listeners: Set<Listener<Value>> = new Set();
3545

36-
const setState = (action: SetStateAction<Value>) => {
37-
const nextState =
38-
typeof action === 'function' ? (action as (state: Value) => Value)(state) : action;
46+
const setState: StoreApi<Value>['setState'] = (action: SetStateAction<Value>) => {
47+
const nextState = isActionFunction<Value>(action) ? action(state) : action;
3948

4049
if (!Object.is(nextState, state)) {
4150
const prevState = state;
42-
state = nextState;
51+
state =
52+
typeof nextState !== 'object' || nextState === null
53+
? nextState
54+
: Object.assign({}, state, nextState);
55+
4356
listeners.forEach((listener) => listener(state, prevState));
4457
}
4558
};
4659

47-
const getState = () => state;
48-
const getInitialState = () => state;
49-
50-
const subscribe = (listener: Listener) => {
60+
const subscribe = (listener: Listener<Value>) => {
5161
listeners.add(listener);
62+
5263
return () => listeners.delete(listener);
5364
};
54-
if (typeof createState === 'function') {
55-
state = (createState as StateCreator<Value>)(setState, getState);
65+
66+
const getState = () => state;
67+
const getInitialState = () => state;
68+
69+
if (isStateCreator<Value>(createState)) {
70+
state = createState(setState, getState);
5671
} else {
5772
state = createState;
5873
}
5974

60-
const useStore = <Selected>(selector?: (state: Value) => Selected) =>
61-
useSyncExternalStore(
75+
76+
function useStore(): Value;
77+
function useStore<Selected>(selector: (state: Value) => Selected): Selected;
78+
function useStore<Selected>(selector?: (state: Value) => Selected): Selected | Value {
79+
return useSyncExternalStore(
6280
subscribe,
6381
() => (selector ? selector(getState()) : getState()),
6482
() => (selector ? selector(getInitialState()) : getInitialState())
6583
);
84+
}
6685

6786
return {
6887
set: setState,

0 commit comments

Comments
 (0)