Skip to content

Commit b665616

Browse files
committed
feat(store): add createStore + useModel apis
1 parent ffb11fa commit b665616

File tree

7 files changed

+366
-63
lines changed

7 files changed

+366
-63
lines changed

__test__/Model/lane.spec.ts

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/// <reference path="../index.d.ts" />
2+
import { renderHook, act } from '@testing-library/react-hooks'
3+
import { createStore, useModel } from '../../src'
4+
5+
describe('lane model', () => {
6+
test('single model', async () => {
7+
const { useStore } = createStore(() => {
8+
const [count, setCount] = useModel(1)
9+
return { count, setCount }
10+
})
11+
let renderTimes = 0
12+
const { result } = renderHook(() => {
13+
const { count, setCount } = useStore()
14+
renderTimes += 1
15+
return { renderTimes, count, setCount }
16+
})
17+
await act(async () => {
18+
expect(renderTimes).toEqual(1)
19+
expect(result.current.count).toBe(1)
20+
})
21+
22+
await act(async () => {
23+
await result.current.setCount(5)
24+
})
25+
26+
await act(() => {
27+
expect(renderTimes).toEqual(2)
28+
expect(result.current.count).toBe(5)
29+
})
30+
})
31+
32+
test('multiple models', async () => {
33+
const { useStore } = createStore(() => {
34+
const [count, setCount] = useModel(1)
35+
const [name, setName] = useModel('Jane')
36+
return { count, name, setName, setCount }
37+
})
38+
let renderTimes = 0
39+
const { result } = renderHook(() => {
40+
const { count, setCount, name, setName } = useStore()
41+
renderTimes += 1
42+
return { renderTimes, count, setCount, name, setName }
43+
})
44+
await act(async () => {
45+
expect(renderTimes).toEqual(1)
46+
expect(result.current.count).toBe(1)
47+
})
48+
49+
await act(async () => {
50+
await result.current.setCount(5)
51+
})
52+
53+
await act(() => {
54+
expect(renderTimes).toEqual(2)
55+
expect(result.current.count).toBe(5)
56+
expect(result.current.name).toBe('Jane')
57+
})
58+
59+
await act(async () => {
60+
await result.current.setName('Bob')
61+
})
62+
63+
await act(() => {
64+
expect(renderTimes).toEqual(3)
65+
expect(result.current.name).toBe('Bob')
66+
expect(result.current.count).toBe(5)
67+
})
68+
})
69+
70+
test('multiple stores', async () => {
71+
const { useStore } = createStore(() => {
72+
const [count, setCount] = useModel(1)
73+
74+
return { count, setCount }
75+
})
76+
77+
const { useStore: useOtherStore } = createStore(() => {
78+
const [name, setName] = useModel('Jane')
79+
return { name, setName }
80+
})
81+
let renderTimes = 0
82+
const { result } = renderHook(() => {
83+
const { count, setCount } = useStore()
84+
const { name, setName } = useOtherStore()
85+
renderTimes += 1
86+
return { renderTimes, count, setCount, name, setName }
87+
})
88+
await act(async () => {
89+
expect(renderTimes).toEqual(1)
90+
expect(result.current.count).toBe(1)
91+
})
92+
93+
await act(async () => {
94+
await result.current.setCount(5)
95+
})
96+
97+
await act(() => {
98+
expect(renderTimes).toEqual(2)
99+
expect(result.current.count).toBe(5)
100+
expect(result.current.name).toBe('Jane')
101+
})
102+
103+
await act(async () => {
104+
await result.current.setName('Bob')
105+
})
106+
107+
await act(() => {
108+
expect(renderTimes).toEqual(3)
109+
expect(result.current.name).toBe('Bob')
110+
expect(result.current.count).toBe(5)
111+
})
112+
})
113+
114+
test('share single model between components', async () => {
115+
const { useStore } = createStore(() => {
116+
const [count, setCount] = useModel(1)
117+
return { count, setCount }
118+
})
119+
let renderTimes = 0
120+
const { result } = renderHook(() => {
121+
const { count, setCount } = useStore()
122+
renderTimes += 1
123+
return { renderTimes, count, setCount }
124+
})
125+
126+
const { result: mirrorResult } = renderHook(() => {
127+
const { count, setCount } = useStore()
128+
renderTimes += 1
129+
return { renderTimes, count, setCount }
130+
})
131+
await act(async () => {
132+
expect(renderTimes).toEqual(2)
133+
expect(mirrorResult.current.count).toBe(1)
134+
})
135+
136+
await act(async () => {
137+
await result.current.setCount(5)
138+
})
139+
140+
await act(() => {
141+
expect(renderTimes).toEqual(4)
142+
expect(mirrorResult.current.count).toBe(5)
143+
})
144+
})
145+
146+
test('complex case', async () => {
147+
const { useStore, getState } = createStore(() => {
148+
const [count, setCount] = useModel(1)
149+
const [name, setName] = useModel('Jane')
150+
return { count, setCount, name, setName }
151+
})
152+
const { useStore: useOtherStore } = createStore(() => {
153+
const [data, setData] = useModel({ status: 'UNKNOWN' })
154+
return { data, setData }
155+
})
156+
const { useStore: useOnce } = createStore(() => {
157+
const [status, set] = useModel(false)
158+
return { status, set }
159+
})
160+
let renderTimes = 0
161+
const { result } = renderHook(() => {
162+
const { count, setCount } = useStore()
163+
const { setData } = useOtherStore()
164+
renderTimes += 1
165+
return { renderTimes, count, setCount, setData }
166+
})
167+
168+
const { result: mirrorResult } = renderHook(() => {
169+
const { setName, name } = useStore()
170+
const { data } = useOtherStore()
171+
const { status, set } = useOnce()
172+
renderTimes += 1
173+
return { renderTimes, data, setName, name, status, set }
174+
})
175+
await act(async () => {
176+
expect(renderTimes).toEqual(2)
177+
expect(mirrorResult.current.name).toBe('Jane')
178+
})
179+
180+
await act(async () => {
181+
await result.current.setData({ status: 'SUCCESS' })
182+
})
183+
184+
// Both component will rerender
185+
// TODO: Only rerender second FC
186+
await act(() => {
187+
expect(renderTimes).toEqual(4)
188+
expect(mirrorResult.current.data).toEqual({ status: 'SUCCESS' })
189+
})
190+
191+
await act(async () => {
192+
await mirrorResult.current.setName('Bob')
193+
})
194+
195+
await act(() => {
196+
expect(renderTimes).toEqual(6)
197+
expect(mirrorResult.current.name).toBe('Bob')
198+
expect(mirrorResult.current.status).toBe(false)
199+
})
200+
201+
await act(async () => {
202+
await mirrorResult.current.set(true)
203+
})
204+
205+
await act(() => {
206+
expect(renderTimes).toEqual(7)
207+
expect(mirrorResult.current.status).toBe(true)
208+
})
209+
210+
await act(async () => {
211+
await mirrorResult.current.setName('Jane')
212+
})
213+
214+
await act(() => {
215+
expect(renderTimes).toEqual(9)
216+
expect(mirrorResult.current.name).toBe('Jane')
217+
expect(mirrorResult.current.status).toBe(true)
218+
expect(getState().name).toBe('Jane')
219+
expect(getState().count).toBe(1)
220+
})
221+
})
222+
})

