1
1
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'
3
8
4
9
interface ModelOptions < C extends ModelConfig > {
5
10
storeName : string
@@ -11,7 +16,195 @@ interface ModelOptions<C extends ModelConfig> {
11
16
}
12
17
13
18
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
+
14
26
constructor ( private options : ModelOptions < C > ) {
27
+ const { name, config, rootModel } = options
15
28
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
16
209
}
17
210
}
0 commit comments