Skip to content

Commit 1913871

Browse files
authored
Merge pull request #93 from byte-fe/feat/useStore
perf(useStore): create/clean hash only when component mount/unmount
2 parents b5e82af + e06ac26 commit 1913871

File tree

2 files changed

+201
-21
lines changed

2 files changed

+201
-21
lines changed

README.md

Lines changed: 191 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const Todo = {
2323
items: ['Install react-model', 'Read github docs', 'Build App']
2424
},
2525
actions: {
26-
add: (s, actions, todo) => {
26+
add: todo => {
2727
// s is the readonly version of state
2828
// you can also return partial state here but don't need to keep immutable manually
2929
// state is the mutable state
@@ -33,14 +33,14 @@ const Todo = {
3333
}
3434

3535
// Model Register
36-
const { useStore } = Model({ Todo })
36+
const { useStore } = Model(Todo)
3737

3838
const App = () => {
3939
return <TodoList />
4040
}
4141

4242
const TodoList = () => {
43-
const [state, actions] = useStore('Todo')
43+
const [state, actions] = useStore()
4444
return <div>
4545
<Addon handler={actions.add} />
4646
{state.items.map((item, index) => (<Todo key={index} item={item} />))}
@@ -82,13 +82,19 @@ npm install react-model
8282
- [How can I disable the console debugger?](#how-can-i-disable-the-console-debugger)
8383
- [How can I add custom middleware](#how-can-i-add-custom-middleware)
8484
- [How can I make persist models](#how-can-i-make-persist-models)
85+
- [How can I deal with local state](#how-can-i-deal-with-local-state)
86+
- [actions throw error from immer.module.js](#actions-throw-error-from-immer.module.js)
8587

8688
## Core Concept
8789

8890
### Model
8991

9092
Every model have their own state and actions.
9193

94+
<details>
95+
<summary><del>old model</del> (will be deprecated in v3.0)</summary>
96+
<p>
97+
9298
```typescript
9399
const initialState = {
94100
counter: 0,
@@ -111,7 +117,7 @@ interface ActionsParamType = {
111117
get: undefined
112118
} // You only need to tag the type of params here !
113119

114-
const Model: ModelType<StateType, ActionsParamType> = {
120+
const model: ModelType<StateType, ActionsParamType> = {
115121
actions: {
116122
increment: async (state, _, params) => {
117123
return {
@@ -144,7 +150,73 @@ const Model: ModelType<StateType, ActionsParamType> = {
144150
state: initialState
145151
}
146152

147-
export default Model
153+
export default model
154+
155+
// You can use these types when use Class Components.
156+
// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>
157+
// type ConsumerType = { actions: ConsumerActionsType; state: StateType }
158+
// type ActionType = ConsumerActionsType
159+
// export { ConsumerType, StateType, ActionType }
160+
```
161+
</p>
162+
</details>
163+
164+
```typescript
165+
const initialState = {
166+
counter: 0,
167+
light: false,
168+
response: {}
169+
}
170+
171+
interface StateType = {
172+
counter: number
173+
light: boolean
174+
response: {
175+
code?: number
176+
message?: string
177+
}
178+
}
179+
180+
interface ActionsParamType = {
181+
increment: number
182+
openLight: undefined
183+
get: undefined
184+
} // You only need to tag the type of params here !
185+
186+
const model: NextModelType<StateType, ActionsParamType> = {
187+
actions: {
188+
increment: async (payload, { state }) => {
189+
return {
190+
counter: state.counter + (params || 1)
191+
}
192+
},
193+
openLight: async (_, { state, actions }) => {
194+
await actions.increment(1) // You can use other actions within the model
195+
await actions.get() // support async functions (block actions)
196+
actions.get()
197+
await actions.increment(1) // + 1
198+
await actions.increment(1) // + 2
199+
await actions.increment(1) // + 3 as expected !
200+
return { light: !state.light }
201+
},
202+
get: async () => {
203+
await new Promise((resolve, reject) =>
204+
setTimeout(() => {
205+
resolve()
206+
}, 3000)
207+
)
208+
return {
209+
response: {
210+
code: 200,
211+
message: `${new Date().toLocaleString()} open light success`
212+
}
213+
}
214+
}
215+
},
216+
state: initialState
217+
}
218+
219+
export default Model(model)
148220

149221
// You can use these types when use Class Components.
150222
// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>
@@ -157,7 +229,7 @@ export default Model
157229

158230
### Model Register
159231

160-
react-model keep the state and actions in a separate store. So you need to register them before using.
232+
react-model keep the state and actions in the separate private store. So you need to register them if you want to use them as the public models.
161233

162234
`model/index.ts`
163235

@@ -166,9 +238,9 @@ import { Model } from 'react-model'
166238
import Home from '../model/home'
167239
import Shared from '../model/shared'
168240

169-
const stores = { Home, Shared }
241+
const models = { Home, Shared }
170242

171-
export const { getInitialState, useStore, getState, getActions, subscribe, unsubscribe } = Model(stores)
243+
export const { getInitialState, useStore, getState, actions, subscribe, unsubscribe } = Model(models)
172244
```
173245

174246
[⇧ back to top](#table-of-contents)
@@ -180,7 +252,7 @@ The actions return from useStore can invoke the dom changes.
180252

181253
The execution of actions returned by useStore will invoke the rerender of current component first.
182254

183-
It's the only difference between the actions returned by useStore and getActions now.
255+
It's the only difference between the actions returned by useStore and actions now.
184256

185257
```tsx
186258
import React from 'react'
@@ -251,9 +323,9 @@ const BasicHook = () => {
251323

252324
### actions
253325

254-
You can call other models' actions with getActions api
326+
You can call other models' actions with actions api
255327

256-
getActions can be used in both class components and functional components.
328+
actions can be used in both class components and functional components.
257329

258330
```js
259331
import { actions } from './index'
@@ -271,6 +343,8 @@ const model = {
271343
export default model
272344
```
273345

346+
[⇧ back to top](#table-of-contents)
347+
274348
### subscribe
275349

276350
subscribe(storeName, actions, callback) run the callback when the specific actions executed.
@@ -307,7 +381,7 @@ TypeScript Example
307381
// StateType and ActionsParamType definition
308382
// ...
309383

310-
const Model: ModelType<StateType, ActionsParamType> = {
384+
const model: NextModelType<StateType, ActionsParamType> = {
311385
actions: {
312386
increment: async (s, _, params) => {
313387
// issue: https://github.com/Microsoft/TypeScript/issues/29196
@@ -321,6 +395,8 @@ const Model: ModelType<StateType, ActionsParamType> = {
321395
}
322396
}
323397
}
398+
399+
export default Model(model)
324400
```
325401

326402
JavaScript Example
@@ -350,9 +426,9 @@ const initialState = {
350426
counter: 0
351427
}
352428

353-
const Model: ModelType<StateType, ActionsParamType> = {
429+
const model: NextModelType<StateType, ActionsParamType> = {
354430
actions: {
355-
increment: (state, _, params) => {
431+
increment: (params, { state }) => {
356432
return {
357433
counter: state.counter + (params || 1)
358434
}
@@ -365,6 +441,8 @@ const Model: ModelType<StateType, ActionsParamType> = {
365441
},
366442
state: initialState
367443
}
444+
445+
export default Model(model)
368446
```
369447

370448
</p>
@@ -674,3 +752,102 @@ Model({ Example }, JSON.parse(localStorage.getItem('__REACT_MODEL__')))
674752
```
675753
676754
[⇧ back to top](#table-of-contents)
755+
756+
### How can I deal with local state
757+
758+
What should I do to make every Counter hold there own model? 🤔
759+
760+
```tsx
761+
class App extends Component {
762+
render() {
763+
return (
764+
<div className="App">
765+
<Counter />
766+
<Counter />
767+
<Counter />
768+
</div>
769+
)
770+
}
771+
}
772+
```
773+
774+
<details>
775+
<summary>Counter model</summary>
776+
<p>
777+
778+
```ts
779+
interface State {
780+
count: number
781+
}
782+
783+
interface ActionParams {
784+
increment: number
785+
}
786+
787+
const model: NextModelType<State, ActionParams> = {
788+
state: {
789+
count: 0
790+
},
791+
actions: {
792+
increment: payload => {
793+
// immer.module.js:972 Uncaught (in promise) Error: An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft
794+
// Not allowed
795+
// return state => (state.count += payload)
796+
return state => {
797+
state.count += payload
798+
}
799+
}
800+
}
801+
}
802+
803+
```
804+
805+
</p>
806+
</details>
807+
808+
<details>
809+
<summary>Counter.tsx</summary>
810+
<p>
811+
812+
```tsx
813+
814+
const Counter = () => {
815+
const [{ useStore }] = useState(() => Model(model))
816+
const [state, actions] = useStore()
817+
return (
818+
<div>
819+
<div>{state.count}</div>
820+
<button onClick={() => actions.increment(3)}>Increment</button>
821+
</div>
822+
)
823+
}
824+
825+
export default Counter
826+
```
827+
828+
</p>
829+
</details>
830+
831+
[⇧ back to top](#table-of-contents)
832+
833+
### actions throw error from immer.module.js
834+
835+
```
836+
immer.module.js:972 Uncaught (in promise) Error: An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft
837+
```
838+
839+
How to fix:
840+
841+
```tsx
842+
actions: {
843+
increment: payload => {
844+
// Not allowed
845+
// return state => (state.count += payload)
846+
return state => {
847+
state.count += payload
848+
}
849+
}
850+
}
851+
```
852+
853+
[⇧ back to top](#table-of-contents)

src/index.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ function Model<M extends Models, MT extends NextModelType>(
2828
initialState?: Global['State']
2929
) {
3030
if (isNextModelType(models)) {
31+
Global.uid += 1
3132
const hash = '__' + Global.uid
3233
Global.State[hash] = models.state
3334
const nextActions: Actions = Object.entries(models.actions).reduce(
@@ -163,17 +164,19 @@ const getActions = (
163164

164165
const useStore = (modelName: string, depActions?: string[]) => {
165166
const setState = useState(Global.State[modelName])[1]
166-
Global.uid += 1
167-
const hash = '' + Global.uid
168-
if (!Global.Setter.functionSetter[modelName]) {
169-
Global.Setter.functionSetter[modelName] = {}
170-
}
171-
Global.Setter.functionSetter[modelName][hash] = { setState, depActions }
167+
172168
useEffect(() => {
169+
Global.uid += 1
170+
const hash = '' + Global.uid
171+
if (!Global.Setter.functionSetter[modelName]) {
172+
Global.Setter.functionSetter[modelName] = {}
173+
}
174+
Global.Setter.functionSetter[modelName][hash] = { setState, depActions }
173175
return function cleanup() {
174176
delete Global.Setter.functionSetter[modelName][hash]
175177
}
176-
})
178+
}, [])
179+
177180
const updaters = getActions(modelName, { setState, type: 'function' })
178181
return [getState(modelName), updaters]
179182
}

0 commit comments

Comments
 (0)