-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
150 lines (128 loc) · 3.47 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/**
* let deku component be stateful
* @param {Function} Component
*/
function stateful(Component) {
//Component as a function
if(typeof Component === 'function'){
Component = {render: Component}
}
let {shouldUpdate=() => {return true}} = Component
//cache states
let _states = {}
//cache components
let _components = {}
// cache previous props and state
let _previous = {}
const INIT_TYPE = '_stateful_init_dispatch_type'
/**
* let options be with state.
* @param {Object} options
*/
let model = function(options){
let {path} = options
let state = _states[path]
return {...options, state}
}
let _updatePrevious = function({path, props}){
let state = _states[path]
_previous[path] = {..._previous[path], prevProps: props, prevState: state}
}
/**
* run before render, do some initial operation
* @param {Object}
*/
let onBefore = function(options){
let {path, props, dispatch} = options
if(!Component.setState){
Component.setState = (data) => {
_states[path] = {..._states[path], ...data}
return dispatch({..._states[path], type: INIT_TYPE})
}
}
if(!_previous[path]){
_previous[path] = {}
}
if(!_states[path]){
//initial state and only run one time before render.
if(Component.initialState){
let s = Component.initialState({props, path})
_states[path] = s || {}
}
if(props.state){
_states[path] = {...state, ...props.state}
}
if(Component.onBefore){
Component.onBefore({state, ...options})
}
}
}
/**
* add state to onCreate
* @param {Object} options
*/
let onCreate = function(options){
if(Component.onCreate){
return Component.onCreate(model(options))
}
}
/**
* add state to onCreate
* @param {Object} options
*/
let onUpdate = function(options){
let {path} = options, ret
let md = model(options)
let {prevProps, prevState} = _previous[path]
if(Component.onUpdate && shouldUpdate && shouldUpdate({...md, prevProps, prevState})){
ret = Component.onUpdate(md)
//update previous props and state
_updatePrevious(options)
}
return ret
}
/**
* add shouldUpdate and onBefore to render
* note that Component.onBefore only run one time.
* @param {Object} options
*/
let render = function(options){
onBefore(options)
let {path, props, dispatch} = options
let {setState} = Component
let state = _states[path]
let {prevProps, prevState} = _previous[path]
//cache the component
//you can optimise the app by using shouldUpdate
if(!_components[path]){
_components[path] = Component.render({...options, state, setState})
//update previous props and state when create
_updatePrevious(options)
}else if(shouldUpdate && shouldUpdate({...options, state, prevProps, prevState})){
_components[path] = Component.render({...options, state, setState})
}
return _components[path]
}
/**
* reset cache when component removed
* @param {Object} options
*/
let onRemove = function(options){
let {path} = options
if(Component.onRemove){
Component.onRemove(model(options))
}
delete _states[path]
delete _components[path]
delete _previous[path]
}
return Object.assign({},
Component,
{
render,
onUpdate,
onCreate,
onRemove
})
}
export default stateful