1
+ <!DOCTYPE html>
2
+ < html lang ="en ">
3
+
4
+ < head >
5
+ < meta charset ="UTF-8 ">
6
+ < title > Title</ title >
7
+ </ head >
8
+
9
+ < body >
10
+ < div id ="app "> </ div >
11
+ < script >
12
+ // propsKey 属性是否应该作为DOM Property被设置。
13
+ // true 表示可以使用DOM Property设置,el[propsKey] = xx, false表示只可以用setAttribute设置
14
+ function shouldSetAsProps ( el , propsKey , value ) {
15
+ // 所有表单元素都具有form属性表示
16
+ if ( propsKey === 'form' && el . tagName . toLowerCase ( ) === 'input' )
17
+ return false
18
+ return propsKey in el
19
+ }
20
+
21
+ // 渲染器
22
+ function createRenderer ( options = { } ) {
23
+ const {
24
+ createElement,
25
+ setElementText,
26
+ insert,
27
+ patchProps
28
+ } = options
29
+
30
+ // 挂载函数
31
+ function mountElement ( vnode , container ) {
32
+ // 通过vnode的el属性获得真实DOM
33
+ const el = vnode . el = createElement ( vnode . type )
34
+ // children处理
35
+ if ( typeof vnode . children === 'string' ) {
36
+ // 如果节点值是string,设置文本
37
+ setElementText ( el , vnode . children )
38
+ } else if ( Array . isArray ( vnode . children ) ) {
39
+ // children是一个数组,遍历这个数组,数组每个元素都是一个vnode
40
+ // 调用patch将他们挂载上去. oldVNode=null, container = el
41
+ vnode . children . forEach ( child => {
42
+ patch ( null , child , el )
43
+ } )
44
+ }
45
+ // 设置vnode属性
46
+ if ( vnode . props ) {
47
+ for ( const propsKey in vnode . props ) {
48
+ const value = vnode . props [ propsKey ] ;
49
+ patchProps ( el , propsKey , null , value )
50
+ }
51
+ }
52
+ // 将el挂载在container上
53
+ insert ( el , container )
54
+ }
55
+
56
+ // 更新函数
57
+ function patchElement ( oldVNode , newVNode ) {
58
+ const el = newVNode . el = oldVNode . el ,
59
+ oldProps = oldVNode . props ,
60
+ newProps = newVNode . props
61
+ // 更新Props
62
+ for ( const key in newProps ) {
63
+ if ( newProps [ key ] !== oldProps [ key ] ) {
64
+ patchProps ( el , key , oldProps [ key ] , newProps [ key ] )
65
+ }
66
+ }
67
+ for ( const key in oldProps ) {
68
+ if ( ! ( key in newProps ) ) {
69
+ patchProps ( el , key , oldProps [ key ] , null )
70
+ }
71
+ }
72
+ // 更新子节点
73
+ patchChildren ( oldVNode , newVNode , el )
74
+ }
75
+
76
+ function patchChildren ( oldVNode , newVNode , container ) {
77
+ // 判断新子节点的类型是否是文本类型
78
+ if ( typeof newVNode . children === 'string' ) {
79
+ // 旧子节点的类型只有三种: 无子节点, 文本子节点, 组合子节点
80
+ if ( Array . isArray ( oldVNode . children ) ) {
81
+ oldVNode . children . forEach ( child => unmounted ( child ) )
82
+ }
83
+ setElementText ( container , newVNode . children )
84
+ } else if ( Array . isArray ( newVNode . children ) ) { // 新子节点的类型是一组子节点
85
+ if ( Array . isArray ( oldVNode . children ) ) { // 旧子节点的类型是一组节点
86
+ // 核心Diff算法比较两个VNode子节点的区别
87
+ // 此处是简单实现,并非Diff算法
88
+ oldVNode . children . forEach ( child => unmounted ( child ) )
89
+ newVNode . children . forEach ( child => patch ( null , child , container ) )
90
+ } else {
91
+ // 此时,旧子节点的类型要么是文本类型,要么是无
92
+ // 但无论那种情况,都需要将容器清空,然后将新的一组子节点逐个挂载
93
+ setElementText ( container , '' )
94
+ newVNode . children . forEach ( child => {
95
+ patch ( null , child , container )
96
+ } )
97
+ }
98
+ } else { // newVNode.children === null或者'' 没有新子节点,
99
+ if ( Array . isArray ( oldVNode . children ) ) { // 旧子节点是一组节点 就逐个卸载
100
+ oldVNode . children . forEach ( child => unmounted ( child ) )
101
+ } else if ( typeof oldVNode . children === 'string' ) { // 旧子节点是文本节点,就清空内容
102
+ setElementText ( container , '' )
103
+ }
104
+ }
105
+ }
106
+
107
+ // 补丁函数
108
+ function patch ( oldVNode , newVNode , container ) {
109
+ // oldVNode存在,新旧vnode的type不同
110
+ if ( oldVNode && oldVNode . type !== newVNode . type ) {
111
+ unmounted ( oldVNode )
112
+ oldVNode = null
113
+ }
114
+ // 代码运行到这说明oldVNode和newVNode的type是相同的
115
+ const { type } = newVNode
116
+ // newVNode type是string, 说明是普通标签元素. 如果type是object,则描述的是组件
117
+ if ( typeof type === 'string' ) {
118
+ if ( ! oldVNode ) {
119
+ // 挂载
120
+ mountElement ( newVNode , container )
121
+ } else {
122
+ patchElement ( oldVNode , newVNode )
123
+ }
124
+ } else if ( typeof type === 'object' ) {
125
+ // 组件类型的VNode
126
+ } else if ( type === 'xxx' ) {
127
+ // 其他类型的VNode
128
+ }
129
+
130
+ }
131
+
132
+ // 卸载函数
133
+ function unmounted ( vnode ) {
134
+ const parent = vnode . el . parentNode
135
+ if ( parent ) parent . removeChild ( vnode . el )
136
+ }
137
+
138
+ // 渲染函数
139
+ function render ( vnode , container ) {
140
+ if ( vnode ) {
141
+ // 新vnode存在,将其与旧vnode一起传递给patch函数打补丁
142
+ patch ( container . _vnode , vnode , container )
143
+ } else {
144
+ if ( container . _vnode ) {
145
+ // 新vnode不存在,旧vnode存在,说明是卸载(unmount)操作
146
+ // 清空DOM
147
+ unmounted ( container . _vnode )
148
+ }
149
+ }
150
+ container . _vnode = vnode // vnode改成旧的
151
+ }
152
+
153
+ // 同构渲染
154
+ function hydrate ( vnode , container ) {
155
+
156
+ }
157
+
158
+ return {
159
+ render,
160
+ hydrate
161
+ }
162
+ }
163
+
164
+ // normalizeClass里处理Object.
165
+ function handleObject ( set , obj ) {
166
+ for ( const key in obj ) {
167
+ if ( obj [ key ] ) set . add ( key ) // 如果对象的值为true, set里把键(className)加进去
168
+ }
169
+ }
170
+
171
+ function normalizeClass ( classValue ) {
172
+ // 字符串
173
+ if ( typeof classValue === 'string' ) return classValue
174
+ let resultClassSet = new Set ( )
175
+ // 数组和对象情况
176
+ if ( Array . isArray ( classValue ) ) {
177
+ for ( const value of classValue ) {
178
+ if ( typeof value === 'string' ) resultClassSet . add ( value )
179
+ else {
180
+ // 对象
181
+ handleObject ( resultClassSet , value )
182
+ }
183
+ }
184
+ } else {
185
+ // 对象
186
+ handleObject ( resultClassSet , classValue )
187
+ }
188
+ return Array . from ( resultClassSet ) . join ( ' ' ) . trim ( )
189
+ }
190
+
191
+ const vnode = {
192
+ type : 'div' ,
193
+ props : {
194
+ id : 'foo' ,
195
+ class : normalizeClass ( [
196
+ 'ds' ,
197
+ {
198
+ test1 : true ,
199
+ test2 : false ,
200
+ test3 : true
201
+ }
202
+ ] ) ,
203
+ onClick : [
204
+ ( ) => {
205
+ console . log ( 1 )
206
+ } ,
207
+ ( ) => {
208
+ console . log ( 2 )
209
+ }
210
+ ]
211
+ } ,
212
+ children : [
213
+ {
214
+ type : 'h1' ,
215
+ children : 'hello h1'
216
+ }
217
+ ]
218
+ }
219
+ const renderer = createRenderer ( {
220
+ createElement ( tag ) {
221
+ return document . createElement ( tag )
222
+ } ,
223
+ setElementText ( el , text ) {
224
+ el . textContent = text
225
+ } ,
226
+ insert ( el , parent , anchor = null ) {
227
+ parent . insertBefore ( el , anchor )
228
+ } ,
229
+ patchProps ( el , propsKey , previousValue , nextValue ) {
230
+ if ( / ^ o n / . test ( propsKey ) ) { // on开头的属性是事件
231
+ const invokers = el . _vei || ( el . _vei = { } )
232
+ // 根据事件名获取invoker
233
+ let invoker = invokers [ propsKey ]
234
+ const eventName = propsKey . slice ( 2 ) . toLowerCase ( )
235
+ if ( nextValue ) {
236
+ if ( ! invoker ) { // invoker不存在
237
+ invoker = el . _vei [ propsKey ] = ( event ) => { // 伪造的事件处理函数放到el._vei[propsKey]中
238
+ if ( event . timestamp < invoker . attached ) return ; // 事件发生的时间 早于 事件处理函数绑定的时间,则不执行事件处理函数
239
+ if ( Array . isArray ( invoker . value ) ) { // invoker.value 是一个数组
240
+ invoker . value . forEach ( fn => fn ( event ) )
241
+ } else {
242
+ invoker . value ( event ) // 当伪造事件处理函数invoker执行的时候,会执行内部真正的事件处理函数invoker.value,传递event进去
243
+ }
244
+ }
245
+ invoker . value = nextValue // 真正的事件处理函数赋值给invoker.value
246
+ invokers . attached = performance . now ( ) // 事件绑定时间
247
+ el . addEventListener ( eventName , invoker )
248
+ } else {
249
+ // invoker存在,nextValue存在,那就是更新。把伪造事件处理函数invoker的value更新
250
+ invoker . value = nextValue
251
+ }
252
+ } else if ( invoker ) { // nextValue不存在,而invoker存在,说明要把原来的事件移除绑定
253
+ el . removeEventListener ( eventName , invoker )
254
+ }
255
+ }
256
+ if ( propsKey === ' class' ) {
257
+ el . className = nextValue
258
+ } else if ( shouldSetAsProps ( el , propsKey , nextValue ) ) { // HTML Attribute 跟 DOM Property有对应
259
+ const type = typeof el [ propsKey ]
260
+
261
+ // 如果type 是布尔类型的值而且value是空字符串,说明应该设置为true,而不是false
262
+ if ( type === 'boolean' && nextValue === '' ) el [ propsKey ] = true // 矫正设置DOM Property
263
+ else el [ propsKey ] = nextValue // 设置DOM Property
264
+
265
+ } else { // HTML Attribute 和 DOM Property 没对应,比如class 和 className
266
+ el . setAttribute ( propsKey , vnode . props [ propsKey ] )
267
+ }
268
+ }
269
+ } )
270
+ renderer . render ( vnode , document . getElementById ( 'app' ) )
271
+
272
+ </ script >
273
+ </ body >
274
+
275
+ </ html >
0 commit comments