1
+ export class Component {
2
+ constructor ( props = { } ) {
3
+ this . props = props ;
4
+ this . state = null ;
5
+ }
6
+
7
+ setState ( nextState ) {
8
+ const isCompat = isObject ( this . state ) && isObject ( nextState ) ;
9
+ const commitState = ( ) => this . state = isCompat ? Object . assign ( { } , this . state , nextState ) : nextState ;
10
+ const prevState = isObject ( this . state ) ? Object . assign ( { } , this . state ) : this . state ;
11
+
12
+ if ( runHook ( this , 'shouldComponentUpdate' ) && this . base ) {
13
+ runHook ( this , 'componentWillUpdate' , this . props , nextState ) ;
14
+ commitState ( ) ;
15
+ patch ( this . base , this . render ( ) ) ;
16
+ runHook ( this , 'componentDidUpdate' , this . props , prevState ) ;
17
+ } else commitState ( ) ;
18
+ }
19
+
20
+ static render ( vnode , parent ) {
21
+ if ( isClassComponent ( vnode ) ) {
22
+ let instance = new vnode . type ( combineChildrenWithProps ( vnode ) ) ;
23
+ runHook ( instance , 'componentWillMount' ) ;
24
+ instance . base = render ( instance . render ( ) , parent ) ;
25
+ instance . base . instance = instance ;
26
+ runHook ( instance , 'componentDidMount' ) ;
27
+ return instance . base ;
28
+ } else return render ( vnode . type ( combineChildrenWithProps ( vnode ) ) , parent ) ;
29
+ }
30
+
31
+ static patch ( dom , vnode , parent = dom . parentNode ) {
32
+ if ( dom . instance && dom . instance . constructor == vnode . type ) {
33
+ runHook ( dom . instance , 'componentWillReceiveProps' , combineChildrenWithProps ( vnode ) ) ;
34
+ dom . instance . props = combineChildrenWithProps ( vnode ) ;
35
+ return patch ( dom , dom . instance . render ( ) , parent ) ;
36
+ } else if ( isClassComponent ( vnode . type ) ) {
37
+ const newdom = Component . render ( vnode , parent ) ;
38
+ return parent ? ( replace ( newdom , dom , parent ) && newdom ) : ( newdom ) ;
39
+ } else if ( ! isClassComponent ( vnode . type ) ) return patch ( dom , vnode . type ( combineChildrenWithProps ( vnode ) ) , parent ) ;
40
+ }
41
+ }
42
+
43
+ export const createElement = ( type , props , ...children ) => ( { type, props : props || { } , children } ) ;
44
+
45
+ export function render ( vnode , parent ) {
46
+ if ( isObject ( vnode ) ) {
47
+ let dom = isFunction ( vnode . type ) ? Component . render ( vnode , parent ) : document . createElement ( vnode . type ) ;
48
+ vnode . children . flat ( 1 ) . map ( ( child ) => render ( child , dom ) ) ;
49
+ ! isFunction ( vnode . type ) && Object . keys ( vnode . props ) . map ( ( key ) => setAttribute ( dom , key , vnode . props [ key ] ) ) ;
50
+ return mount ( dom , parent ) ;
51
+ } else return mount ( document . createTextNode ( vnode || '' ) , parent ) ;
52
+ }
53
+
54
+ function patch ( dom , vnode , parent = dom . parentNode ) {
55
+ if ( isObject ( vnode ) ) {
56
+ if ( isTextNode ( dom ) ) return replace ( render ( vnode , parent ) , dom , parent ) ;
57
+ else if ( isFunction ( vnode . type ) ) return Component . patch ( dom , vnode , parent ) ;
58
+ else {
59
+ let dom_map = Array . from ( dom . childNodes ) // Build a key value map to identify dom-node to its equivalent vnode
60
+ . reduce ( ( prev , node , idx ) => ( { ...prev , [ node . _idx || `__${ idx } ` ] : node } ) , { } ) ;
61
+
62
+ vnode . children . flat ( 1 ) . map ( ( child , idx ) => {
63
+ let key = ( child . props && child . props . key ) || `__${ idx } ` ;
64
+ mount ( dom_map [ key ] ? patch ( dom_map [ key ] , child , dom ) : render ( child , dom ) ) ;
65
+ delete dom_map [ key ] ; // marks dom-vnode pair available by removing from map
66
+ } ) ;
67
+
68
+ Object . values ( dom_map ) . forEach ( element => { // Unmount DOM nodes which are missing in the latest vnodes
69
+ runHook ( element . instance , 'componentWillUnmount' ) ;
70
+ element . remove ( ) ;
71
+ } ) ;
72
+
73
+ ! isFunction ( vnode . type ) && Object . keys ( vnode . props ) . map ( ( key ) => setAttribute ( dom , key , vnode . props [ key ] ) ) ;
74
+ }
75
+ }
76
+ else if ( isTextNode ( dom ) && dom . textContent != vnode ) return replace ( render ( vnode , parent ) , dom , parent ) ;
77
+ }
78
+
79
+ function setAttribute ( dom , key , value ) {
80
+ if ( key . startsWith ( 'on' ) && isFunction ( value ) ) delegateEvent ( dom , key , value ) ;
81
+ else if ( key == 'ref' && isFunction ( value ) ) value ( dom ) ;
82
+ else if ( [ 'checked' , 'value' , 'className' , 'key' ] . includes ( key ) ) dom [ key == 'key' ? '_idx' :key ] = value ;
83
+ else dom . setAttribute ( key , value ) ;
84
+ }
85
+
86
+ // Utils
87
+ const isFunction = ( node ) => typeof node == 'function' ;
88
+ const isObject = ( node ) => typeof node == 'object' ;
89
+ const isTextNode = ( node ) => node . nodeType == 3 ;
90
+ const replace = ( el , dom , parent ) => ( parent && parent . replaceChild ( el , dom ) && el ) ;
91
+ const mount = ( el , parent ) => parent ? parent . appendChild ( el ) : el ;
92
+ const isClassComponent = ( node ) => Component . isPrototypeOf ( node . type ) ;
93
+ const runHook = ( instance , hook , ...args ) => isFunction ( instance && instance [ hook ] ) ? instance [ hook ] ( ...args ) : true ;
94
+ const delegateEvent = ( dom , event , handler ) => {
95
+ event = event . slice ( 2 ) . toLowerCase ( ) ;
96
+ dom . _evnt = dom . _evnt || { } ;
97
+ dom . removeEventListener ( event , dom . _evnt [ event ] ) ;
98
+ dom . addEventListener ( event , dom . _evnt [ event ] = handler ) ;
99
+ }
100
+ const combineChildrenWithProps = ( { props, children } ) => Object . assign ( { } , props , { children } ) ;
0 commit comments