jest.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ module.exports = {
132132

133133
// The glob patterns Jest uses to detect test files
134134
// testMatch: [
135-
// "**/__tests__/**/*.[jt]s?(x)",
136-
// "**/?(*.)+(spec|test).[tj]s?(x)"
135+
// "**/__test__/**/lane.spec.[jt]s?(x)",
136+
// // "**/?(*.)+(spec|test).[tj]s?(x)"
137137
// ],
138138

139139
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"main": "./dist/react-model.js",
66
"module": "./dist/react-model.esm.js",
77
"umd:main": "./dist/react-model.umd.js",
8-
"types": "./src/index",
98
"scripts": {
109
"build:prod": "microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json",
1110
"build:dev": "microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json",

src/global.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const State = {}
2+
const mutableState = {}
23
const Actions = {}
34
const AsyncState = {}
45
const Middlewares = {}
@@ -22,6 +23,8 @@ let devTools: any
2223
let withDevTools = false
2324

2425
let uid = 0 // The unique id of hooks
26+
let storeId = 0 // The unique id of stores
27+
let currentStoreId = 0 // Used for useModel
2528

2629
export default {
2730
Actions,
@@ -32,6 +35,9 @@ export default {
3235
State,
3336
devTools,
3437
subscriptions,
38+
mutableState,
3539
uid,
40+
storeId,
41+
currentStoreId,
3642
withDevTools
3743
} as Global

src/index.d.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ interface Global {
2828
State: {
2929
[modelName: string]: any
3030
}
31+
mutableState: {
32+
[modelName: string]: any
33+
}
3134
AsyncState: {
3235
[modelName: string]: undefined | ((context?: any) => Promise<Partial<any>>)
3336
}
@@ -40,6 +43,8 @@ interface Global {
4043
devTools: any
4144
withDevTools: boolean
4245
uid: number
46+
storeId: number
47+
currentStoreId: number
4348
}
4449

4550
type ClassSetter = React.Dispatch<any> | undefined
@@ -63,6 +68,9 @@ type Actions<S = {}, ActionKeys = {}, ExtContext extends object = {}> = {
6368
[P in keyof ActionKeys]: Action<S, ActionKeys[P], ActionKeys, ExtContext>
6469
}
6570

71+
// v4.1+ Custom Hooks
72+
type CustomModelHook<State> = () => State
73+
6674
type Dispatch<A> = (value: A) => void
6775
type SetStateAction<S> = S | ((prevState: S) => S)
6876

@@ -143,6 +151,11 @@ interface API<MT extends ModelType = ModelType<any, any, {}>> {
143151
actions: Readonly<getConsumerActionsType<Get<MT, 'actions'>>>
144152
}
145153

154+
interface LaneAPI<S> {
155+
useStore: () => S
156+
getState: () => S
157+
}
158+
146159
interface APIs<M extends Models> {
147160
useStore: <
148161
K extends keyof M,

0 commit comments

Comments
 (0)