Skip to content

Commit f81dfa8

Browse files
committed
feat: parse model
1 parent e0381f5 commit f81dfa8

File tree

3 files changed

+200
-2
lines changed

3 files changed

+200
-2
lines changed

package-lock.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/Container.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { shallowEqual } from '../utils/shallowEqual'
22
import { Subscriber } from '../types'
33

44
export class Container<T> {
5-
private subscribers: Subscriber<T>[] = []
5+
public subscribers: Subscriber<T>[] = []
66

77
notify(payload?: T): void {
88
for (let i = 0; i < this.subscribers.length; i++) {

src/core/Model.tsx

+194-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { useState, useRef, useEffect } from 'react'
2-
import { ModelConfig } from '../types'
2+
import produce from 'immer'
3+
import { ConfigReducer, ContextPropsModel, MapStateToProps, ModelConfig, ModelContextProps, ModelEffect, Subscriber } from '../types'
4+
import { invariant } from '../utils/invariant'
5+
import { isObject } from '../utils/type'
6+
import { createProvider } from './createProvider'
7+
import { Container } from './Container'
38

49
interface ModelOptions<C extends ModelConfig> {
510
storeName: string
@@ -11,7 +16,195 @@ interface ModelOptions<C extends ModelConfig> {
1116
}
1217

1318
export class Model<C extends ModelConfig> {
19+
public Provider: React.FC
20+
21+
private model: ContextPropsModel<C>
22+
private initialState: C['state']
23+
private container: Container<C['state']>
24+
private useContext: () => ModelContextProps
25+
1426
constructor(private options: ModelOptions<C>) {
27+
const { name, config, rootModel } = options
1528

29+
this.initialState = config.state
30+
this.model = this.initModel(config)
31+
this.container = new Container<C['state']>()
32+
33+
rootModel[name] = this.model
34+
35+
const [Provider, useContext] = createProvider(this.model)
36+
37+
this.Provider = Provider
38+
this.useContext = useContext
39+
}
40+
41+
public useModel(mapStateToProps: MapStateToProps<C['state']>): any {
42+
const [, dispatcher] = useState()
43+
const { subscribers } = this.container
44+
const { model } = this.useContext()
45+
46+
const subscriberRef = useRef<Subscriber<C['state']>>()
47+
48+
// 组件初始化时注册监听函数,使用 useRef 保证每个组件只注册一次
49+
if (!subscriberRef.current) {
50+
const subscriber = {
51+
dispatcher,
52+
mapStateToProps,
53+
prevState: mapStateToProps(this.model.state),
54+
}
55+
56+
subscriberRef.current = subscriber
57+
subscribers.push(subscriber)
58+
}
59+
60+
/* eslint-disable-next-line */
61+
useEffect(() => {
62+
return (): void => {
63+
// 组件卸载时解绑监听函数
64+
const index = subscribers.indexOf(subscriberRef.current!)
65+
subscribers.splice(index, 1)
66+
}
67+
}, [])
68+
69+
invariant(
70+
isObject(model),
71+
'[store.useModel] You should add <Provider></Provider> or withProvider() in the upper layer when calling useModel'
72+
)
73+
74+
const state = mapStateToProps(model.state)
75+
76+
return {
77+
state,
78+
reducers: model.reducers,
79+
effects: model.effects,
80+
}
81+
}
82+
83+
private produceState(state: C['state'], reducer: ConfigReducer, payload: any = []): C['state'] {
84+
let newState
85+
86+
if (typeof state === 'object') {
87+
newState = produce(state, draft => {
88+
reducer(draft, ...payload)
89+
})
90+
} else {
91+
newState = reducer(state, ...payload)
92+
}
93+
94+
return newState
95+
}
96+
97+
private notify(name: string, state: C['state']): void {
98+
this.container.notify(state)
99+
}
100+
101+
private getReducers(config: C): ContextPropsModel<C>['reducers'] {
102+
const reducers: ContextPropsModel<C>['reducers'] = Object.keys(config.reducers).reduce(
103+
(reducers, name) => {
104+
const originReducer = config.reducers[name]
105+
106+
const reducer = (...payload: any[]): void => {
107+
const newState = this.produceState(this.model.state, originReducer, payload)
108+
109+
this.model.state = newState
110+
this.notify(name, newState)
111+
}
112+
113+
reducers[name] = reducer
114+
return reducers
115+
},
116+
Object.create(null)
117+
)
118+
119+
// 如果用户没有定义 setValue 则内置该方法
120+
if (!reducers.setValue) {
121+
reducers.setValue = (key, value): void => {
122+
const newState = this.produceState(this.model.state, draft => {
123+
draft[key] = value
124+
})
125+
126+
this.model.state = newState
127+
this.notify('setValue', newState)
128+
}
129+
}
130+
131+
// 如果用户没有定义 setValues 则内置该方法
132+
if (!reducers.setValues) {
133+
reducers.setValues = (partialState): void => {
134+
const newState = this.produceState(this.model.state, draft => {
135+
Object.keys(partialState).forEach(key => {
136+
draft[key] = Object.assign(draft[key], partialState[key])
137+
})
138+
})
139+
140+
this.model.state = newState
141+
142+
this.notify('setValues', newState)
143+
}
144+
}
145+
146+
// 如果用户没有定义 reset 则内置该方法
147+
if (!reducers.reset) {
148+
reducers.reset = (key): void => {
149+
const newState = this.produceState(this.model.state, draft => {
150+
if (key) {
151+
draft[key] = this.initialState[key]
152+
} else {
153+
Object.keys(this.initialState).forEach(key => {
154+
draft[key] = this.initialState[key]
155+
})
156+
}
157+
})
158+
159+
this.model.state = newState
160+
this.notify('reset', newState)
161+
}
162+
}
163+
164+
return reducers
165+
}
166+
167+
private getEffects(config: C): ContextPropsModel<C>['effects'] {
168+
return Object.keys(config.effects).reduce((effects, name) => {
169+
const originEffect = config.effects[name]
170+
171+
const effect: ModelEffect<typeof originEffect> = async (...payload: any): Promise<void> => {
172+
try {
173+
effect.identifier++
174+
effect.loading = true
175+
176+
this.container.notify(null)
177+
178+
const result = await originEffect(...payload)
179+
return result
180+
} catch (error) {
181+
throw error
182+
} finally {
183+
effect.identifier--
184+
185+
/* istanbul ignore else */
186+
if (effect.identifier === 0) {
187+
effect.loading = false
188+
this.container.notify(null)
189+
}
190+
}
191+
}
192+
193+
effect.loading = false
194+
effect.identifier = 0
195+
effects[name] = effect
196+
197+
return effects
198+
}, Object.create(null))
199+
}
200+
201+
private initModel(config: C): ContextPropsModel<C> {
202+
// @ts-ignore
203+
config.reducers = this.getReducers(config)
204+
// @ts-ignore
205+
config.effects = this.getEffects(config)
206+
207+
// @ts-ignore
208+
return config
16209
}
17210
}

0 commit comments

Comments
 (0)