' 这种情况呢? 那inst就不能复用了, 类比一下 DOM 里的 div --> span
57 | 。 把render 第四个参数 old ---> olddomOrComp , 通过这个参数来判断 dom 或者inst 是否可以复用:
58 | ```jsx harmony
59 | //inst 是否可以复用
60 | function render (vnode, parent, comp, olddomOrComp) {
61 | ...
62 | } else if(typeof vnode.nodeName === "string") {
63 | if(!olddomOrComp || olddomOrComp.nodeName !== vnode.nodeName.toUpperCase()) { // <--- dom 可以复用
64 | createNewDom(vnode, parent, comp, olddomOrComp, myIndex)
65 | }
66 | ...
67 | } else if (typeof vnode.nodeName == "function") {
68 | let func = vnode.nodeName
69 | let inst
70 | if(olddomOrComp && olddomOrComp instanceof func) { // <--- inst 可以复用
71 | inst = olddomOrComp
72 | olddomOrComp.props = vnode.props
73 | }
74 | ....
75 |
76 | render(innerVnode, parent, inst, inst.__rendered)
77 | ```
78 | 这里 在最后的 render(innerVnode, parent, inst, olddom) 被改为了: render(innerVnode, parent, inst, inst.__rendered)。 这样是符合 olddomOrComp定义的。
79 | 但是 olddom 其实是有2个作用的
80 | 1. 判断dom是否可以复用
81 | 2. parent.replaceChild(dom, olddom), olddom确定了新的dom的位置
82 | 而 olddomOrComp 是做不到第二点。 即使: parent.replaceChild(dom, getDOM(olddomOrComp)) 也是不行的。 原因是:
83 | 假如初始 CompA --> setState后 CompA --> , 那么inst 不可以复用, inst.__rendered 是undefined, 就从replaceChild变成了appendChild
84 |
85 | 怎么解决呢? 引入第5个参数 myIndex: dom的位置问题都交给这个变量。 olddomOrComp只负责决定 复用的问题
86 |
87 | so, 加入myIndex的代码如下:
88 | ```javascript 1.7
89 | /**
90 | * 替换新的Dom, 如果没有在最后插入
91 | * @param parent
92 | * @param newDom
93 | * @param myIndex
94 | */
95 | function setNewDom(parent, newDom, myIndex) {
96 | const old = parent.childNodes[myIndex]
97 | if (old) {
98 | parent.replaceChild(newDom, old)
99 | } else {
100 | parent.appendChild(newDom)
101 | }
102 | }
103 |
104 | function render(vnode, parent, comp, olddomOrComp, myIndex) {
105 | let dom
106 | if(typeof vnode === "string" || typeof vnode === "number" ) {
107 | ...
108 | } else {
109 |
110 | dom = document.createTextNode(vnode)
111 | setNewDom(parent, dom, myIndex) // <--- 根据myIndex设置 dom
112 | }
113 | } else if(typeof vnode.nodeName === "string") {
114 | if(!olddomOrComp || olddomOrComp.nodeName !== vnode.nodeName.toUpperCase()) {
115 | createNewDom(vnode, parent, comp, olddomOrComp, myIndex)
116 | } else {
117 | diffDOM(vnode, parent, comp, olddomOrComp, myIndex)
118 | }
119 | } else if (typeof vnode.nodeName === "function") {
120 | ...
121 | let innerVnode = inst.render()
122 | render(innerVnode, parent, inst, inst.__rendered, myIndex) // <--- 传递 myIndex
123 | }
124 | }
125 |
126 | function createNewDom(vnode, parent, comp, olddomOrComp, myIndex) {
127 | ...
128 | setAttrs(dom, vnode.props)
129 |
130 | setNewDom(parent, dom, myIndex) // <--- 根据myIndex设置 dom
131 |
132 | for(let i = 0; i < vnode.children.length; i++) {
133 | render(vnode.children[i], dom, null, null, i) // <--- i 就是myIndex
134 | }
135 | }
136 |
137 | function diffDOM(vnode, parent, comp, olddom) {
138 | ...
139 | for(let i = 0; i < vnode.children.length; i++) {
140 | render(vnode.children[i], olddom, null, renderedArr[i], i) // <--- i 就是myIndex
141 | }
142 | ...
143 | }
144 |
145 | ```
146 |
147 | 重新考虑 Father里面调用 setState。 此时已经不会创建新实例了。
148 |
149 | 那么 假如现在对 Grandson调用setState呢? 很不幸, 我们需要创建Granssonson1, Granssonson2, Granssonson3, 调用几次, 我们就得跟着新建几次。
150 | 上面的复用方式 并没有解决这个问题, 之前 __rendered 引用链 到 dom就结束了。
151 |
把__rendered这条链 完善吧!!
152 |
153 | 首先 对__rendered 重新定义如下:
154 | 1. 当X 是组件实例的时候, __rendered 为X渲染出的 组件实例 或者 dom元素
155 | 2. 当X 是dom元素的时候, __rendered 为一个数组, 是X的子组件实例 或者 子dom元素
156 | ```
157 | Father --__rendered--> Son --__rendered--> Grandson --__rendered--> div --__rendered--> [Granssonson1, Granssonson2, Granssonson3,]
158 | ```
159 |
160 | 在dom 下创建 "直接子节点" 的时候。 需要把这个纪录到dom.__rendered 数组中。 或者说, 如果新建的一个dom元素/组件实例 是dom的 "直接子节点", 那么需要把它纪录到
161 | parent.__rendered 数组中。 那怎么判断 创建出来的是 "直接子节点" 呢? 答案是render 第3个参数 comp为null的, 很好理解, comp的意思是 "谁渲染了我"
162 | 很明显, 只有 dom下的 "直接子节点" comp才是null, 其他的情况, comp肯定不是null, 比如 Son的comp是Father, Gsss1
163 | 的comp是Grandsonson1。。。
164 |
165 | 并且当setState重新渲染的时候, 如果老的dom/inst没有被复用, 则应该用新的dom/inst 替换
166 |
167 | 1. 创建dom的时候。
168 | ```javascript 1.7
169 | function createNewDom(vnode, parent, comp, olddomOrComp, myIndex) {
170 | ...
171 | if (comp) {
172 | comp.__rendered = dom
173 | } else {
174 | parent.__rendered[myIndex] = dom
175 | }
176 | ...
177 | }
178 | ```
179 | 2. 组件实例
180 | ```javascript 1.7
181 | else if (typeof vnode.nodeName == "function") {
182 | ...
183 | if(olddomOrComp && olddomOrComp instanceof func) {
184 | inst = olddomOrComp
185 | } else {
186 | inst = new func(vnode.props)
187 |
188 | if (comp) {
189 | comp.__rendered = inst
190 | } else {
191 | parent.__rendered[myIndex] = inst
192 | }
193 | }
194 | ...
195 | }
196 | ```
197 | 3. diffDOM 的时候: a. remove多余的节点; b. render子节点的时候olddomOrComp = olddom.__rendered[i]
198 | ```javascript 1.7
199 | function diffDOM(vnode, parent, comp, olddom) {
200 | ...
201 | olddom.__rendered.slice(vnode.children.length) // <--- 移除多余 子节点
202 | .forEach(element => {
203 | olddom.removeChild(getDOM(element))
204 | })
205 |
206 | olddom.__rendered = olddom.__rendered.slice(0, vnode.children.length)
207 | for(let i = 0; i < vnode.children.length; i++) {
208 | render(vnode.children[i], olddom, null, olddom.__rendered[i], i)
209 | }
210 | olddom.__vnode = vnode
211 | }
212 | ```
213 |
214 | 所以完整的代码:
215 | ```jsx harmony
216 | function render(vnode, parent, comp, olddomOrComp, myIndex) {
217 | let dom
218 | if(typeof vnode === "string" || typeof vnode === "number" ) {
219 | if(olddomOrComp && olddomOrComp.splitText) {
220 | if(olddomOrComp.nodeValue !== vnode) {
221 | olddomOrComp.nodeValue = vnode
222 | }
223 | } else {
224 | dom = document.createTextNode(vnode)
225 | parent.__rendered[myIndex] = dom //comp 一定是null
226 |
227 | setNewDom(parent, dom, myIndex)
228 | }
229 | } else if(typeof vnode.nodeName === "string") {
230 | if(!olddomOrComp || olddomOrComp.nodeName !== vnode.nodeName.toUpperCase()) {
231 | createNewDom(vnode, parent, comp, olddomOrComp, myIndex)
232 | } else {
233 | diffDOM(vnode, parent, comp, olddomOrComp)
234 | }
235 | } else if (typeof vnode.nodeName === "function") {
236 | let func = vnode.nodeName
237 | let inst
238 | if(olddomOrComp && olddomOrComp instanceof func) {
239 | inst = olddomOrComp
240 | inst.props = vnode.props
241 | } else {
242 | inst = new func(vnode.props)
243 |
244 | if (comp) {
245 | comp.__rendered = inst
246 | } else {
247 | parent.__rendered[myIndex] = inst
248 | }
249 | }
250 |
251 | let innerVnode = inst.render()
252 | render(innerVnode, parent, inst, inst.__rendered, myIndex)
253 | }
254 | }
255 |
256 | function createNewDom(vnode, parent, comp, olddomOrComp, myIndex) {
257 | let dom = document.createElement(vnode.nodeName)
258 |
259 | dom.__rendered = [] // 创建dom的 设置 __rendered 引用
260 | dom.__vnode = vnode
261 |
262 | if (comp) {
263 | comp.__rendered = dom
264 | } else {
265 | parent.__rendered[myIndex] = dom
266 | }
267 |
268 | setAttrs(dom, vnode.props)
269 |
270 | setNewDom(parent, dom, myIndex)
271 |
272 | for(let i = 0; i < vnode.children.length; i++) {
273 | render(vnode.children[i], dom, null, null, i)
274 | }
275 | }
276 |
277 | function diffDOM(vnode, parent, comp, olddom) {
278 | const {onlyInLeft, bothIn, onlyInRight} = diffObject(vnode.props, olddom.__vnode.props)
279 | setAttrs(olddom, onlyInLeft)
280 | removeAttrs(olddom, onlyInRight)
281 | diffAttrs(olddom, bothIn.left, bothIn.right)
282 |
283 |
284 | olddom.__rendered.slice(vnode.children.length)
285 | .forEach(element => {
286 | olddom.removeChild(getDOM(element))
287 | })
288 |
289 | const __renderedArr = olddom.__rendered.slice(0, vnode.children.length)
290 | olddom.__rendered = __renderedArr
291 | for(let i = 0; i < vnode.children.length; i++) {
292 | render(vnode.children[i], olddom, null, __renderedArr[i], i)
293 | }
294 | olddom.__vnode = vnode
295 | }
296 |
297 | class Component {
298 | constructor(props) {
299 | this.props = props
300 | }
301 |
302 | setState(state) {
303 | setTimeout(() => {
304 | this.state = state
305 |
306 |
307 | const vnode = this.render()
308 | let olddom = getDOM(this)
309 | const myIndex = getDOMIndex(olddom)
310 | render(vnode, olddom.parentNode, this, this.__rendered, myIndex)
311 | }, 0)
312 | }
313 | }
314 | function getDOMIndex(dom) {
315 | const cn = dom.parentNode.childNodes
316 | for(let i= 0; i < cn.length; i++) {
317 | if (cn[i] === dom ) {
318 | return i
319 | }
320 | }
321 | }
322 | ```
323 |
324 | 
325 |
326 | 现在 __rendered链 完善了, setState触发的渲染, 都会先去尝试复用 组件实例。 [在线演示](http://jsfiddle.net/yankang/k8ypszLd/)
327 |
328 | ### 生命周期
329 | 前面讨论的__rendered 和生命周期有 什么关系呢? 生命周期是组件实例的生命周期, 之前的工作起码保证了一点: constructor 只会被调用一次了吧。。。
330 | 后面讨论的生命周期 都是基于 "组件实例"的 复用才有意义。tinyreact 将实现以下的生命周期:
331 |
332 | 1. componentWillMount
333 | 2. componentDidMount
334 | 3. componentWillReceiveProps
335 | 4. shouldComponentUpdate
336 | 5. componentWillUpdate
337 | 6. componentDidUpdate
338 | 7. componentWillUnmount
339 | 他们 和 react同名函数 含义相同
340 |
341 | #### componentWillMount, componentDidMount, componentDidUpdate
342 | 这三个生命周期 是如此之简单: componentWillMount紧接着 创建实例的时候调用; 渲染完成之后,如果
343 | 组件是新建的componentDidMount , 否则:componentDidUpdate
344 | ```jsx harmony
345 | else if (typeof vnode.nodeName === "function") {
346 | let func = vnode.nodeName
347 | let inst
348 | if(olddomOrComp && olddomOrComp instanceof func) {
349 | inst = olddomOrComp
350 | inst.props = vnode.props
351 | } else {
352 | inst = new func(vnode.props)
353 | inst.componentWillMount && inst.componentWillMount()
354 |
355 |
356 | if (comp) {
357 | comp.__rendered = inst
358 | } else {
359 | parent.__rendered[myIndex] = inst
360 | }
361 | }
362 |
363 | let innerVnode = inst.render()
364 | render(innerVnode, parent, inst, inst.__rendered, myIndex)
365 |
366 | if(olddomOrComp && olddomOrComp instanceof func) {
367 | inst.componentDidUpdate && inst.componentDidUpdate()
368 | } else {
369 | inst.componentDidMount && inst.componentDidMount()
370 | }
371 | }
372 | ```
373 |
374 | #### componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate
375 | 当组件 获取新的props的时候, 会调用componentWillReceiveProps, 参数为newProps, 并且在这个方法内部this.props 还是值向oldProps,
376 | 由于 props的改变 由 只能由 父组件 触发。 所以只用在 render函数里面处理就ok。不过 要在 inst.props = vnode.props 之前调用componentWillReceiveProps:
377 | ```jsx harmony
378 | else if (typeof vnode.nodeName === "function") {
379 | let func = vnode.nodeName
380 | let inst
381 | if(olddomOrComp && olddomOrComp instanceof func) {
382 | inst = olddomOrComp
383 | inst.componentWillReceiveProps && inst.componentWillReceiveProps(vnode.props) // <-- 在 inst.props = vnode.props 之前调用
384 |
385 | inst.props = vnode.props
386 | } else {
387 | ...
388 | }
389 | }
390 | ```
391 |
392 | 当 组件的 props或者state发生改变的时候,组件一定会渲染吗?shouldComponentUpdate说了算!! 如果组件没有shouldComponentUpdate这个方法, 默认是渲染的。
393 | 否则是基于 shouldComponentUpdate的返回值。 这个方法接受两个参数 newProps, newState 。
394 | 另外由于 props和 state(setState) 改变都会引起 shouldComponentUpdate调用, 所以:
395 | ```jsx harmony
396 | function render(vnode, parent, comp, olddomOrComp, myIndex) {
397 | ...
398 | else if (typeof vnode.nodeName === "function") {
399 | let func = vnode.nodeName
400 | let inst
401 | if(olddomOrComp && olddomOrComp instanceof func) {
402 | inst = olddomOrComp
403 | inst.componentWillReceiveProps && inst.componentWillReceiveProps(vnode.props) // <-- 在 inst.props = vnode.props 之前调用
404 |
405 | let shoudUpdate
406 | if(inst.shouldComponentUpdate) {
407 | shoudUpdate = inst.shouldComponentUpdate(vnode.props, olddomOrComp.state) // <-- 在 inst.props = vnode.props 之前调用
408 | } else {
409 | shoudUpdate = true
410 | }
411 |
412 | inst.props = vnode.props
413 | if (!shoudUpdate) { // <-- 在 inst.props = vnode.props 之后
414 | return // do nothing just return
415 | }
416 |
417 |
418 | } else {
419 | ...
420 | }
421 | }
422 | ...
423 | }
424 |
425 |
426 | setState(state) {
427 | setTimeout(() => {
428 | let shoudUpdate
429 | if(this.shouldComponentUpdate) {
430 | shoudUpdate = this.shouldComponentUpdate(this.props, state)
431 | } else {
432 | shoudUpdate = true
433 | }
434 | this.state = state
435 | if (!shoudUpdate) { // <-- 在 this.state = state 之后
436 | return // do nothing just return
437 | }
438 |
439 | const vnode = this.render()
440 | let olddom = getDOM(this)
441 | const myIndex = getDOMIndex(olddom)
442 | render(vnode, olddom.parentNode, this, this.__rendered, myIndex)
443 | this.componentDidUpdate && this.componentDidUpdate() // <-- 需要调用下: componentDidUpdate
444 | }, 0)
445 | }
446 | ```
447 | 当 shoudUpdate 为false的时候呢, 直接return 就ok了, 但是shoudUpdate 为false 只是表明 不渲染, 但是在 return之前, newProps和newState一定要设置到组件实例上。
448 |
**注** setState render之后 也是需要调用: componentDidUpdate
449 |
450 | 当 shoudUpdate == true 的时候。 会调用: componentWillUpdate, 参数为newProps和newState。 这个函数调用之后,就会把nextProps和nextState分别设置到this.props和this.state中。
451 | ```jsx harmony
452 | function render(vnode, parent, comp, olddomOrComp, myIndex) {
453 | ...
454 | else if (typeof vnode.nodeName === "function") {
455 | ...
456 | let shoudUpdate
457 | if(inst.shouldComponentUpdate) {
458 | shoudUpdate = inst.shouldComponentUpdate(vnode.props, olddomOrComp.state) // <-- 在 inst.props = vnode.props 之前调用
459 | } else {
460 | shoudUpdate = true
461 | }
462 | shoudUpdate && inst.componentWillUpdate && inst.componentWillUpdate(vnode.props, olddomOrComp.state) // <-- 在 inst.props = vnode.props 之前调用
463 | inst.props = vnode.props
464 | if (!shoudUpdate) { // <-- 在 inst.props = vnode.props 之后
465 | return // do nothing just return
466 | }
467 |
468 | ...
469 | }
470 |
471 |
472 | setState(state) {
473 | setTimeout(() => {
474 | ...
475 | shoudUpdate && this.componentWillUpdate && this.componentWillUpdate(this.props, state) // <-- 在 this.state = state 之前调用
476 | this.state = state
477 | if (!shoudUpdate) { // <-- 在 this.state = state 之后
478 | return // do nothing just return
479 | }
480 | ...
481 | }
482 | ```
483 |
484 | #### componentWillUnmount
485 | 当组件要被销毁的时候, 调用组件的componentWillUnmount。 inst没有被复用的时候, 要销毁。 dom没有被复用的时候, 也要销毁, 而且是树形结构
486 | 的递归操作。 有点像 render的递归, 直接看代码:
487 | ```jsx harmony
488 | function recoveryComp(comp) {
489 | if (comp instanceof Component) { // <--- component
490 | comp.componentWillUnmount && comp.componentWillUnmount()
491 | recoveryComp(comp.__rendered)
492 | } else if (comp.__rendered instanceof Array) { // <--- dom like div/span
493 | comp.__rendered.forEach(element => {
494 | recoveryComp(element)
495 | })
496 | } else { // <--- TextNode
497 | // do nothing
498 | }
499 | }
500 | ```
501 | recoveryComp 是这样的一个 递归函数:
502 | * 当domOrComp 为组件实例的时候, 首先调用:componentWillUnmount, 然后 recoveryDomOrComp(inst.__rendered) 。 这里的先后顺序关系很重要
503 | * 当domOrComp 为DOM节点 (非文本 TextNode), 遍历 recoveryDomOrComp(子节点)
504 | * 当domOrComp 为TextNode,nothing...
505 | 与render一样, 由于组件 最终一定会render html的标签。 所以这个递归一定是能够正常返回的。
506 |
哪些地方需要调用recoveryComp ?
507 | 1. 所有olddomOrComp 没有被复用的地方。 因为一旦olddomOrComp 不被复用, 一定有一个新的取得它, 它就要被销毁
508 | 2. 多余的 子节点。 div 起初有3个子节点, setState之后变成了2个。 多出来的要被销毁
509 | ```jsx harmony
510 | function diffDOM(vnode, parent, comp, olddom) {
511 | const {onlyInLeft, bothIn, onlyInRight} = diffObject(vnode.props, olddom.__vnode.props)
512 | setAttrs(olddom, onlyInLeft)
513 | removeAttrs(olddom, onlyInRight)
514 | diffAttrs(olddom, bothIn.left, bothIn.right)
515 |
516 |
517 | const willRemoveArr = olddom.__rendered.slice(vnode.children.length)
518 | const renderedArr = olddom.__rendered.slice(0, vnode.children.length)
519 | olddom.__rendered = renderedArr
520 | for(let i = 0; i < vnode.children.length; i++) {
521 | render(vnode.children[i], olddom, null, renderedArr[i], i)
522 | }
523 |
524 | willRemoveArr.forEach(element => {
525 | recoveryComp(element)
526 | olddom.removeChild(getDOM(element))
527 | })
528 |
529 | olddom.__vnode = vnode
530 | }
531 | ```
532 |
533 | 到这里, tinyreact 就有 生命周期了
534 |
535 | 之前的代码 由于会用到 dom.__rendered。 所以:
536 | ```jsx harmony
537 | const root = document.getElementById("root")
538 | root.__rendered = []
539 | render(, root)
540 | ```
541 | 为了不要在 调用render之前 设置:__rendered 做个小的改动 :
542 | ```jsx harmony
543 | /**
544 | * 渲染vnode成实际的dom
545 | * @param vnode 虚拟dom表示
546 | * @param parent 实际渲染出来的dom,挂载的父元素
547 | */
548 | export default function render(vnode, parent) {
549 | parent.__rendered =[] //<--- 这里设置 __rendered
550 | renderInner(vnode, parent, null, null, 0)
551 | }
552 |
553 | function renderInner(vnode, parent, comp, olddomOrComp, myIndex) {
554 | ...
555 | }
556 | ```
557 |
558 | ### 其他
559 | tinyreact 未实现功能:
560 | 1. context
561 | 2. 事件代理
562 | 3. 多吃调用setState, 只render一次
563 | 4. react 顶层Api
564 | 5. 。。。
565 |
566 | tinyreat 有些地方参考了[preact](https://github.com/developit/preact)
567 |
568 | npm包:
569 | ```
570 | npm install tinyreact --save
571 | ```
572 | [所有代码托管在git](https://github.com/ykforerlang/tinyreact) example 目录下有blog中的例子
573 |
574 | [经典的TodoList](https://ykforerlang.github.io/todo/index.html)。 项目 [代码](https://github.com/ykforerlang/todo)
575 |
576 |
--------------------------------------------------------------------------------
/blog/从0实现一个tiny react(二).md:
--------------------------------------------------------------------------------
1 | # 从0实现一个tiny react(二)
2 | ui = f(d)! 这是react考虑ui的方式,开发者可以把重心放到d 数据上面来了。 从开发者的角度来讲 d一旦改变,react将会把ui重新渲染,使其再次满足
3 | ui = f(d), 开发者没有任何dom操作, 交给react就好!!
4 |
5 | 怎么重新渲染呢? (一)文 中我们实现了一种方式, state改变的时候,用新的dom树替换一下老的dom树, 这是完全可行的。
6 | 考虑一下这个例子 [在线演示地址](http://jsfiddle.net/yankang/z0e9ngwL/):
7 | ```javascript 1.7
8 | class AppWithNoVDOM extends Component {
9 | constructor(props) {
10 | super(props)
11 | }
12 |
13 | testApp3() {
14 | let result = []
15 | for(let i = 0; i < 10000 ; i++) {
16 | result.push({i}
)
30 | }
31 | return result
32 | }
33 |
34 | render() {
35 | return (
36 |
43 | )
44 | }
45 | }
46 |
47 | const startTime = new Date().getTime()
48 | render(, document.getElementById("root"))
49 | console.log("duration:", new Date().getTime() - startTime)
50 |
51 |
52 | ...
53 | setState(state) {
54 | setTimeout(() => {
55 | this.state = state
56 | const vnode = this.render()
57 | let olddom = getDOM(this)
58 | const startTime = new Date().getTime()
59 | render(vnode, olddom.parentNode, this, olddom)
60 | console.log("duration:", new Date().getTime() - startTime)
61 | }, 0)
62 | }
63 | ...
64 | ```
65 | 我们在 render, setState 设置下时间点。 在10000万个div的情况下, 第一次render和setState触发的render 耗时大概在180ms (可能跟机器配置有关)
66 | 当点击的时候, 由于调用`this.setState({})`, 页面将会重新渲染, 再次建立10000万个div, 但是实际上这里的DOM一点也没改。
67 | 应用越复杂, 无用功越多,卡顿越明显
68 |
69 | 为了解决这个问题, react提出了virtual-dom的概念:vnode(纯js对象) '代表' dom, 在渲染之前, 先比较出oldvnode和newvode的 区别。 然后增量的
70 | 更新dom。 virtual-dom 使得ui=f(d) 得以在实际项目上使用。
71 | (注意: virtual-dom 并不会加快应用速度, 只是让应用在不直接操作dom的情况下,通过暴力的比较,增量更新 让应用没有那么慢)
72 |
73 | 如何增量更新呢?
74 | ### 复用DOM
75 | 回想一下, 在 [(一)](https://segmentfault.com/a/1190000010822571) render函数 里面对于每一个判定为 dom类型的VDOM, 是直接创建一个新的DOM:
76 | ```javascript 1.7
77 | ...
78 | else if(typeof vnode.nodeName == "string") {
79 | dom = document.createElement(vnode.nodeName)
80 | ...
81 | }
82 | ...
83 | ```
84 | 一定要创建一个 新的DOM 结构吗?
85 | 考虑这种情况:假如一个组件, 初次渲染为 renderBefore, 调用setState再次渲染为 renderAfter 调用setState再再次渲染为 renderAfterAfter。 VNODE如下
86 | ```javascript 1.7
87 | const renderBefore = {
88 | tagName: 'div',
89 | props: {
90 | width: '20px',
91 | className: 'xx'
92 | },
93 | children:[vnode1, vnode2, vnode3]
94 | }
95 | const renderAfter = {
96 | tagName: 'div',
97 | props: {
98 | width: '30px',
99 | title: 'yy'
100 | },
101 | children:[vnode1, vnode2]
102 | }
103 | const renderAfterAfter = {
104 | tagName: 'span',
105 | props: {
106 | className: 'xx'
107 | },
108 | children:[vnode1, vnode2, vnode3]
109 | }
110 | ```
111 | renderBefore 和renderAfter 都是div, 只不过props和children有部分区别,那我们是不是可以通过修改DOM属性, 修改DOM子节点,把 rederBefore 变化为renderAfter呢?, 这样就避开了DOM创建。 而 renderAfter和renderAfterAfter
112 | 属于不同的DOM类型, 浏览器还没提供修改DOM类型的Api,是无法复用的, 是一定要创建新的DOM的。
113 |
114 | 原则如下:
115 | * 不同元素类型是无法复用的, span 是无法变成 div的。
116 | * 对于相同元素:
117 | * 更新属性,
118 | * 复用子节点。
119 |
120 | 所以,现在的代码可能是这样的:
121 | ```javascript 1.7
122 | ...
123 | else if(typeof vnode.nodeName == "string") {
124 | if(!olddom || olddom.nodeName != vnode.nodeName.toUpperCase()) {
125 | createNewDom(vnode, parent, comp, olddom)
126 | } else {
127 | diffDOM(vnode, parent, comp, olddom) // 包括 更新属性, 子节点复用
128 | }
129 | }
130 | ...
131 | ```
132 | #### 更新属性
133 | 对于 renderBefore => renderAfter 。 属性部分需要做3件事情。
134 | 1. renderBefore 和 renderAfter 的属性交集 如果值不同, 更新值 updateAttr
135 | 2. renderBefore 和 renderAfter 的属性差集 置空 removeAttr
136 | 3. renderAfter 和 renderBefore 的属性差集 设置新值 setAttr
137 | ```javascript 1.7
138 | const {onlyInLeft, bothIn, onlyInRight} = diffObject(newProps, oldProps)
139 | setAttrs(olddom, onlyInLeft)
140 | removeAttrs(olddom, onlyInRight)
141 | diffAttrs(olddom, bothIn.left, bothIn.right)
142 |
143 | function diffObject(leftProps, rightProps) {
144 | const onlyInLeft = {}
145 | const bothLeft = {}
146 | const bothRight = {}
147 | const onlyInRight = {}
148 |
149 | for(let key in leftProps) {
150 | if(rightProps[key] === undefined) {
151 | onlyInLeft[key] = leftProps[key]
152 | } else {
153 | bothLeft[key] = leftProps[key]
154 | bothRight[key] = rightProps[key]
155 | }
156 | }
157 |
158 | for(let key in rightProps) {
159 | if(leftProps[key] === undefined) {
160 | onlyInRight[key] = rightProps[key]
161 | }
162 | }
163 |
164 | return {
165 | onlyInRight,
166 | onlyInLeft,
167 | bothIn: {
168 | left: bothLeft,
169 | right: bothRight
170 | }
171 | }
172 | }
173 |
174 | function setAttrs(dom, props) {
175 | const allKeys = Object.keys(props)
176 | allKeys.forEach(k => {
177 | const v = props[k]
178 |
179 | if(k == "className") {
180 | dom.setAttribute("class", v)
181 | return
182 | }
183 |
184 | if(k == "style") {
185 | if(typeof v == "string") {
186 | dom.style.cssText = v //IE
187 | }
188 |
189 | if(typeof v == "object") {
190 | for (let i in v) {
191 | dom.style[i] = v[i]
192 | }
193 | }
194 | return
195 |
196 | }
197 |
198 | if(k[0] == "o" && k[1] == "n") {
199 | const capture = (k.indexOf("Capture") != -1)
200 | dom.addEventListener(k.substring(2).toLowerCase(), v, capture)
201 | return
202 | }
203 |
204 | dom.setAttribute(k, v)
205 | })
206 | }
207 |
208 | function removeAttrs(dom, props) {
209 | for(let k in props) {
210 | if(k == "className") {
211 | dom.removeAttribute("class")
212 | continue
213 | }
214 |
215 | if(k == "style") {
216 | dom.style.cssText = "" //IE
217 | continue
218 | }
219 |
220 |
221 | if(k[0] == "o" && k[1] == "n") {
222 | const capture = (k.indexOf("Capture") != -1)
223 | const v = props[k]
224 | dom.removeEventListener(k.substring(2).toLowerCase(), v, capture)
225 | continue
226 | }
227 |
228 | dom.removeAttribute(k)
229 | }
230 | }
231 |
232 | /**
233 | * 调用者保证newProps 与 oldProps 的keys是相同的
234 | * @param dom
235 | * @param newProps
236 | * @param oldProps
237 | */
238 | function diffAttrs(dom, newProps, oldProps) {
239 | for(let k in newProps) {
240 | let v = newProps[k]
241 | let ov = oldProps[k]
242 | if(v === ov) continue
243 |
244 | if(k == "className") {
245 | dom.setAttribute("class", v)
246 | continue
247 | }
248 |
249 | if(k == "style") {
250 | if(typeof v == "string") {
251 | dom.style.cssText = v
252 | } else if( typeof v == "object" && typeof ov == "object") {
253 | for(let vk in v) {
254 | if(v[vk] !== ov[vk]) {
255 | dom.style[vk] = v[vk]
256 | }
257 | }
258 |
259 | for(let ovk in ov) {
260 | if(v[ovk] === undefined){
261 | dom.style[ovk] = ""
262 | }
263 | }
264 | } else { //typeof v == "object" && typeof ov == "string"
265 | dom.style = {}
266 | for(let vk in v) {
267 | dom.style[vk] = v[vk]
268 | }
269 | }
270 | continue
271 | }
272 |
273 | if(k[0] == "o" && k[1] == "n") {
274 | const capture = (k.indexOf("Capture") != -1)
275 | let eventKey = k.substring(2).toLowerCase()
276 | dom.removeEventListener(eventKey, ov, capture)
277 | dom.addEventListener(eventKey, v, capture)
278 | continue
279 | }
280 |
281 | dom.setAttribute(k, v)
282 | }
283 | }
284 | ```
285 | '新'的dom结构 属性和 renderAfter对应了。
286 | 但是 children部分 还是之前的
287 | #### 操作子节点
288 | 之前 操作子节点的代码:
289 | ```javascript 1.7
290 | for(let i = 0; i < vnode.children.length; i++) {
291 | render(vnode.children[i], dom, null, null)
292 | }
293 | ```
294 | render 的第3个参数comp '谁渲染了我', 第4个参数olddom '之前的旧dom元素'。现在复用旧的dom, 所以第4个参数可能是有值的 代码如下:
295 | ```javascript 1.7
296 | let olddomChild = olddom.firstChild
297 | for(let i = 0; i < vnode.children.length; i++) {
298 | render(vnode.children[i], olddom, null, olddomChild)
299 | olddomChild = olddomChild && olddomChild.nextSibling
300 | }
301 |
302 | //删除多余的子节点
303 | while (olddomChild) {
304 | let next = olddomChild.nextSibling
305 | olddom.removeChild(olddomChild)
306 | olddomChild = next
307 | }
308 | ```
309 |
310 | 综上所述 完整的diffDOM 如下:
311 | ```javascript 1.7
312 | function diffDOM(vnode, parent, comp, olddom) {
313 | const {onlyInLeft, bothIn, onlyInRight} = diffObject(vnode.props, olddom.__vnode.props)
314 | setAttrs(olddom, onlyInLeft)
315 | removeAttrs(olddom, onlyInRight)
316 | diffAttrs(olddom, bothIn.left, bothIn.right)
317 |
318 |
319 | let olddomChild = olddom.firstChild
320 | for(let i = 0; i < vnode.children.length; i++) {
321 | render(vnode.children[i], olddom, null, olddomChild)
322 | olddomChild = olddomChild && olddomChild.nextSibling
323 | }
324 |
325 | while (olddomChild) { //删除多余的子节点
326 | let next = olddomChild.nextSibling
327 | olddom.removeChild(olddomChild)
328 | olddomChild = next
329 | }
330 | olddom.__vnode = vnode
331 | }
332 | ```
333 | 由于需要在diffDOM的时候 从olddom获取 oldVNODE(即 diffObject(vnode.props, olddom.__vnode.props))。 所以:
334 | ```javascript 1.7
335 | // 在创建的时候
336 | ...
337 | let dom = document.createElement(vnode.nodeName)
338 | dom.__vnode = vnode
339 | ...
340 |
341 |
342 | // diffDOM
343 | ...
344 | const {onlyInLeft, bothIn, onlyInRight} = diffObject(vnode.props, olddom.__vnode.props)
345 | ...
346 | olddom.__vnode = vnode // 更新完之后, 需要把__vnode的指向 更新
347 | ...
348 | ```
349 | 另外 对于 TextNode的复用:
350 | ```javascript 1.7
351 | ...
352 | if(typeof vnode == "string" || typeof vnode == "number") {
353 | if(olddom && olddom.splitText) {
354 | if(olddom.nodeValue !== vnode) {
355 | olddom.nodeValue = vnode
356 | }
357 | } else {
358 | dom = document.createTextNode(vnode)
359 | if(olddom) {
360 | parent.replaceChild(dom, olddom)
361 | } else {
362 | parent.appendChild(dom)
363 | }
364 | }
365 | }
366 | ...
367 | ```
368 | 重新 跑一下开头 的例子 [新的复用DOM演示](http://jsfiddle.net/yankang/cyc4ss5c/) setState后渲染时间变成了 20ms 左右。 从 180ms 到20ms 差不多快有一个数量级的差距了。
369 | 到底快了多少,取决于前后结构的相似程度, 如果前后结构基本相同,diff是有意义的减少了DOM操作。
370 |
371 | #### 复用子节点 - **key**
372 | ```javascript 1.7
373 | 初始渲染
374 | ...
375 | render() {
376 | return (
377 |
378 |
379 |
380 |
381 |
382 | )
383 | }
384 | ...
385 |
386 | setState再次渲染
387 | ...
388 | render() {
389 | return (
390 |
391 | hi
392 |
393 |
394 |
395 |
396 | )
397 | }
398 | ...
399 | ```
400 | 我们之前的子节点复用顺序就是按照DOM顺序, 显然这里如果这样处理的话, 可能导致组件都复用不了。 针对这个问题, React是通过给每一个子组件提供一个 "key"属性来解决的
401 | 对于拥有 同样key的节点, 认为结构相同。 所以问题变成了:
402 | ```
403 | f([{key: 'wca'}, {key: 'wcb}, {key: 'wcc}]) = [{key:'spanhi'}, {key: 'wca'}, {key: 'wcb}, {key: 'wcc}]
404 | ```
405 | 函数f 通过删除, 插入操作,把olddom的children顺序, 改为和 newProps里面的children一样 (按照key值一样)。类似与 [字符串距离](https://en.wikipedia.org/wiki/Edit_distance),
406 | 对于这个问题, 我将会另开一篇文章
407 |
408 | ### 总结
409 | 通过 diff 比较渲染前后 DOM的差别来复用实际的, 我们的性能得到了提高。现在 render方法的描述:
410 | render 方法是根据的vnode, 渲染到实际的dom,如果存在olddom会先尝试复用的 一个递归方法 (由于组件 最终一定会render html的标签。 所以这个递归一定是能够正常返回的)
411 | * vnode是字符串, 如果存在olddom, 且可以复用, 复用之。否则创建textNode节点
412 | * 当vnode.nodeName是 字符串的时候, 如果存在olddom, 且可以复用, 复用之。否则创建dom节点, 根据props设置节点属性, 遍历render children
413 | * 当vnode.nodeName是 function的时候, 获取render方法的返回值 vnode', 执行render(vnode')
414 |
415 | [代码git地址](https://github.com/ykforerlang/tinyreact)
416 |
417 | ### 相关文章
418 | * [从0实现一个tiny react(一)](https://segmentfault.com/a/1190000010822571)
419 | * [从0实现一个tiny react(二)](https://segmentfault.com/a/1190000011052656)
420 | * [从0实现一个tiny react(三)生命周期](https://segmentfault.com/a/1190000011156505)
421 | * [从0开始实现 react-router](https://segmentfault.com/a/1190000012696920)
422 | * [从0实现一个tinyredux](https://segmentfault.com/a/1190000011304634)
423 | * [从0实现一个tiny react-redux](https://segmentfault.com/a/1190000011633971)
424 | * [为什么我们需要reselect](https://segmentfault.com/a/1190000011936772)
425 |
426 |
427 |
428 |
429 |
--------------------------------------------------------------------------------
/blog/从0实现一个tiny react(四).md:
--------------------------------------------------------------------------------
1 | # 从0实现一个tiny react(四)ref context setState
2 |
--------------------------------------------------------------------------------
/example/helloworld/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015"
4 | ],
5 | "plugins": [
6 | ["transform-react-jsx", {
7 | "pragma": "createElement"// default pragma is React.createElement
8 | }]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/example/helloworld/build/index.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports;
11 | /******/ }
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.l = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // define getter function for harmony exports
37 | /******/ __webpack_require__.d = function(exports, name, getter) {
38 | /******/ if(!__webpack_require__.o(exports, name)) {
39 | /******/ Object.defineProperty(exports, name, {
40 | /******/ configurable: false,
41 | /******/ enumerable: true,
42 | /******/ get: getter
43 | /******/ });
44 | /******/ }
45 | /******/ };
46 | /******/
47 | /******/ // getDefaultExport function for compatibility with non-harmony modules
48 | /******/ __webpack_require__.n = function(module) {
49 | /******/ var getter = module && module.__esModule ?
50 | /******/ function getDefault() { return module['default']; } :
51 | /******/ function getModuleExports() { return module; };
52 | /******/ __webpack_require__.d(getter, 'a', getter);
53 | /******/ return getter;
54 | /******/ };
55 | /******/
56 | /******/ // Object.prototype.hasOwnProperty.call
57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58 | /******/
59 | /******/ // __webpack_public_path__
60 | /******/ __webpack_require__.p = "";
61 | /******/
62 | /******/ // Load entry module and return exports
63 | /******/ return __webpack_require__(__webpack_require__.s = 0);
64 | /******/ })
65 | /************************************************************************/
66 | /******/ ([
67 | /* 0 */
68 | /***/ (function(module, exports, __webpack_require__) {
69 |
70 | "use strict";
71 |
72 |
73 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
74 |
75 | var _render = __webpack_require__(1);
76 |
77 | var _render2 = _interopRequireDefault(_render);
78 |
79 | var _createElement = __webpack_require__(2);
80 |
81 | var _createElement2 = _interopRequireDefault(_createElement);
82 |
83 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
84 |
85 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
86 |
87 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
88 |
89 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /**
90 | * Created by apple on 2017/8/21.
91 | */
92 |
93 | var Component = function Component() {
94 | _classCallCheck(this, Component);
95 | };
96 |
97 | var HelloWorld = function (_Component) {
98 | _inherits(HelloWorld, _Component);
99 |
100 | function HelloWorld() {
101 | _classCallCheck(this, HelloWorld);
102 |
103 | return _possibleConstructorReturn(this, (HelloWorld.__proto__ || Object.getPrototypeOf(HelloWorld)).apply(this, arguments));
104 | }
105 |
106 | _createClass(HelloWorld, [{
107 | key: 'render',
108 | value: function render() {
109 | return (0, _createElement2.default)(
110 | 'div',
111 | { style: { color: 'red' } },
112 | 'Hello World'
113 | );
114 | }
115 | }]);
116 |
117 | return HelloWorld;
118 | }(Component);
119 |
120 | (0, _render2.default)((0, _createElement2.default)(HelloWorld, null), document.getElementById("root"));
121 |
122 | /***/ }),
123 | /* 1 */
124 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
125 |
126 | "use strict";
127 | Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
128 | /* harmony export (immutable) */ __webpack_exports__["default"] = render;
129 | /**
130 | * Created by apple on 2017/8/21.
131 | */
132 | function render(vnode, parent, comp, olddom) {
133 | let dom;
134 | if (typeof vnode == "string" || typeof vnode == "number") {
135 | dom = document.createTextNode(vnode);
136 | comp && (comp.__rendered = dom);
137 | parent.appendChild(dom);
138 |
139 | if (olddom) {
140 | parent.replaceChild(dom, olddom);
141 | } else {
142 | parent.appendChild(dom);
143 | }
144 | } else if (typeof vnode.nodeName == "string") {
145 | dom = document.createElement(vnode.nodeName);
146 |
147 | comp && (comp.__rendered = dom);
148 | setAttrs(dom, vnode.props);
149 |
150 | if (olddom) {
151 | parent.replaceChild(dom, olddom);
152 | } else {
153 | parent.appendChild(dom);
154 | }
155 |
156 | for (let i = 0; i < vnode.children.length; i++) {
157 | render(vnode.children[i], dom, null, null);
158 | }
159 | } else if (typeof vnode.nodeName == "function") {
160 | let func = vnode.nodeName;
161 | let inst = new func(vnode.props);
162 |
163 | comp && (comp.__rendered = inst);
164 |
165 | let innerVnode = inst.render(inst);
166 | render(innerVnode, parent, inst, olddom);
167 | }
168 | }
169 |
170 | function setAttrs(dom, props) {
171 | const allKeys = Object.keys(props);
172 | allKeys.forEach(k => {
173 | const v = props[k];
174 |
175 | if (k == "className") {
176 | dom.setAttribute("class", v);
177 | return;
178 | }
179 |
180 | if (k == "style") {
181 | if (typeof v == "string") {
182 | dom.style.cssText = v;
183 | }
184 |
185 | if (typeof v == "object") {
186 | for (let i in v) {
187 | dom.style[i] = v[i];
188 | }
189 | }
190 | return;
191 | }
192 |
193 | if (k[0] == "o" && k[1] == "n") {
194 | const capture = k.indexOf("Capture") != -1;
195 | dom.addEventListener(k.substring(2).toLowerCase(), v, capture);
196 | return;
197 | }
198 |
199 | dom.setAttribute(k, v);
200 | });
201 | }
202 |
203 | /***/ }),
204 | /* 2 */
205 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
206 |
207 | "use strict";
208 | Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
209 | /* harmony export (immutable) */ __webpack_exports__["default"] = createElement;
210 | /**
211 | * Created by apple on 2017/7/16.
212 | */
213 |
214 | /**
215 | *
216 | * @param comp func or div/p/span/..
217 | * @param props {}
218 | * @param children
219 | */
220 | function createElement(comp, props, ...args) {
221 | return {
222 | nodeName: comp,
223 | props: props || {},
224 | children: args || []
225 | };
226 | }
227 |
228 | /***/ })
229 | /******/ ]);
--------------------------------------------------------------------------------
/example/helloworld/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/helloworld/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/8/21.
3 | */
4 |
5 | import render from '../../src/render'
6 | import createElement from '../../src/createElement'
7 |
8 | class Component{}
9 |
10 | class HelloWorld extends Component {
11 | render() {
12 | return Hello World
13 | }
14 | }
15 |
16 | render(, document.getElementById("root"))
17 |
--------------------------------------------------------------------------------
/example/helloworld/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "renderVDOM",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "./index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack --config ./webpack.config.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "babel-core": "~6.25.0",
14 | "babel-loader": "~7.1.1",
15 | "babel-plugin-transform-react-jsx": "^6.24.1",
16 | "babel-preset-es2015": "^6.24.1",
17 | "webpack": "^3.0.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/helloworld/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var rootPath = path.resolve(__dirname)
3 | module.exports = {
4 | entry: {
5 | index: './index.js'
6 | },
7 | output: {
8 | path: rootPath + '/build',
9 | filename: '[name].js'
10 | },
11 | module: {
12 | loaders: [
13 | {
14 | test: /\.jsx?$/,
15 | loader: 'babel-loader'
16 | }
17 | ]
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/example/lifecycle/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015"
4 | ],
5 | "plugins": [
6 | ["transform-react-jsx", {
7 | "pragma": "createElement"// default pragma is React.createElement
8 | }]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/example/lifecycle/ComplexComp.js:
--------------------------------------------------------------------------------
1 | import Component from '../../lib/Component'
2 | import createElement from '../../lib/createElement'
3 | import RenderedHelper from '../../lib/RenderedHelper'
4 |
5 | class TestAppA extends Component {
6 | constructor(props) {
7 | super(props)
8 | console.log("TestAppA constructor")
9 | }
10 |
11 | componentWillMount() {
12 | console.log("TestAppA componentWillMount")
13 | }
14 |
15 | componentDidMount() {
16 | console.log("TestAppA componentDidMount")
17 | }
18 |
19 | componentWillReceiveProps(nextProps) {
20 | console.log("TestAppA componentWillReceiveProps")
21 | }
22 |
23 | shouldComponentUpdate(nextProps, nextState) {
24 | console.log("TestAppA shouldComponentUpdate", nextProps, nextState)
25 | return true
26 | }
27 |
28 | componentWillUpdate() {
29 | console.log("TestAppA componentWillUpdate")
30 | }
31 |
32 | componentDidUpdate() {
33 | console.log("TestAppA componentDidUpdate")
34 | }
35 |
36 | componentWillUnmount() {
37 | console.log("TestAppA componentWillUnmount")
38 | }
39 |
40 |
41 |
42 | render() {
43 | console.log("TestAppA render...")
44 | return (
45 | TestAppA
46 | )
47 | }
48 | }
49 |
50 | class TestAppB extends Component {
51 | constructor(props) {
52 | super(props)
53 | console.log("TestAppB constructor")
54 | }
55 |
56 | componentWillMount() {
57 | console.log("TestAppB componentWillMount")
58 | }
59 |
60 | componentDidMount() {
61 | console.log("TestAppB componentDidMount")
62 | }
63 |
64 | componentWillReceiveProps(nextProps) {
65 | console.log("TestAppB componentWillReceiveProps")
66 | }
67 |
68 | shouldComponentUpdate(nextProps, nextState) {
69 | console.log("TestAppB shouldComponentUpdate", nextProps, nextState)
70 | return true
71 | }
72 |
73 | componentWillUpdate() {
74 | console.log("TestAppB componentWillUpdate")
75 | }
76 |
77 | componentDidUpdate() {
78 | console.log("TestAppB componentDidUpdate")
79 | }
80 |
81 | componentWillUnmount() {
82 | console.log("TestAppB componentWillUnmount")
83 | }
84 |
85 |
86 |
87 | render() {
88 | console.log("TestAppB render...")
89 | return (
90 | TestAppB
91 | )
92 | }
93 | }
94 |
95 | class TestAppC extends Component {
96 | constructor(props) {
97 | super(props)
98 | console.log("TestAppC constructor")
99 | }
100 |
101 | componentWillMount() {
102 | console.log("TestAppC componentWillMount")
103 | }
104 |
105 | componentDidMount() {
106 | console.log("TestAppC componentDidMount")
107 | }
108 |
109 | componentWillReceiveProps(nextProps) {
110 | console.log("TestAppC componentWillReceiveProps")
111 | }
112 |
113 | shouldComponentUpdate(nextProps, nextState) {
114 | console.log("TestAppC shouldComponentUpdate", nextProps, nextState)
115 | return true
116 | }
117 |
118 | componentWillUpdate() {
119 | console.log("TestAppC componentWillUpdate")
120 | }
121 |
122 | componentDidUpdate() {
123 | console.log("TestAppC componentDidUpdate")
124 | }
125 |
126 | componentWillUnmount() {
127 | console.log("TestAppC componentWillUnmount")
128 | }
129 |
130 |
131 |
132 | render() {
133 | console.log("TestAppC render...")
134 | return (
135 | TestAppC
136 | )
137 | }
138 | }
139 |
140 |
141 | class TestAppD extends Component {
142 | constructor(props) {
143 | super(props)
144 | console.log("TestAppD constructor")
145 | }
146 |
147 | componentWillMount() {
148 | console.log("TestAppD componentWillMount")
149 | }
150 |
151 | componentDidMount() {
152 | console.log("TestAppD componentDidMount")
153 | }
154 |
155 | componentWillReceiveProps(nextProps) {
156 | console.log("TestAppD componentWillReceiveProps")
157 | }
158 |
159 | shouldComponentUpdate(nextProps, nextState) {
160 | console.log("TestAppD shouldComponentUpdate", nextProps, nextState)
161 | return true
162 | }
163 |
164 | componentWillUpdate() {
165 | console.log("TestAppD componentWillUpdate")
166 | }
167 |
168 | componentDidUpdate() {
169 | console.log("TestAppD componentDidUpdate")
170 | }
171 |
172 | componentWillUnmount() {
173 | console.log("TestAppD componentWillUnmount")
174 | }
175 |
176 |
177 |
178 | render() {
179 | console.log("TestAppD render")
180 | if (this.props.text === "testA") {
181 | return
182 | } else if (this.props.text === "testB") {
183 | return
184 | } else {
185 | return
186 | }
187 | }
188 | }
189 |
190 | /// app1
191 |
192 | export default class ComplexComp extends Component {
193 | constructor(props) {
194 | super(props)
195 | this.state = {
196 | odd: false
197 | }
198 | }
199 |
200 | render() {
201 | return (
202 | this.setState({
203 | odd: !this.state.odd
204 | })}>
205 |
206 |
207 |
208 |
209 |
210 | {this.state.odd &&
}
211 |
212 |
213 | {!this.state.odd &&
}
214 |
215 |
216 | )
217 | }
218 | }
--------------------------------------------------------------------------------
/example/lifecycle/ComplexComp2.js:
--------------------------------------------------------------------------------
1 | import Component from '../../lib/Component'
2 | import createElement from '../../lib/createElement'
3 | import RenderedHelper from '../../lib/RenderedHelper'
4 |
5 | class TestAppA extends Component {
6 | constructor(props) {
7 | super(props)
8 | console.log("TestAppA constructor")
9 | }
10 |
11 | componentWillMount() {
12 | console.log("TestAppA componentWillMount")
13 | }
14 |
15 | componentDidMount() {
16 | console.log("TestAppA componentDidMount")
17 | }
18 |
19 | componentWillReceiveProps(nextProps) {
20 | console.log("TestAppA componentWillReceiveProps")
21 | }
22 |
23 | shouldComponentUpdate(nextProps, nextState) {
24 | console.log("TestAppA shouldComponentUpdate", nextProps, nextState)
25 | return true
26 | }
27 |
28 | componentWillUpdate() {
29 | console.log("TestAppA componentWillUpdate")
30 | }
31 |
32 | componentDidUpdate() {
33 | console.log("TestAppA componentDidUpdate")
34 | }
35 |
36 | componentWillUnmount() {
37 | console.log("TestAppA componentWillUnmount")
38 | }
39 |
40 |
41 |
42 | render() {
43 | console.log("TestAppA render...")
44 | return (
45 | TestAppA
46 | )
47 | }
48 | }
49 |
50 | class TestAppB extends Component {
51 | constructor(props) {
52 | super(props)
53 | console.log("TestAppB constructor")
54 | }
55 |
56 | componentWillMount() {
57 | console.log("TestAppB componentWillMount")
58 | }
59 |
60 | componentDidMount() {
61 | console.log("TestAppB componentDidMount")
62 | }
63 |
64 | componentWillReceiveProps(nextProps) {
65 | console.log("TestAppB componentWillReceiveProps")
66 | }
67 |
68 | shouldComponentUpdate(nextProps, nextState) {
69 | console.log("TestAppB shouldComponentUpdate", nextProps, nextState)
70 | return true
71 | }
72 |
73 | componentWillUpdate() {
74 | console.log("TestAppB componentWillUpdate")
75 | }
76 |
77 | componentDidUpdate() {
78 | console.log("TestAppB componentDidUpdate")
79 | }
80 |
81 | componentWillUnmount() {
82 | console.log("TestAppB componentWillUnmount")
83 | }
84 |
85 |
86 |
87 | render() {
88 | console.log("TestAppB render...")
89 | return (
90 | TestAppB
91 | )
92 | }
93 | }
94 |
95 | class TestAppC extends Component {
96 | constructor(props) {
97 | super(props)
98 | console.log("TestAppC constructor")
99 | }
100 |
101 | componentWillMount() {
102 | console.log("TestAppC componentWillMount")
103 | }
104 |
105 | componentDidMount() {
106 | console.log("TestAppC componentDidMount")
107 | }
108 |
109 | componentWillReceiveProps(nextProps) {
110 | console.log("TestAppC componentWillReceiveProps")
111 | }
112 |
113 | shouldComponentUpdate(nextProps, nextState) {
114 | console.log("TestAppC shouldComponentUpdate", nextProps, nextState)
115 | return true
116 | }
117 |
118 | componentWillUpdate() {
119 | console.log("TestAppC componentWillUpdate")
120 | }
121 |
122 | componentDidUpdate() {
123 | console.log("TestAppC componentDidUpdate")
124 | }
125 |
126 | componentWillUnmount() {
127 | console.log("TestAppC componentWillUnmount")
128 | }
129 |
130 |
131 |
132 | render() {
133 | console.log("TestAppC render...")
134 | return (
135 | TestAppC
136 | )
137 | }
138 | }
139 |
140 |
141 | class TestAppD extends Component {
142 | constructor(props) {
143 | super(props)
144 | console.log("TestAppD constructor")
145 | }
146 |
147 | componentWillMount() {
148 | console.log("TestAppD componentWillMount")
149 | }
150 |
151 | componentDidMount() {
152 | console.log("TestAppD componentDidMount")
153 | }
154 |
155 | componentWillReceiveProps(nextProps) {
156 | console.log("TestAppD componentWillReceiveProps")
157 | }
158 |
159 | shouldComponentUpdate(nextProps, nextState) {
160 | console.log("TestAppD shouldComponentUpdate", nextProps, nextState)
161 | return true
162 | }
163 |
164 | componentWillUpdate() {
165 | console.log("TestAppD componentWillUpdate")
166 | }
167 |
168 | componentDidUpdate() {
169 | console.log("TestAppD componentDidUpdate")
170 | }
171 |
172 | componentWillUnmount() {
173 | console.log("TestAppD componentWillUnmount")
174 | }
175 |
176 |
177 |
178 | render() {
179 | console.log("TestAppD render")
180 | if (this.props.text === "testA") {
181 | return
182 | } else if (this.props.text === "testB") {
183 | return
184 | } else {
185 | return
186 | }
187 | }
188 | }
189 |
190 | /// app1
191 |
192 | export default class ComplexComp extends Component {
193 | constructor(props) {
194 | super(props)
195 | this.state = {
196 | odd: false
197 | }
198 | }
199 |
200 | render() {
201 | return (
202 | this.setState({
203 | odd: !this.state.odd
204 | })}>
205 | {this.state.odd ?
:
}
206 |
hi2
207 |
hi3
208 |
209 | )
210 | }
211 | }
--------------------------------------------------------------------------------
/example/lifecycle/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/lifecycle/index.js:
--------------------------------------------------------------------------------
1 | import render from '../../lib/render'
2 | import Component from '../../lib/Component'
3 | import createElement from '../../lib/createElement'
4 | import RenderedHelper from '../../lib/RenderedHelper'
5 | import ComplexComp from './ComplexComp'
6 | import ComplexComp2 from './ComplexComp2'
7 |
8 | class TestApp extends Component {
9 | constructor(props) {
10 | super(props)
11 | console.log("TestApp constructor")
12 | }
13 |
14 | componentWillMount() {
15 | console.log("TestApp componentWillMount")
16 | }
17 |
18 | componentDidMount() {
19 | console.log("TestApp componentDidMount")
20 | }
21 |
22 | componentWillReceiveProps(nextProps) {
23 | console.log("TestApp componentWillReceiveProps")
24 | }
25 |
26 | shouldComponentUpdate(nextProps, nextState) {
27 | console.log("TestApp shouldComponentUpdate", nextProps, nextState)
28 | return true
29 | }
30 |
31 | componentWillUpdate() {
32 | console.log("TestApp componentWillUpdate")
33 | }
34 |
35 | componentDidUpdate() {
36 | console.log("TestApp componentDidUpdate")
37 | }
38 |
39 | componentWillUnmount() {
40 | console.log("TestApp componentWillUnmount")
41 | }
42 |
43 |
44 |
45 | render() {
46 | console.log("TestApp render...")
47 | return (
48 | TestApp
49 | )
50 | }
51 | }
52 |
53 | /// app1
54 |
55 | class App1 extends Component {
56 | render() {
57 | return (
58 | {
59 | this.setState({})
60 | }}>
61 |
62 |
63 | )
64 | }
65 | }
66 |
67 |
68 |
69 | render(, document.getElementById("root"))
--------------------------------------------------------------------------------
/example/lifecycle/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "renderVDOM",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "./index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack --config ./webpack.config.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "babel-core": "~6.25.0",
14 | "babel-loader": "~7.1.1",
15 | "babel-plugin-transform-react-jsx": "^6.24.1",
16 | "babel-preset-es2015": "^6.24.1",
17 | "webpack": "^3.0.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/lifecycle/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var rootPath = path.resolve(__dirname)
3 |
4 | module.exports = {
5 | entry: {
6 | index: './index.js'
7 | },
8 | output: {
9 | path: rootPath + '/build',
10 | filename: '[name].js'
11 | },
12 | module: {
13 | loaders: [
14 | {
15 | test: /\.jsx?$/,
16 | loader: 'babel-loader'
17 | }
18 | ]
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/example/my-vdom/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015"
4 | ],
5 | "plugins": [
6 | ["transform-react-jsx", {
7 | "pragma": "createElement"// default pragma is React.createElement
8 | }]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/example/my-vdom/diff.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/8/24.
3 | */
4 |
5 | /**
6 | * 1. 替换 div --> p
7 | * 2. 属性改变
8 | * 3. 子节点 增加 删除
9 | * @param oldTree
10 | * @param newTree
11 | */
12 | export function diff(oldTree, newTree) {
13 |
14 | }
--------------------------------------------------------------------------------
/example/my-vdom/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/my-vdom/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/8/21.
3 | */
4 |
5 | import createElement from '../../src/createElement'
6 | import diff from './diff'
7 |
8 | const app1= (
9 |
10 |
14 |
15 | - li1
16 | - li2
17 | - li3
18 |
19 |
20 | )
21 |
22 | const app2 =(
23 |
24 |
25 | - li1
26 | - li2
27 | - li3
28 |
29 |
30 | )
31 |
32 | const diff = diff(app1, app2)
33 | console.log("diff:", diff)
--------------------------------------------------------------------------------
/example/my-vdom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "renderVDOM",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "./index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack --config ./webpack.config.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "babel-core": "~6.25.0",
14 | "babel-loader": "~7.1.1",
15 | "babel-plugin-transform-react-jsx": "^6.24.1",
16 | "babel-preset-es2015": "^6.24.1",
17 | "webpack": "^3.0.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/my-vdom/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var rootPath = path.resolve(__dirname)
3 | module.exports = {
4 | entry: {
5 | index: './index.js'
6 | },
7 | output: {
8 | path: rootPath + '/build',
9 | filename: '[name].js'
10 | },
11 | module: {
12 | loaders: [
13 | {
14 | test: /\.jsx?$/,
15 | loader: 'babel-loader'
16 | }
17 | ]
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/example/propsAndState/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015"
4 | ],
5 | "plugins": [
6 | ["transform-react-jsx", {
7 | "pragma": "createElement"// default pragma is React.createElement
8 | }]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/example/propsAndState/build/index.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports;
11 | /******/ }
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.l = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // define getter function for harmony exports
37 | /******/ __webpack_require__.d = function(exports, name, getter) {
38 | /******/ if(!__webpack_require__.o(exports, name)) {
39 | /******/ Object.defineProperty(exports, name, {
40 | /******/ configurable: false,
41 | /******/ enumerable: true,
42 | /******/ get: getter
43 | /******/ });
44 | /******/ }
45 | /******/ };
46 | /******/
47 | /******/ // getDefaultExport function for compatibility with non-harmony modules
48 | /******/ __webpack_require__.n = function(module) {
49 | /******/ var getter = module && module.__esModule ?
50 | /******/ function getDefault() { return module['default']; } :
51 | /******/ function getModuleExports() { return module; };
52 | /******/ __webpack_require__.d(getter, 'a', getter);
53 | /******/ return getter;
54 | /******/ };
55 | /******/
56 | /******/ // Object.prototype.hasOwnProperty.call
57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58 | /******/
59 | /******/ // __webpack_public_path__
60 | /******/ __webpack_require__.p = "";
61 | /******/
62 | /******/ // Load entry module and return exports
63 | /******/ return __webpack_require__(__webpack_require__.s = 3);
64 | /******/ })
65 | /************************************************************************/
66 | /******/ ([
67 | /* 0 */
68 | /***/ (function(module, exports, __webpack_require__) {
69 |
70 | "use strict";
71 |
72 |
73 | Object.defineProperty(exports, "__esModule", {
74 | value: true
75 | });
76 |
77 | var _createClass = function () {
78 | function defineProperties(target, props) {
79 | for (var i = 0; i < props.length; i++) {
80 | var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);
81 | }
82 | }return function (Constructor, protoProps, staticProps) {
83 | if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;
84 | };
85 | }(); /**
86 | * Created by apple on 2017/7/20.
87 | */
88 |
89 | var _render = __webpack_require__(1);
90 |
91 | var _util = __webpack_require__(2);
92 |
93 | function _classCallCheck(instance, Constructor) {
94 | if (!(instance instanceof Constructor)) {
95 | throw new TypeError("Cannot call a class as a function");
96 | }
97 | }
98 |
99 | var Component = function () {
100 | function Component(props) {
101 | _classCallCheck(this, Component);
102 |
103 | this.props = props;
104 | }
105 |
106 | _createClass(Component, [{
107 | key: 'setState',
108 | value: function setState(state) {
109 | var _this = this;
110 |
111 | setTimeout(function () {
112 | var shoudUpdate = void 0;
113 | if (_this.shouldComponentUpdate) {
114 | shoudUpdate = _this.shouldComponentUpdate(_this.props, state);
115 | } else {
116 | shoudUpdate = true;
117 | }
118 |
119 | shoudUpdate && _this.componentWillUpdate && _this.componentWillUpdate(_this.props, state);
120 | _this.state = Object.assign(_this.state, state);
121 |
122 | if (!shoudUpdate) {
123 | return; // do nothing just return
124 | }
125 |
126 | var vnode = _this.render();
127 | var olddom = (0, _util.getDOM)(_this);
128 | var myIndex = (0, _util.getDOMIndex)(olddom);
129 | (0, _render.renderInner)(vnode, olddom.parentNode, _this, _this.__rendered, myIndex);
130 | _this.componentDidUpdate && _this.componentDidUpdate();
131 | }, 0);
132 | }
133 | }]);
134 |
135 | return Component;
136 | }();
137 |
138 | exports.default = Component;
139 |
140 | var a = {
141 | "nodeName": "div",
142 | "props": {},
143 | "children": ["i", { "nodeName": "div", "props": {}, "children": ["am"] }, {
144 | "nodeName": "div",
145 | "props": {},
146 | "children": ["grandson"]
147 | }]
148 | };
149 |
150 | /***/ }),
151 | /* 1 */
152 | /***/ (function(module, exports, __webpack_require__) {
153 |
154 | "use strict";
155 |
156 |
157 | var _typeof2 = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
158 |
159 | Object.defineProperty(exports, "__esModule", {
160 | value: true
161 | });
162 |
163 | var _typeof = typeof Symbol === "function" && _typeof2(Symbol.iterator) === "symbol" ? function (obj) {
164 | return typeof obj === "undefined" ? "undefined" : _typeof2(obj);
165 | } : function (obj) {
166 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj === "undefined" ? "undefined" : _typeof2(obj);
167 | }; /**
168 | * Created by apple on 2017/8/21.
169 | */
170 |
171 | exports.default = render;
172 | exports.renderInner = renderInner;
173 |
174 | var _util = __webpack_require__(2);
175 |
176 | var _Component = __webpack_require__(0);
177 |
178 | var _Component2 = _interopRequireDefault(_Component);
179 |
180 | var _events = __webpack_require__(4);
181 |
182 | function _interopRequireDefault(obj) {
183 | return obj && obj.__esModule ? obj : { default: obj };
184 | }
185 |
186 | /**
187 | * 渲染vnode成实际的dom
188 | * @param vnode 虚拟dom表示
189 | * @param parent 实际渲染出来的dom,挂载的父元素
190 | */
191 | function render(vnode, parent) {
192 | parent.__rendered = [];
193 |
194 | //events init
195 | (0, _events.init)();
196 |
197 | renderInner(vnode, parent, null, null, 0);
198 | }
199 |
200 | /**
201 | * 渲染vnode成实际的dom
202 | * @param vnode 虚拟dom表示
203 | * @param parent 实际渲染出来的dom,挂载的父元素
204 | * @param comp 谁渲染了我
205 | * @param olddomOrComp 老的dom/组件实例
206 | * @param myIndex 在dom节点的位置
207 | */
208 | function renderInner(vnode, parent, comp, olddomOrComp, myIndex) {
209 | var dom = void 0;
210 | if (typeof vnode === "string" || typeof vnode === "number") {
211 | if (olddomOrComp && olddomOrComp.splitText) {
212 | if (olddomOrComp.nodeValue !== vnode) {
213 | olddomOrComp.nodeValue = vnode;
214 | }
215 | } else {
216 | if (olddomOrComp) {
217 | recoveryComp(olddomOrComp);
218 | }
219 |
220 | dom = document.createTextNode(vnode);
221 | parent.__rendered[myIndex] = dom; //comp 一定是null
222 |
223 | setNewDom(parent, dom, myIndex);
224 | }
225 | } else if (typeof vnode.nodeName === "string") {
226 | if (!olddomOrComp || olddomOrComp.nodeName !== vnode.nodeName.toUpperCase()) {
227 | createNewDom(vnode, parent, comp, olddomOrComp, myIndex);
228 | } else {
229 | diffDOM(vnode, parent, comp, olddomOrComp, myIndex);
230 | }
231 | } else if (typeof vnode.nodeName === "function") {
232 | var func = vnode.nodeName;
233 | var inst = void 0;
234 | if (olddomOrComp && olddomOrComp instanceof func) {
235 | inst = olddomOrComp;
236 | inst.componentWillReceiveProps && inst.componentWillReceiveProps(vnode.props);
237 |
238 | var shoudUpdate = void 0;
239 | if (inst.shouldComponentUpdate) {
240 | shoudUpdate = inst.shouldComponentUpdate(vnode.props, olddomOrComp.state);
241 | } else {
242 | shoudUpdate = true;
243 | }
244 |
245 | shoudUpdate && inst.componentWillUpdate && inst.componentWillUpdate(vnode.props, olddomOrComp.state);
246 | inst.props = vnode.props;
247 |
248 | if (!shoudUpdate) {
249 | return; // do nothing just return
250 | }
251 | } else {
252 | if (olddomOrComp) {
253 | recoveryComp(olddomOrComp);
254 | }
255 |
256 | inst = new func(vnode.props);
257 | inst.componentWillMount && inst.componentWillMount();
258 |
259 | if (comp) {
260 | comp.__rendered = inst;
261 | } else {
262 | parent.__rendered[myIndex] = inst;
263 | }
264 | }
265 |
266 | var innerVnode = inst.render();
267 | renderInner(innerVnode, parent, inst, inst.__rendered, myIndex);
268 |
269 | if (olddomOrComp && olddomOrComp instanceof func) {
270 | inst.componentDidUpdate && inst.componentDidUpdate();
271 | } else {
272 | inst.componentDidMount && inst.componentDidMount();
273 | }
274 | }
275 | }
276 |
277 | /**
278 | * 替换新的Dom, 如果没有在最后插入
279 | * @param parent
280 | * @param newDom
281 | * @param myIndex
282 | */
283 | function setNewDom(parent, newDom, myIndex) {
284 | var old = parent.childNodes[myIndex];
285 | if (old) {
286 | parent.replaceChild(newDom, old);
287 | } else {
288 | parent.appendChild(newDom);
289 | }
290 | }
291 |
292 | function setAttrs(dom, props) {
293 | var allKeys = Object.keys(props);
294 | allKeys.forEach(function (k) {
295 | var v = props[k];
296 |
297 | if (k == "className") {
298 | dom.setAttribute("class", v);
299 | return;
300 | }
301 |
302 | if (k == "value") {
303 | dom.value = v;
304 | return;
305 | }
306 |
307 | if (k == "style") {
308 | if (typeof v == "string") {
309 | dom.style.cssText = v; //IE
310 | }
311 |
312 | if ((typeof v === 'undefined' ? 'undefined' : _typeof(v)) == "object") {
313 | for (var i in v) {
314 | dom.style[i] = v[i];
315 | }
316 | }
317 | return;
318 | }
319 |
320 | if (k[0] == "o" && k[1] == "n") {
321 |
322 | var key = k.substring(2, 3).toLowerCase() + k.substring(3);
323 | dom.__events[key] = v;
324 | return;
325 | }
326 |
327 | dom.setAttribute(k, v);
328 | });
329 | }
330 |
331 | function removeAttrs(dom, props) {
332 | for (var k in props) {
333 | if (k == "className") {
334 | dom.removeAttribute("class");
335 | continue;
336 | }
337 |
338 | if (k == "value") {
339 | dom.value = "";
340 | continue;
341 | }
342 |
343 | if (k == "style") {
344 | dom.style.cssText = ""; //IE
345 | continue;
346 | }
347 |
348 | if (k[0] == "o" && k[1] == "n") {
349 | var key = k.substring(2, 3).toLowerCase() + k.substring(3);
350 | dom.__events[key] = null;
351 | continue;
352 | }
353 |
354 | dom.removeAttribute(k);
355 | }
356 | }
357 |
358 | /**
359 | * 调用者保证newProps 与 oldProps 的keys是相同的
360 | * @param dom
361 | * @param newProps
362 | * @param oldProps
363 | */
364 | function diffAttrs(dom, newProps, oldProps) {
365 | for (var k in newProps) {
366 | var v = newProps[k];
367 | var ov = oldProps[k];
368 | if (v === ov) continue;
369 |
370 | if (k == "className") {
371 | dom.setAttribute("class", v);
372 | continue;
373 | }
374 |
375 | if (k == "value") {
376 | dom.value = v;
377 | continue;
378 | }
379 |
380 | if (k == "style") {
381 | if (typeof v == "string") {
382 | dom.style.cssText = v;
383 | } else if ((typeof v === 'undefined' ? 'undefined' : _typeof(v)) == "object" && (typeof ov === 'undefined' ? 'undefined' : _typeof(ov)) == "object") {
384 | for (var vk in v) {
385 | if (v[vk] !== ov[vk]) {
386 | dom.style[vk] = v[vk];
387 | }
388 | }
389 |
390 | for (var ovk in ov) {
391 | if (v[ovk] === undefined) {
392 | dom.style[ovk] = "";
393 | }
394 | }
395 | } else {
396 | //typeof v == "object" && typeof ov == "string"
397 | dom.style = {};
398 | for (var _vk in v) {
399 | dom.style[_vk] = v[_vk];
400 | }
401 | }
402 | continue;
403 | }
404 |
405 | if (k[0] == "o" && k[1] == "n") {
406 | var key = k.substring(2, 3).toLowerCase() + k.substring(3);
407 | dom.__events[key] = v;
408 | continue;
409 | }
410 |
411 | dom.setAttribute(k, v);
412 | }
413 | }
414 |
415 | function createNewDom(vnode, parent, comp, olddomOrComp, myIndex) {
416 | if (olddomOrComp) {
417 | recoveryComp(olddomOrComp);
418 | }
419 |
420 | var dom = document.createElement(vnode.nodeName);
421 |
422 | dom.__rendered = [];
423 | dom.__vnode = vnode;
424 | dom.__myIndex = myIndex; // 方便 getDOMIndex 方法
425 | dom.__events = {};
426 |
427 | if (comp) {
428 | comp.__rendered = dom;
429 | } else {
430 | parent.__rendered[myIndex] = dom;
431 | }
432 |
433 | setAttrs(dom, vnode.props);
434 |
435 | setNewDom(parent, dom, myIndex);
436 |
437 | for (var i = 0; i < vnode.children.length; i++) {
438 | renderInner(vnode.children[i], dom, null, null, i);
439 | }
440 | }
441 |
442 | function diffDOM(vnode, parent, comp, olddom) {
443 | var _diffObject = (0, _util.diffObject)(vnode.props, olddom.__vnode.props),
444 | onlyInLeft = _diffObject.onlyInLeft,
445 | bothIn = _diffObject.bothIn,
446 | onlyInRight = _diffObject.onlyInRight;
447 |
448 | setAttrs(olddom, onlyInLeft);
449 | removeAttrs(olddom, onlyInRight);
450 | diffAttrs(olddom, bothIn.left, bothIn.right);
451 |
452 | var willRemoveArr = olddom.__rendered.slice(vnode.children.length);
453 | var renderedArr = olddom.__rendered.slice(0, vnode.children.length);
454 | olddom.__rendered = renderedArr;
455 | for (var i = 0; i < vnode.children.length; i++) {
456 | renderInner(vnode.children[i], olddom, null, renderedArr[i], i);
457 | }
458 |
459 | willRemoveArr.forEach(function (element) {
460 | recoveryComp(element);
461 | olddom.removeChild((0, _util.getDOM)(element));
462 | });
463 |
464 | olddom.__vnode = vnode;
465 | }
466 |
467 | function recoveryComp(comp) {
468 | if (comp instanceof _Component2.default) {
469 | comp.componentWillUnmount && comp.componentWillUnmount();
470 | recoveryComp(comp.__rendered);
471 | } else if (comp.__rendered instanceof Array) {
472 | comp.__rendered.forEach(function (element) {
473 | recoveryComp(element);
474 | });
475 | } else {
476 | // do nothing
477 | }
478 | }
479 |
480 | /***/ }),
481 | /* 2 */
482 | /***/ (function(module, exports, __webpack_require__) {
483 |
484 | "use strict";
485 |
486 |
487 | Object.defineProperty(exports, "__esModule", {
488 | value: true
489 | });
490 | exports.diffObject = diffObject;
491 | exports.getDOM = getDOM;
492 | exports.getDOMIndex = getDOMIndex;
493 |
494 | var _Component = __webpack_require__(0);
495 |
496 | var _Component2 = _interopRequireDefault(_Component);
497 |
498 | function _interopRequireDefault(obj) {
499 | return obj && obj.__esModule ? obj : { default: obj };
500 | }
501 |
502 | function diffObject(leftProps, rightProps) {
503 | var onlyInLeft = {};
504 | var bothLeft = {};
505 | var bothRight = {};
506 | var onlyInRight = {};
507 |
508 | for (var key in leftProps) {
509 | if (rightProps[key] === undefined) {
510 | onlyInLeft[key] = leftProps[key];
511 | } else {
512 | bothLeft[key] = leftProps[key];
513 | bothRight[key] = rightProps[key];
514 | }
515 | }
516 |
517 | for (var _key in rightProps) {
518 | if (leftProps[_key] === undefined) {
519 | onlyInRight[_key] = rightProps[_key];
520 | }
521 | }
522 |
523 | return {
524 | onlyInRight: onlyInRight,
525 | onlyInLeft: onlyInLeft,
526 | bothIn: {
527 | left: bothLeft,
528 | right: bothRight
529 | }
530 | };
531 | } /**
532 | * Created by apple on 2017/8/30.
533 | */
534 | function getDOM(comp) {
535 | var rendered = comp;
536 | while (rendered instanceof _Component2.default) {
537 | //判断对象是否是dom
538 | rendered = rendered.__rendered;
539 | }
540 | return rendered;
541 | }
542 |
543 | function getDOMIndex(dom) {
544 | return dom.__myIndex;
545 | }
546 |
547 | /***/ }),
548 | /* 3 */
549 | /***/ (function(module, exports, __webpack_require__) {
550 |
551 | "use strict";
552 |
553 |
554 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
555 |
556 | var _render = __webpack_require__(1);
557 |
558 | var _render2 = _interopRequireDefault(_render);
559 |
560 | var _Component3 = __webpack_require__(0);
561 |
562 | var _Component4 = _interopRequireDefault(_Component3);
563 |
564 | var _createElement = __webpack_require__(5);
565 |
566 | var _createElement2 = _interopRequireDefault(_createElement);
567 |
568 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
569 |
570 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
571 |
572 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
573 |
574 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
575 | * Created by apple on 2017/8/21.
576 | */
577 |
578 |
579 | var Dog = function (_Component) {
580 | _inherits(Dog, _Component);
581 |
582 | function Dog() {
583 | _classCallCheck(this, Dog);
584 |
585 | return _possibleConstructorReturn(this, (Dog.__proto__ || Object.getPrototypeOf(Dog)).apply(this, arguments));
586 | }
587 |
588 | _createClass(Dog, [{
589 | key: 'render',
590 | value: function render() {
591 | return (0, _createElement2.default)(
592 | 'div',
593 | { style: { color: this.props.color } },
594 | 'i am a ',
595 | this.props.color,
596 | ' dog, click me to change color'
597 | );
598 | }
599 | }]);
600 |
601 | return Dog;
602 | }(_Component4.default);
603 |
604 | var colorArray = ['red', 'blue', 'yellow', 'black', 'green'];
605 |
606 | var PS = function (_Component2) {
607 | _inherits(PS, _Component2);
608 |
609 | function PS(props) {
610 | _classCallCheck(this, PS);
611 |
612 | var _this2 = _possibleConstructorReturn(this, (PS.__proto__ || Object.getPrototypeOf(PS)).call(this, props));
613 |
614 | _this2.state = {
615 | color: 'grey'
616 | };
617 | return _this2;
618 | }
619 |
620 | _createClass(PS, [{
621 | key: 'handleClick',
622 | value: function handleClick() {
623 | this.setState({
624 | color: colorArray[parseInt(Math.random() * 5)]
625 | });
626 | }
627 | }, {
628 | key: 'render',
629 | value: function render() {
630 | return (0, _createElement2.default)(
631 | 'div',
632 | { onClick: this.handleClick.bind(this) },
633 | (0, _createElement2.default)(Dog, { color: this.state.color })
634 | );
635 | }
636 | }]);
637 |
638 | return PS;
639 | }(_Component4.default);
640 |
641 | (0, _render2.default)((0, _createElement2.default)(PS, null), document.getElementById("root"));
642 |
643 | /***/ }),
644 | /* 4 */
645 | /***/ (function(module, exports, __webpack_require__) {
646 |
647 | "use strict";
648 |
649 |
650 | Object.defineProperty(exports, "__esModule", {
651 | value: true
652 | });
653 | exports.init = init;
654 | var supportEventType = ['keydown', 'keypress', 'keyup', 'click', 'mouseenter', 'mouseover', 'mousemove', 'change'];
655 |
656 | function getSyntheticEvent(e) {
657 | e.stopPropagation = function () {
658 | e.__notup = true; // 不冒泡
659 | };
660 | return e;
661 | }
662 |
663 | function getRelatedDOMList(e) {
664 | var path = e.path || e.deepPath;
665 | if (path) return path;
666 |
667 | var result = [];
668 |
669 | var node = e.target;
670 | while (node !== window.document) {
671 |
672 | result.push(node);
673 | node = node.parentNode;
674 | }
675 |
676 | return result;
677 | }
678 |
679 | function superHandle(e) {
680 |
681 | var syntheticEvent = getSyntheticEvent(e);
682 |
683 | var domList = getRelatedDOMList(e);
684 | console.log('domList:', domList);
685 |
686 | var eventType = e.type;
687 |
688 | for (var i = domList.length - 1; i >= 0; i--) {
689 | // 捕获期
690 | var eleDom = domList[i];
691 |
692 | if (!eleDom.__events) break;
693 |
694 | var handle = eleDom.__events[eventType + 'Capture'];
695 | handle && handle(syntheticEvent);
696 | }
697 |
698 | for (var _i = 0; _i < domList.length; _i++) {
699 | // 冒泡期
700 | var _eleDom = domList[_i];
701 |
702 | if (!_eleDom.__events) break;
703 |
704 | var _handle = _eleDom.__events[eventType];
705 | _handle && _handle(syntheticEvent);
706 | if (syntheticEvent.__notup) {
707 | break;
708 | }
709 | }
710 | }
711 |
712 | function init() {
713 | supportEventType.forEach(function (eventType) {
714 | document.addEventListener(eventType, superHandle);
715 | });
716 | }
717 |
718 | /***/ }),
719 | /* 5 */
720 | /***/ (function(module, exports, __webpack_require__) {
721 |
722 | "use strict";
723 |
724 |
725 | Object.defineProperty(exports, "__esModule", {
726 | value: true
727 | });
728 | exports.default = createElement;
729 | /**
730 | * Created by apple on 2017/7/16.
731 | */
732 |
733 | /**
734 | *
735 | * @param comp func or div/p/span/..
736 | * @param props {}
737 | * @param children
738 | */
739 | function createElement(comp, props) {
740 | var children = [];
741 |
742 | for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
743 | args[_key - 2] = arguments[_key];
744 | }
745 |
746 | for (var i = 0; i < args.length; i++) {
747 | if (typeof args[i] === 'boolean' || args[i] === undefined || args === null) continue;
748 | if (args[i] instanceof Array) {
749 | children = children.concat(args[i]);
750 | } else {
751 | children.push(args[i]);
752 | }
753 | }
754 |
755 | return {
756 | nodeName: comp,
757 | props: props || {},
758 | children: children
759 | };
760 | }
761 |
762 | /***/ })
763 | /******/ ]);
--------------------------------------------------------------------------------
/example/propsAndState/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/propsAndState/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/8/21.
3 | */
4 | import render from '../../lib/render'
5 | import Component from '../../lib/Component'
6 | import createElement from '../../lib/createElement'
7 |
8 | class Dog extends Component {
9 | render() {
10 | return (
11 | i am a {this.props.color} dog, click me to change color
12 | )
13 | }
14 | }
15 |
16 | const colorArray = ['red', 'blue', 'yellow', 'black', 'green']
17 | class PS extends Component {
18 | constructor(props) {
19 | super(props)
20 | this.state = {
21 | color: 'grey'
22 | }
23 | }
24 | handleClick() {
25 | this.setState({
26 | color: colorArray[parseInt(Math.random() * 5)]
27 | })
28 | }
29 | render() {
30 | return (
31 |
32 |
33 |
34 | )
35 | }
36 | }
37 |
38 | render(, document.getElementById("root"))
39 |
--------------------------------------------------------------------------------
/example/propsAndState/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "renderVDOM",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "./index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack --config ./webpack.config.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "babel-core": "~6.25.0",
14 | "babel-loader": "~7.1.1",
15 | "babel-plugin-transform-react-jsx": "^6.24.1",
16 | "babel-preset-es2015": "^6.24.1",
17 | "webpack": "^3.0.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/propsAndState/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var rootPath = path.resolve(__dirname)
3 |
4 | module.exports = {
5 | entry: {
6 | index: './index.js'
7 | },
8 | output: {
9 | path: rootPath + '/build',
10 | filename: '[name].js'
11 | },
12 | module: {
13 | loaders: [
14 | {
15 | test: /\.jsx?$/,
16 | loader: 'babel-loader'
17 | }
18 | ]
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/example/renderVDOM/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015"
4 | ],
5 | "plugins": [
6 | ["transform-react-jsx", {
7 | "pragma": "createElement"// default pragma is React.createElement
8 | }]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/example/renderVDOM/build/index.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports;
11 | /******/ }
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.l = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // define getter function for harmony exports
37 | /******/ __webpack_require__.d = function(exports, name, getter) {
38 | /******/ if(!__webpack_require__.o(exports, name)) {
39 | /******/ Object.defineProperty(exports, name, {
40 | /******/ configurable: false,
41 | /******/ enumerable: true,
42 | /******/ get: getter
43 | /******/ });
44 | /******/ }
45 | /******/ };
46 | /******/
47 | /******/ // getDefaultExport function for compatibility with non-harmony modules
48 | /******/ __webpack_require__.n = function(module) {
49 | /******/ var getter = module && module.__esModule ?
50 | /******/ function getDefault() { return module['default']; } :
51 | /******/ function getModuleExports() { return module; };
52 | /******/ __webpack_require__.d(getter, 'a', getter);
53 | /******/ return getter;
54 | /******/ };
55 | /******/
56 | /******/ // Object.prototype.hasOwnProperty.call
57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58 | /******/
59 | /******/ // __webpack_public_path__
60 | /******/ __webpack_require__.p = "";
61 | /******/
62 | /******/ // Load entry module and return exports
63 | /******/ return __webpack_require__(__webpack_require__.s = 0);
64 | /******/ })
65 | /************************************************************************/
66 | /******/ ([
67 | /* 0 */
68 | /***/ (function(module, exports, __webpack_require__) {
69 |
70 | "use strict";
71 |
72 |
73 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
74 |
75 | var _renderVDOM = __webpack_require__(1);
76 |
77 | var _renderVDOM2 = _interopRequireDefault(_renderVDOM);
78 |
79 | var _createElement = __webpack_require__(2);
80 |
81 | var _createElement2 = _interopRequireDefault(_createElement);
82 |
83 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
84 |
85 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
86 |
87 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
88 |
89 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /**
90 | * Created by apple on 2017/8/21.
91 | */
92 |
93 | var Component = function Component() {
94 | _classCallCheck(this, Component);
95 | };
96 |
97 | var Grandson = function (_Component) {
98 | _inherits(Grandson, _Component);
99 |
100 | function Grandson() {
101 | _classCallCheck(this, Grandson);
102 |
103 | return _possibleConstructorReturn(this, (Grandson.__proto__ || Object.getPrototypeOf(Grandson)).apply(this, arguments));
104 | }
105 |
106 | _createClass(Grandson, [{
107 | key: 'render',
108 | value: function render() {
109 | return (0, _createElement2.default)(
110 | 'div',
111 | null,
112 | 'i am grandson'
113 | ); // React.createElement('div', null, "i am grandson")
114 | }
115 | }]);
116 |
117 | return Grandson;
118 | }(Component);
119 |
120 | var Son = function (_Component2) {
121 | _inherits(Son, _Component2);
122 |
123 | function Son() {
124 | _classCallCheck(this, Son);
125 |
126 | return _possibleConstructorReturn(this, (Son.__proto__ || Object.getPrototypeOf(Son)).apply(this, arguments));
127 | }
128 |
129 | _createClass(Son, [{
130 | key: 'render',
131 | value: function render() {
132 | return (0, _createElement2.default)(Grandson, null); // React.createElement(Grandson)
133 | }
134 | }]);
135 |
136 | return Son;
137 | }(Component);
138 |
139 | var Father = function (_Component3) {
140 | _inherits(Father, _Component3);
141 |
142 | function Father() {
143 | _classCallCheck(this, Father);
144 |
145 | return _possibleConstructorReturn(this, (Father.__proto__ || Object.getPrototypeOf(Father)).apply(this, arguments));
146 | }
147 |
148 | _createClass(Father, [{
149 | key: 'render',
150 | value: function render() {
151 | return (0, _createElement2.default)(Son, null); // React.createElement(Son)
152 | }
153 | }]);
154 |
155 | return Father;
156 | }(Component);
157 |
158 | var vv = (0, _renderVDOM2.default)((0, _createElement2.default)(Father, null));
159 | console.log("vv:", vv);
160 |
161 | /***/ }),
162 | /* 1 */
163 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
164 |
165 | "use strict";
166 | Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
167 | /* harmony export (immutable) */ __webpack_exports__["default"] = renderVDOM;
168 | /**
169 | * Created by apple on 2017/8/21.
170 | */
171 | function renderVDOM(vnode) {
172 | if (typeof vnode == "string") {
173 | return vnode;
174 | } else if (typeof vnode.nodeName == "string") {
175 | let result = {
176 | nodeName: vnode.nodeName,
177 | props: vnode.props,
178 | children: []
179 | };
180 | for (let i = 0; i < vnode.children.length; i++) {
181 | result.children.push(renderVDOM(vnode.children[i]));
182 | }
183 | return result;
184 | } else if (typeof vnode.nodeName == "function") {
185 | let func = vnode.nodeName;
186 | let inst = new func(vnode.props);
187 | let innerVnode = func.prototype.render.call(inst);
188 | return renderVDOM(innerVnode);
189 | }
190 | }
191 |
192 | /***/ }),
193 | /* 2 */
194 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
195 |
196 | "use strict";
197 | Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
198 | /* harmony export (immutable) */ __webpack_exports__["default"] = createElement;
199 | /**
200 | * Created by apple on 2017/7/16.
201 | */
202 |
203 | /**
204 | *
205 | * @param comp func or div/p/span/..
206 | * @param props {}
207 | * @param children
208 | */
209 | function createElement(comp, props, ...args) {
210 | return {
211 | nodeName: comp,
212 | props: props || {},
213 | children: args || []
214 | };
215 | }
216 |
217 | /***/ })
218 | /******/ ]);
--------------------------------------------------------------------------------
/example/renderVDOM/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/8/21.
3 | */
4 |
5 | import renderVDOM from '../../src/renderVDOM'
6 | import createElement from '../../src/createElement'
7 |
8 |
9 | class Component{}
10 |
11 | class Grandson extends Component {
12 | render() {
13 | return i am grandson
// React.createElement('div', null, "i am grandson")
14 | }
15 | }
16 | class Son extends Component {
17 | render() {
18 | return // React.createElement(Grandson)
19 | }
20 | }
21 | class Father extends Component {
22 | render() {
23 | return // React.createElement(Son)
24 | }
25 | }
26 |
27 | const vv = renderVDOM()
28 | console.log("vv:", vv)
29 |
--------------------------------------------------------------------------------
/example/renderVDOM/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "renderVDOM",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "./index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack --config ./webpack.config.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "babel-core": "~6.25.0",
14 | "babel-loader": "~7.1.1",
15 | "babel-plugin-transform-react-jsx": "^6.24.1",
16 | "babel-preset-es2015": "^6.24.1",
17 | "webpack": "^3.0.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/renderVDOM/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var rootPath = path.resolve(__dirname)
3 | module.exports = {
4 | entry: {
5 | index: './index.js'
6 | },
7 | output: {
8 | path: rootPath + '/build',
9 | filename: '[name].js'
10 | },
11 | module: {
12 | loaders: [
13 | {
14 | test: /\.jsx?$/,
15 | loader: 'babel-loader'
16 | }
17 | ]
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/example/todo/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015"
4 | ],
5 | "plugins": [
6 | ["transform-react-jsx", {
7 | "pragma": "createElement"// default pragma is React.createElement
8 | }]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/example/todo/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 |
4 | .idea
5 | *.iml
6 |
7 | **/node_modules
8 | **/build
--------------------------------------------------------------------------------
/example/todo/README.md:
--------------------------------------------------------------------------------
1 | ### tinyreact todo
--------------------------------------------------------------------------------
/example/todo/app/Footer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/9/13.
3 | */
4 | import { createElement, Component } from 'tinyreact'
5 |
6 | export default class Footer extends Component {
7 | render() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | )
15 | }
16 | }
--------------------------------------------------------------------------------
/example/todo/app/HeaderInput.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/9/13.
3 | */
4 | import { createElement, Component } from 'tinyreact'
5 |
6 | export default class HeaderInput extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {
10 | value: ''
11 | }
12 | }
13 |
14 | handleBlur(e) {
15 | this.setState({
16 | value: e.target.value
17 | })
18 | }
19 |
20 | handleClick() {
21 | this.props.addTask(this.state.value)
22 | this.setState({
23 | value:''
24 | })
25 | }
26 |
27 | render() {
28 | return (
29 |
30 |
33 |
34 |
35 | )
36 | }
37 | }
--------------------------------------------------------------------------------
/example/todo/app/TaskList.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/9/13.
3 | */
4 | import { createElement, Component } from 'tinyreact'
5 | export default class TaskList extends Component {
6 | render() {
7 | return (
8 |
9 |
10 | {this.props.list.map((element) => {
11 | return (- this.props.changeStatus(element)}
14 | >{element.value}
)
15 | })}
16 |
17 |
18 | )
19 | }
20 | }
--------------------------------------------------------------------------------
/example/todo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/todo/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/8/21.
3 | */
4 | import Tinyreact, { createElement, Component } from 'tinyreact'
5 |
6 | import HeaderInput from './app/HeaderInput'
7 | import TaskList from './app/TaskList'
8 | import Footer from './app/Footer'
9 |
10 | class App extends Component {
11 | constructor(props) {
12 | super(props)
13 | this.state = {
14 | list: [
15 | {
16 | value: "init task",
17 | status: "done"
18 | }
19 | ],
20 | filter:'all'
21 | }
22 | }
23 |
24 | addTask(value) {
25 | const list = this.state.list
26 | list.push({
27 | value,
28 | status: 'undo'
29 | })
30 | this.setState({
31 | list: list
32 | })
33 | }
34 |
35 | changeStatus(element) {
36 | let list = this.state.list
37 | list = list.map(ele => {
38 | if (ele === element) {
39 | return {
40 | value: element.value,
41 | status: element.status === 'undo' ? 'done' : 'undo'
42 | }
43 | } else {
44 | return ele
45 | }
46 | })
47 |
48 | this.setState({
49 | list: list
50 | })
51 | }
52 |
53 | filterStatus(filter) {
54 | this.setState({
55 | filter
56 | })
57 | }
58 |
59 | render() {
60 | let list
61 | if(this.state.filter === "all") {
62 | list = this.state.list
63 | } else {
64 | list = this.state.list.filter(element => (element.status === this.state.filter))
65 | }
66 |
67 | return (
68 |
69 |
70 |
74 |
77 |
78 | )
79 | }
80 | }
81 |
82 | Tinyreact.render(, document.getElementById("root"))
--------------------------------------------------------------------------------
/example/todo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo",
3 | "version": "1.0.0",
4 | "description": "todo list by tinyreact",
5 | "main": "./index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack --config ./webpack.config.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "babel-core": "~6.25.0",
14 | "babel-loader": "~7.1.1",
15 | "babel-plugin-transform-react-jsx": "^6.24.1",
16 | "babel-preset-es2015": "^6.24.1",
17 | "webpack": "^3.0.0"
18 | },
19 | "dependencies": {
20 | "tinyreact": "^1.0.5"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/example/todo/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var rootPath = path.resolve(__dirname)
3 | module.exports = {
4 | entry: {
5 | index: './index.js'
6 | },
7 | output: {
8 | path: rootPath + '/build',
9 | filename: '[name].js'
10 | },
11 | module: {
12 | loaders: [
13 | {
14 | test: /\.jsx?$/,
15 | loader: 'babel-loader'
16 | }
17 | ]
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/example/yiwandiv/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015"
4 | ],
5 | "plugins": [
6 | ["transform-react-jsx", {
7 | "pragma": "createElement"// default pragma is React.createElement
8 | }]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/example/yiwandiv/build/index.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports;
11 | /******/ }
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.l = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // define getter function for harmony exports
37 | /******/ __webpack_require__.d = function(exports, name, getter) {
38 | /******/ if(!__webpack_require__.o(exports, name)) {
39 | /******/ Object.defineProperty(exports, name, {
40 | /******/ configurable: false,
41 | /******/ enumerable: true,
42 | /******/ get: getter
43 | /******/ });
44 | /******/ }
45 | /******/ };
46 | /******/
47 | /******/ // getDefaultExport function for compatibility with non-harmony modules
48 | /******/ __webpack_require__.n = function(module) {
49 | /******/ var getter = module && module.__esModule ?
50 | /******/ function getDefault() { return module['default']; } :
51 | /******/ function getModuleExports() { return module; };
52 | /******/ __webpack_require__.d(getter, 'a', getter);
53 | /******/ return getter;
54 | /******/ };
55 | /******/
56 | /******/ // Object.prototype.hasOwnProperty.call
57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58 | /******/
59 | /******/ // __webpack_public_path__
60 | /******/ __webpack_require__.p = "";
61 | /******/
62 | /******/ // Load entry module and return exports
63 | /******/ return __webpack_require__(__webpack_require__.s = 3);
64 | /******/ })
65 | /************************************************************************/
66 | /******/ ([
67 | /* 0 */
68 | /***/ (function(module, exports, __webpack_require__) {
69 |
70 | "use strict";
71 |
72 |
73 | Object.defineProperty(exports, "__esModule", {
74 | value: true
75 | });
76 |
77 | var _createClass = function () {
78 | function defineProperties(target, props) {
79 | for (var i = 0; i < props.length; i++) {
80 | var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);
81 | }
82 | }return function (Constructor, protoProps, staticProps) {
83 | if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;
84 | };
85 | }(); /**
86 | * Created by apple on 2017/7/20.
87 | */
88 |
89 | var _render = __webpack_require__(1);
90 |
91 | var _util = __webpack_require__(2);
92 |
93 | function _classCallCheck(instance, Constructor) {
94 | if (!(instance instanceof Constructor)) {
95 | throw new TypeError("Cannot call a class as a function");
96 | }
97 | }
98 |
99 | var Component = function () {
100 | function Component(props) {
101 | _classCallCheck(this, Component);
102 |
103 | this.props = props;
104 | }
105 |
106 | _createClass(Component, [{
107 | key: 'setState',
108 | value: function setState(state) {
109 | var _this = this;
110 |
111 | setTimeout(function () {
112 | var shoudUpdate = void 0;
113 | if (_this.shouldComponentUpdate) {
114 | shoudUpdate = _this.shouldComponentUpdate(_this.props, state);
115 | } else {
116 | shoudUpdate = true;
117 | }
118 |
119 | shoudUpdate && _this.componentWillUpdate && _this.componentWillUpdate(_this.props, state);
120 | _this.state = state;
121 |
122 | if (!shoudUpdate) {
123 | return; // do nothing just return
124 | }
125 |
126 | var vnode = _this.render();
127 | var olddom = (0, _util.getDOM)(_this);
128 | var myIndex = (0, _util.getDOMIndex)(olddom);
129 | (0, _render.renderInner)(vnode, olddom.parentNode, _this, _this.__rendered, myIndex);
130 | _this.componentDidUpdate && _this.componentDidUpdate();
131 | }, 0);
132 | }
133 | }]);
134 |
135 | return Component;
136 | }();
137 |
138 | exports.default = Component;
139 |
140 | /***/ }),
141 | /* 1 */
142 | /***/ (function(module, exports, __webpack_require__) {
143 |
144 | "use strict";
145 |
146 |
147 | var _typeof2 = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
148 |
149 | Object.defineProperty(exports, "__esModule", {
150 | value: true
151 | });
152 |
153 | var _typeof = typeof Symbol === "function" && _typeof2(Symbol.iterator) === "symbol" ? function (obj) {
154 | return typeof obj === "undefined" ? "undefined" : _typeof2(obj);
155 | } : function (obj) {
156 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj === "undefined" ? "undefined" : _typeof2(obj);
157 | }; /**
158 | * Created by apple on 2017/8/21.
159 | */
160 |
161 | exports.default = render;
162 | exports.renderInner = renderInner;
163 |
164 | var _util = __webpack_require__(2);
165 |
166 | var _Component = __webpack_require__(0);
167 |
168 | var _Component2 = _interopRequireDefault(_Component);
169 |
170 | function _interopRequireDefault(obj) {
171 | return obj && obj.__esModule ? obj : { default: obj };
172 | }
173 |
174 | /**
175 | * 渲染vnode成实际的dom
176 | * @param vnode 虚拟dom表示
177 | * @param parent 实际渲染出来的dom,挂载的父元素
178 | */
179 | function render(vnode, parent) {
180 | parent.__rendered = [];
181 | renderInner(vnode, parent, null, null, 0);
182 | }
183 |
184 | /**
185 | * 渲染vnode成实际的dom
186 | * @param vnode 虚拟dom表示
187 | * @param parent 实际渲染出来的dom,挂载的父元素
188 | * @param comp 谁渲染了我
189 | * @param olddomOrComp 老的dom/组件实例
190 | * @param myIndex 在dom节点的位置
191 | */
192 | function renderInner(vnode, parent, comp, olddomOrComp, myIndex) {
193 | var dom = void 0;
194 | if (typeof vnode === "string" || typeof vnode === "number") {
195 | if (olddomOrComp && olddomOrComp.splitText) {
196 | if (olddomOrComp.nodeValue !== vnode) {
197 | olddomOrComp.nodeValue = vnode;
198 | }
199 | } else {
200 | if (olddomOrComp) {
201 | recoveryComp(olddomOrComp);
202 | }
203 |
204 | dom = document.createTextNode(vnode);
205 | parent.__rendered[myIndex] = dom; //comp 一定是null
206 |
207 | setNewDom(parent, dom, myIndex);
208 | }
209 | } else if (typeof vnode.nodeName === "string") {
210 | if (!olddomOrComp || olddomOrComp.nodeName !== vnode.nodeName.toUpperCase()) {
211 | createNewDom(vnode, parent, comp, olddomOrComp, myIndex);
212 | } else {
213 | diffDOM(vnode, parent, comp, olddomOrComp, myIndex);
214 | }
215 | } else if (typeof vnode.nodeName === "function") {
216 | var func = vnode.nodeName;
217 | var inst = void 0;
218 | if (olddomOrComp && olddomOrComp instanceof func) {
219 | inst = olddomOrComp;
220 | inst.componentWillReceiveProps && inst.componentWillReceiveProps(vnode.props);
221 |
222 | var shoudUpdate = void 0;
223 | if (inst.shouldComponentUpdate) {
224 | shoudUpdate = inst.shouldComponentUpdate(vnode.props, olddomOrComp.state);
225 | } else {
226 | shoudUpdate = true;
227 | }
228 |
229 | shoudUpdate && inst.componentWillUpdate && inst.componentWillUpdate(vnode.props, olddomOrComp.state);
230 | inst.props = vnode.props;
231 |
232 | if (!shoudUpdate) {
233 | return; // do nothing just return
234 | }
235 | } else {
236 | if (olddomOrComp) {
237 | recoveryComp(olddomOrComp);
238 | }
239 |
240 | inst = new func(vnode.props);
241 | inst.componentWillMount && inst.componentWillMount();
242 |
243 | if (comp) {
244 | comp.__rendered = inst;
245 | } else {
246 | parent.__rendered[myIndex] = inst;
247 | }
248 | }
249 |
250 | var innerVnode = inst.render();
251 | renderInner(innerVnode, parent, inst, inst.__rendered, myIndex);
252 |
253 | if (olddomOrComp && olddomOrComp instanceof func) {
254 | inst.componentDidUpdate && inst.componentDidUpdate();
255 | } else {
256 | inst.componentDidMount && inst.componentDidMount();
257 | }
258 | }
259 | }
260 |
261 | /**
262 | * 替换新的Dom, 如果没有在最后插入
263 | * @param parent
264 | * @param newDom
265 | * @param myIndex
266 | */
267 | function setNewDom(parent, newDom, myIndex) {
268 | var old = parent.childNodes[myIndex];
269 | if (old) {
270 | parent.replaceChild(newDom, old);
271 | } else {
272 | parent.appendChild(newDom);
273 | }
274 | }
275 |
276 | function setAttrs(dom, props) {
277 | var allKeys = Object.keys(props);
278 | allKeys.forEach(function (k) {
279 | var v = props[k];
280 |
281 | if (k == "className") {
282 | dom.setAttribute("class", v);
283 | return;
284 | }
285 |
286 | if (k == "style") {
287 | if (typeof v == "string") {
288 | dom.style.cssText = v; //IE
289 | }
290 |
291 | if ((typeof v === 'undefined' ? 'undefined' : _typeof(v)) == "object") {
292 | for (var i in v) {
293 | dom.style[i] = v[i];
294 | }
295 | }
296 | return;
297 | }
298 |
299 | if (k[0] == "o" && k[1] == "n") {
300 | var capture = k.indexOf("Capture") != -1;
301 | dom.addEventListener(k.substring(2).toLowerCase(), v, capture);
302 | return;
303 | }
304 |
305 | dom.setAttribute(k, v);
306 | });
307 | }
308 |
309 | function removeAttrs(dom, props) {
310 | for (var k in props) {
311 | if (k == "className") {
312 | dom.removeAttribute("class");
313 | continue;
314 | }
315 |
316 | if (k == "style") {
317 | dom.style.cssText = ""; //IE
318 | continue;
319 | }
320 |
321 | if (k[0] == "o" && k[1] == "n") {
322 | var capture = k.indexOf("Capture") != -1;
323 | var v = props[k];
324 | dom.removeEventListener(k.substring(2).toLowerCase(), v, capture);
325 | continue;
326 | }
327 |
328 | dom.removeAttribute(k);
329 | }
330 | }
331 |
332 | /**
333 | * 调用者保证newProps 与 oldProps 的keys是相同的
334 | * @param dom
335 | * @param newProps
336 | * @param oldProps
337 | */
338 | function diffAttrs(dom, newProps, oldProps) {
339 | for (var k in newProps) {
340 | var v = newProps[k];
341 | var ov = oldProps[k];
342 | if (v === ov) continue;
343 |
344 | if (k == "className") {
345 | dom.setAttribute("class", v);
346 | continue;
347 | }
348 |
349 | if (k == "style") {
350 | if (typeof v == "string") {
351 | dom.style.cssText = v;
352 | } else if ((typeof v === 'undefined' ? 'undefined' : _typeof(v)) == "object" && (typeof ov === 'undefined' ? 'undefined' : _typeof(ov)) == "object") {
353 | for (var vk in v) {
354 | if (v[vk] !== ov[vk]) {
355 | dom.style[vk] = v[vk];
356 | }
357 | }
358 |
359 | for (var ovk in ov) {
360 | if (v[ovk] === undefined) {
361 | dom.style[ovk] = "";
362 | }
363 | }
364 | } else {
365 | //typeof v == "object" && typeof ov == "string"
366 | dom.style = {};
367 | for (var _vk in v) {
368 | dom.style[_vk] = v[_vk];
369 | }
370 | }
371 | continue;
372 | }
373 |
374 | if (k[0] == "o" && k[1] == "n") {
375 | var capture = k.indexOf("Capture") != -1;
376 | var eventKey = k.substring(2).toLowerCase();
377 | dom.removeEventListener(eventKey, ov, capture);
378 | dom.addEventListener(eventKey, v, capture);
379 | continue;
380 | }
381 |
382 | dom.setAttribute(k, v);
383 | }
384 | }
385 |
386 | function createNewDom(vnode, parent, comp, olddomOrComp, myIndex) {
387 | if (olddomOrComp) {
388 | recoveryComp(olddomOrComp);
389 | }
390 |
391 | var dom = document.createElement(vnode.nodeName);
392 |
393 | dom.__rendered = [];
394 | dom.__vnode = vnode;
395 | dom.__myIndex = myIndex; // 方便 getDOMIndex 方法
396 |
397 | if (comp) {
398 | comp.__rendered = dom;
399 | } else {
400 | parent.__rendered[myIndex] = dom;
401 | }
402 |
403 | setAttrs(dom, vnode.props);
404 |
405 | setNewDom(parent, dom, myIndex);
406 |
407 | for (var i = 0; i < vnode.children.length; i++) {
408 | renderInner(vnode.children[i], dom, null, null, i);
409 | }
410 | }
411 |
412 | function diffDOM(vnode, parent, comp, olddom) {
413 | var _diffObject = (0, _util.diffObject)(vnode.props, olddom.__vnode.props),
414 | onlyInLeft = _diffObject.onlyInLeft,
415 | bothIn = _diffObject.bothIn,
416 | onlyInRight = _diffObject.onlyInRight;
417 |
418 | setAttrs(olddom, onlyInLeft);
419 | removeAttrs(olddom, onlyInRight);
420 | diffAttrs(olddom, bothIn.left, bothIn.right);
421 |
422 | var willRemoveArr = olddom.__rendered.slice(vnode.children.length);
423 | var renderedArr = olddom.__rendered.slice(0, vnode.children.length);
424 | olddom.__rendered = renderedArr;
425 | for (var i = 0; i < vnode.children.length; i++) {
426 | renderInner(vnode.children[i], olddom, null, renderedArr[i], i);
427 | }
428 |
429 | willRemoveArr.forEach(function (element) {
430 | recoveryComp(element);
431 | olddom.removeChild((0, _util.getDOM)(element));
432 | });
433 |
434 | olddom.__vnode = vnode;
435 | }
436 |
437 | function recoveryComp(comp) {
438 | if (comp instanceof _Component2.default) {
439 | comp.componentWillUnmount && comp.componentWillUnmount();
440 | recoveryComp(comp.__rendered);
441 | } else if (comp.__rendered instanceof Array) {
442 | comp.__rendered.forEach(function (element) {
443 | recoveryComp(element);
444 | });
445 | } else {
446 | // do nothing
447 | }
448 | }
449 |
450 | /***/ }),
451 | /* 2 */
452 | /***/ (function(module, exports, __webpack_require__) {
453 |
454 | "use strict";
455 |
456 |
457 | Object.defineProperty(exports, "__esModule", {
458 | value: true
459 | });
460 | exports.diffObject = diffObject;
461 | exports.getDOM = getDOM;
462 | exports.getDOMIndex = getDOMIndex;
463 |
464 | var _Component = __webpack_require__(0);
465 |
466 | var _Component2 = _interopRequireDefault(_Component);
467 |
468 | function _interopRequireDefault(obj) {
469 | return obj && obj.__esModule ? obj : { default: obj };
470 | }
471 |
472 | function diffObject(leftProps, rightProps) {
473 | var onlyInLeft = {};
474 | var bothLeft = {};
475 | var bothRight = {};
476 | var onlyInRight = {};
477 |
478 | for (var key in leftProps) {
479 | if (rightProps[key] === undefined) {
480 | onlyInLeft[key] = leftProps[key];
481 | } else {
482 | bothLeft[key] = leftProps[key];
483 | bothRight[key] = rightProps[key];
484 | }
485 | }
486 |
487 | for (var _key in rightProps) {
488 | if (leftProps[_key] === undefined) {
489 | onlyInRight[_key] = rightProps[_key];
490 | }
491 | }
492 |
493 | return {
494 | onlyInRight: onlyInRight,
495 | onlyInLeft: onlyInLeft,
496 | bothIn: {
497 | left: bothLeft,
498 | right: bothRight
499 | }
500 | };
501 | } /**
502 | * Created by apple on 2017/8/30.
503 | */
504 | function getDOM(comp) {
505 | var rendered = comp.__rendered;
506 | while (rendered instanceof _Component2.default) {
507 | //判断对象是否是dom
508 | rendered = rendered.__rendered;
509 | }
510 | return rendered;
511 | }
512 |
513 | function getDOMIndex(dom) {
514 | return dom.__myIndex;
515 | }
516 |
517 | /***/ }),
518 | /* 3 */
519 | /***/ (function(module, exports, __webpack_require__) {
520 |
521 | "use strict";
522 |
523 |
524 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
525 |
526 | var _render = __webpack_require__(1);
527 |
528 | var _render2 = _interopRequireDefault(_render);
529 |
530 | var _Component2 = __webpack_require__(0);
531 |
532 | var _Component3 = _interopRequireDefault(_Component2);
533 |
534 | var _createElement = __webpack_require__(4);
535 |
536 | var _createElement2 = _interopRequireDefault(_createElement);
537 |
538 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
539 |
540 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
541 |
542 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
543 |
544 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
545 | * Created by apple on 2017/8/21.
546 | */
547 |
548 |
549 | var App = function (_Component) {
550 | _inherits(App, _Component);
551 |
552 | function App(props) {
553 | _classCallCheck(this, App);
554 |
555 | var _this = _possibleConstructorReturn(this, (App.__proto__ || Object.getPrototypeOf(App)).call(this, props));
556 |
557 | _this.state = {
558 | allTest: _this.getTest()
559 | };
560 |
561 | _this.start = 0;
562 | _this.end = 10000;
563 | return _this;
564 | }
565 |
566 | _createClass(App, [{
567 | key: 'getTest',
568 | value: function getTest() {
569 | var result = [];
570 | for (var i = 0; i < 10000; i++) {
571 | result.push((0, _createElement2.default)(
572 | 'div',
573 | { style: {
574 | width: '30px',
575 | color: 'red',
576 | fontSize: '12px',
577 | fontWeight: 600,
578 | height: '20px',
579 | textAlign: 'center',
580 | margin: '5px',
581 | padding: '5px',
582 | border: '1px solid red',
583 | position: 'relative'
584 | }, title: i },
585 | i
586 | ));
587 | }
588 | return result;
589 | }
590 | }, {
591 | key: 'getNowAllTest',
592 | value: function getNowAllTest() {
593 | this.start = this.start - 1;
594 | return [(0, _createElement2.default)(
595 | 'div',
596 | { style: {
597 | width: '30px',
598 | color: 'red',
599 | fontSize: '12px',
600 | fontWeight: 600,
601 | height: '20px',
602 | textAlign: 'center',
603 | margin: '5px',
604 | padding: '5px',
605 | border: '1px solid red',
606 | position: 'relative'
607 | }, title: this.start },
608 | this.start
609 | )].concat(this.state.allTest);
610 | }
611 | }, {
612 | key: 'getNowAllTest2',
613 | value: function getNowAllTest2() {
614 | this.end = this.end + 1;
615 | return this.state.allTest.concat([(0, _createElement2.default)(
616 | 'div',
617 | { style: {
618 | width: '30px',
619 | color: 'red',
620 | fontSize: '12px',
621 | fontWeight: 600,
622 | height: '20px',
623 | textAlign: 'center',
624 | margin: '5px',
625 | padding: '5px',
626 | border: '1px solid red',
627 | position: 'relative'
628 | }, title: this.end },
629 | this.end
630 | )]);
631 | }
632 | }, {
633 | key: 'render',
634 | value: function render() {
635 | var _this2 = this;
636 |
637 | return (0, _createElement2.default)(
638 | 'div',
639 | {
640 | width: 100 },
641 | (0, _createElement2.default)(
642 | 'a',
643 | { onClick: function onClick(e) {
644 | _this2.setState({
645 | allTest: _this2.getNowAllTest2()
646 | });
647 | } },
648 | 'click me'
649 | ),
650 | this.state.allTest
651 | );
652 | }
653 | }]);
654 |
655 | return App;
656 | }(_Component3.default);
657 |
658 | var startTime = new Date().getTime();
659 | (0, _render2.default)((0, _createElement2.default)(App, null), document.getElementById("root"));
660 | console.log("duration:", new Date().getTime() - startTime);
661 |
662 | /***/ }),
663 | /* 4 */
664 | /***/ (function(module, exports, __webpack_require__) {
665 |
666 | "use strict";
667 |
668 |
669 | Object.defineProperty(exports, "__esModule", {
670 | value: true
671 | });
672 | exports.default = createElement;
673 | /**
674 | * Created by apple on 2017/7/16.
675 | */
676 |
677 | /**
678 | *
679 | * @param comp func or div/p/span/..
680 | * @param props {}
681 | * @param children
682 | */
683 | function createElement(comp, props) {
684 | var children = [];
685 |
686 | for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
687 | args[_key - 2] = arguments[_key];
688 | }
689 |
690 | for (var i = 0; i < args.length; i++) {
691 | if (typeof args[i] === 'boolean' || args[i] === undefined || args === null) continue;
692 | if (args[i] instanceof Array) {
693 | children = children.concat(args[i]);
694 | } else {
695 | children.push(args[i]);
696 | }
697 | }
698 |
699 | return {
700 | nodeName: comp,
701 | props: props || {},
702 | children: children
703 | };
704 | }
705 |
706 | /***/ })
707 | /******/ ]);
--------------------------------------------------------------------------------
/example/yiwandiv/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/yiwandiv/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/8/21.
3 | */
4 | import render from '../../lib/render'
5 | import Component from '../../lib/Component'
6 | import createElement from '../../lib/createElement'
7 |
8 |
9 |
10 | class App extends Component {
11 | constructor(props) {
12 | super(props)
13 |
14 | this.state = {
15 | allTest: this.getTest()
16 | }
17 |
18 | this.start = 0
19 | this.end = 10000
20 | }
21 |
22 | getTest() {
23 | let result = []
24 | for(let i = 0; i < 10000; i++) {
25 | result.push({i}
)
37 | }
38 | return result
39 | }
40 |
41 | getNowAllTest() {
42 | this.start = this.start - 1
43 | return [{this.start}
].concat(this.state.allTest)
55 | }
56 |
57 | getNowAllTest2() {
58 | this.end = this.end + 1
59 | return this.state.allTest.concat([{this.end}
])
71 | }
72 |
73 | render() {
74 | return (
75 |
84 | )
85 | }
86 | }
87 |
88 | const startTime = new Date().getTime()
89 | render(, document.getElementById("root"))
90 | console.log("duration:", new Date().getTime() - startTime)
--------------------------------------------------------------------------------
/example/yiwandiv/innerDiffNode.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/9/6.
3 | */
4 | function innerDiffNode(dom, vchildren, context, mountAll, isHydrating) {
5 | let originalChildren = dom.childNodes,
6 | children = [],
7 | keyed = {},
8 | keyedLen = 0,
9 | min = 0,
10 | len = originalChildren.length,
11 | childrenLen = 0,
12 | vlen = vchildren ? vchildren.length : 0,
13 | j, c, vchild, child;
14 |
15 | // Build up a map of keyed children and an Array of unkeyed children:
16 | if (len!==0) {
17 | for (let i=0; i=len) {
63 | dom.appendChild(child);
64 | }
65 | else if (child!==originalChildren[i]) {
66 | if (child===originalChildren[i+1]) {
67 | removeNode(originalChildren[i]);
68 | }
69 | else {
70 | dom.insertBefore(child, originalChildren[i] || null);
71 | }
72 | }
73 | }
74 | }
75 | }
76 |
77 |
78 | // remove unused keyed children:
79 | if (keyedLen) {
80 | for (let i in keyed) if (keyed[i]!==undefined) recollectNodeTree(keyed[i], false);
81 | }
82 |
83 | // remove orphaned unkeyed children:
84 | while (min<=childrenLen) {
85 | if ((child = children[childrenLen--])!==undefined) recollectNodeTree(child, false);
86 | }
87 | }
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/example/yiwandiv/myReorder.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/9/6.
3 | */
4 |
5 | function getKeyObjMap(arr) {
6 | const result = {}
7 | arr.forEach(element => {
8 | result[element.key] = element
9 | })
10 | return result
11 | }
12 |
13 | function myReorder(oldList, newList) {
14 | var oldMap = getKeyObjMap(oldList)
15 |
16 | newList.forEach((element, index) => {
17 | if(oldMap[element.key] === undefined) {
18 | //oldList.splice(index, 0, element)
19 | insertBefore(oldList, element, oldList[index])
20 | } else {
21 | if (element.key !== oldList[index].key) {
22 | let ov = oldMap[element.key]
23 | insertBefore(oldList, ov, oldList[index])
24 | }
25 | }
26 | })
27 |
28 | while (newList.length != oldList.length) {
29 | remove(oldList, oldList[oldList.length -1])
30 | }
31 | }
32 |
33 |
34 | let removeCount = 0
35 | function remove(oldList, item) {
36 | removeCount ++
37 | let iIndex
38 | oldList.forEach((element, index) => {
39 | if(element.key === item.key) {
40 | iIndex = index
41 | }
42 | })
43 | oldList.splice(iIndex, 1)
44 | }
45 |
46 |
47 | let insertBeforeCount = 0
48 | function insertBefore(oldList, item, target) {
49 | insertBeforeCount ++
50 |
51 | if (target === undefined) {
52 | oldList.push(item)
53 | } else {
54 | let iIndex
55 | oldList.forEach((element, index) => {
56 | if(element.key === item.key) {
57 | iIndex = index
58 | }
59 | })
60 | if(iIndex === undefined) {
61 | let tIndex
62 | oldList.forEach((element, index) => {
63 | if(element.key === target.key) {
64 | tIndex = index
65 | }
66 | })
67 | oldList.splice(tIndex, 0, item)
68 | } else {
69 | oldList.splice(iIndex, 1)
70 |
71 | let tIndex
72 | oldList.forEach((element, index) => {
73 | if(element.key === target.key) {
74 | tIndex = index
75 | }
76 | })
77 | oldList.splice(tIndex, 0, item)
78 | }
79 |
80 | }
81 | }
82 |
83 | function equalArr(oldList, newList) {
84 | for (let i = 0; i < oldList.length; i++) {
85 | if (oldList[i].key !== newList[i].key) {
86 | return false
87 | }
88 | }
89 | return true
90 | }
91 |
92 |
93 | var oldList = [
94 | {key: 'a'},
95 | {key: 'b'},
96 | {key: 'c'},
97 | {key: 'd'},
98 | {key: 'h'},
99 | {key: 'u1'},
100 | {key: 'u2'},
101 | {key: 'u3'},
102 | {key: 'u4'},
103 | ]
104 | var newList = [
105 | {key: 'y'},
106 | {key: 'x'},
107 | {key: 'a'},
108 | {key: 'b'},
109 | {key: 'c'},
110 | {key: 'd'},
111 | {key: 'h'}
112 | ]
113 |
114 | myReorder2(oldList, newList)
115 | console.log("mr:", equalArr(oldList, newList), insertBeforeCount, removeCount)
--------------------------------------------------------------------------------
/example/yiwandiv/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "renderVDOM",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "./index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack --config ./webpack.config.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "babel-core": "~6.25.0",
14 | "babel-loader": "~7.1.1",
15 | "babel-plugin-transform-react-jsx": "^6.24.1",
16 | "babel-preset-es2015": "^6.24.1",
17 | "webpack": "^3.0.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/yiwandiv/reorder.js:
--------------------------------------------------------------------------------
1 | function diff (oldList, newList, key) {
2 | var oldMap = makeKeyIndexAndFree(oldList, key)
3 | var newMap = makeKeyIndexAndFree(newList, key)
4 |
5 | var newFree = newMap.free
6 |
7 | var oldKeyIndex = oldMap.keyIndex
8 | var newKeyIndex = newMap.keyIndex
9 |
10 | var moves = []
11 |
12 | // a simulate list to manipulate
13 | var children = []
14 | var i = 0
15 | var item
16 | var itemKey
17 | var freeIndex = 0
18 |
19 | // fist pass to check item in old list: if it's removed or not
20 | while (i < oldList.length) {
21 | item = oldList[i]
22 | itemKey = getItemKey(item, key)
23 | if (itemKey) {
24 | if (!newKeyIndex.hasOwnProperty(itemKey)) {
25 | children.push(null)
26 | } else {
27 | var newItemIndex = newKeyIndex[itemKey]
28 | children.push(newList[newItemIndex])
29 | }
30 | } else {
31 | var freeItem = newFree[freeIndex++]
32 | children.push(freeItem || null)
33 | }
34 | i++
35 | }
36 |
37 | var simulateList = children.slice(0)
38 |
39 | // remove items no longer exist
40 | i = 0
41 | while (i < simulateList.length) {
42 | if (simulateList[i] === null) {
43 | remove(i)
44 | removeSimulate(i)
45 | } else {
46 | i++
47 | }
48 | }
49 |
50 | // i is cursor pointing to a item in new list
51 | // j is cursor pointing to a item in simulateList
52 | var j = i = 0
53 | while (i < newList.length) {
54 | item = newList[i]
55 | itemKey = getItemKey(item, key)
56 |
57 | var simulateItem = simulateList[j]
58 | var simulateItemKey = getItemKey(simulateItem, key)
59 |
60 | if (simulateItem) {
61 | if (itemKey === simulateItemKey) {
62 | j++
63 | } else {
64 | // new item, just inesrt it
65 | if (!oldKeyIndex.hasOwnProperty(itemKey)) {
66 | insert(i, item)
67 | } else {
68 | // if remove current simulateItem make item in right place
69 | // then just remove it
70 | var nextItemKey = getItemKey(simulateList[j + 1], key)
71 | if (nextItemKey === itemKey) {
72 | remove(i)
73 | removeSimulate(j)
74 | j++ // after removing, current j is right, just jump to next one
75 | } else {
76 | // else insert item
77 | insert(i, item)
78 | }
79 | }
80 | }
81 | } else {
82 | insert(i, item)
83 | }
84 |
85 | i++
86 | }
87 |
88 | function remove (index) {
89 | var move = {index: index, type: 0}
90 | moves.push(move)
91 | }
92 |
93 | function insert (index, item) {
94 | var move = {index: index, item: item, type: 1}
95 | moves.push(move)
96 | }
97 |
98 | function removeSimulate (index) {
99 | simulateList.splice(index, 1)
100 | }
101 |
102 | return {
103 | moves: moves,
104 | children: children
105 | }
106 | }
107 |
108 |
--------------------------------------------------------------------------------
/example/yiwandiv/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var rootPath = path.resolve(__dirname)
3 | module.exports = {
4 | entry: {
5 | index: './index.js'
6 | },
7 | output: {
8 | path: rootPath + '/build',
9 | filename: '[name].js'
10 | },
11 | module: {
12 | loaders: [
13 | {
14 | test: /\.jsx?$/,
15 | loader: 'babel-loader'
16 | }
17 | ]
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lib/Component.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
8 | * Created by apple on 2017/7/20.
9 | */
10 |
11 |
12 | var _render = require('./render');
13 |
14 | var _util = require('./util');
15 |
16 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
17 |
18 | var Component = function () {
19 | function Component(props) {
20 | _classCallCheck(this, Component);
21 |
22 | this.props = props;
23 | }
24 |
25 | _createClass(Component, [{
26 | key: 'setState',
27 | value: function setState(state) {
28 | var _this = this;
29 |
30 | setTimeout(function () {
31 | var shoudUpdate = void 0;
32 | if (_this.shouldComponentUpdate) {
33 | shoudUpdate = _this.shouldComponentUpdate(_this.props, state);
34 | } else {
35 | shoudUpdate = true;
36 | }
37 |
38 | shoudUpdate && _this.componentWillUpdate && _this.componentWillUpdate(_this.props, state);
39 | _this.state = Object.assign(_this.state, state);
40 |
41 | if (!shoudUpdate) {
42 | return; // do nothing just return
43 | }
44 |
45 | var vnode = _this.render();
46 | var olddom = (0, _util.getDOM)(_this);
47 | var myIndex = (0, _util.getDOMIndex)(olddom);
48 | (0, _render.renderInner)(vnode, olddom.parentNode, _this, _this.__rendered, myIndex);
49 | _this.componentDidUpdate && _this.componentDidUpdate();
50 | }, 0);
51 | }
52 | }]);
53 |
54 | return Component;
55 | }();
56 |
57 | exports.default = Component;
58 |
59 |
60 | var a = {
61 | "nodeName": "div",
62 | "props": {},
63 | "children": ["i", { "nodeName": "div", "props": {}, "children": ["am"] }, {
64 | "nodeName": "div",
65 | "props": {},
66 | "children": ["grandson"]
67 | }]
68 | };
--------------------------------------------------------------------------------
/lib/RenderedHelper.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
8 |
9 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
10 |
11 | var RenderedHelper = function () {
12 | function RenderedHelper(arr) {
13 | _classCallCheck(this, RenderedHelper);
14 |
15 | this.__arr = arr || [];
16 | }
17 |
18 | _createClass(RenderedHelper, [{
19 | key: "replaceNullPush",
20 | value: function replaceNullPush(now, old) {
21 | if (!old) {
22 | now.__renderedHelperTag = "" + this.__arr.length;
23 | this.__arr.push(now);
24 | } else {
25 | if (this.__arr[old.__renderedHelperTag] === old) {
26 | now.__renderedHelperTag = old.__renderedHelperTag;
27 | this.__arr[now.__renderedHelperTag] = now;
28 | } else {
29 | now.__renderedHelperTag = "" + this.__arr.length;
30 | this.__arr.push(now);
31 | }
32 | }
33 | }
34 | }, {
35 | key: "slice",
36 | value: function slice(start, end) {
37 | return this.__arr.slice(start, end);
38 | }
39 | }, {
40 | key: "getInnerArr",
41 | value: function getInnerArr() {
42 | return this.__arr;
43 | }
44 | }]);
45 |
46 | return RenderedHelper;
47 | }();
48 |
49 | exports.default = RenderedHelper;
--------------------------------------------------------------------------------
/lib/createElement.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.default = createElement;
7 | /**
8 | * Created by apple on 2017/7/16.
9 | */
10 |
11 | /**
12 | *
13 | * @param comp func or div/p/span/..
14 | * @param props {}
15 | * @param children
16 | */
17 | function createElement(comp, props) {
18 | var children = [];
19 |
20 | for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
21 | args[_key - 2] = arguments[_key];
22 | }
23 |
24 | for (var i = 0; i < args.length; i++) {
25 | if (typeof args[i] === 'boolean' || args[i] === undefined || args === null) continue;
26 | if (args[i] instanceof Array) {
27 | children = children.concat(args[i]);
28 | } else {
29 | children.push(args[i]);
30 | }
31 | }
32 |
33 | return {
34 | nodeName: comp,
35 | props: props || {},
36 | children: children
37 | };
38 | }
--------------------------------------------------------------------------------
/lib/events.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.init = init;
7 | var supportEventType = ['keydown', 'keypress', 'keyup', 'click', 'mouseenter', 'mouseover', 'mousemove', 'change'];
8 |
9 | function getSyntheticEvent(e) {
10 | e.stopPropagation = function () {
11 | e.__notup = true; // 不冒泡
12 | };
13 | return e;
14 | }
15 |
16 | function getRelatedDOMList(e) {
17 | var path = e.path || e.deepPath;
18 | if (path) return path;
19 |
20 | var result = [];
21 |
22 | var node = e.target;
23 | while (node !== window.document) {
24 |
25 | result.push(node);
26 | node = node.parentNode;
27 | }
28 |
29 | return result;
30 | }
31 |
32 | function superHandle(e) {
33 |
34 | var syntheticEvent = getSyntheticEvent(e);
35 |
36 | var domList = getRelatedDOMList(e);
37 | console.log('domList:', domList);
38 |
39 | var eventType = e.type;
40 |
41 | for (var i = domList.length - 1; i >= 0; i--) {
42 | // 捕获期
43 | var eleDom = domList[i];
44 |
45 | if (!eleDom.__events) break;
46 |
47 | var handle = eleDom.__events[eventType + 'Capture'];
48 | handle && handle(syntheticEvent);
49 | }
50 |
51 | for (var _i = 0; _i < domList.length; _i++) {
52 | // 冒泡期
53 | var _eleDom = domList[_i];
54 |
55 | if (!_eleDom.__events) break;
56 |
57 | var _handle = _eleDom.__events[eventType];
58 | _handle && _handle(syntheticEvent);
59 | if (syntheticEvent.__notup) {
60 | break;
61 | }
62 | }
63 | }
64 |
65 | function init() {
66 | supportEventType.forEach(function (eventType) {
67 | document.addEventListener(eventType, superHandle);
68 | });
69 | }
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.createElement = exports.Component = undefined;
7 |
8 | var _Component = require('./Component');
9 |
10 | var _Component2 = _interopRequireDefault(_Component);
11 |
12 | var _render = require('./render');
13 |
14 | var _render2 = _interopRequireDefault(_render);
15 |
16 | var _createElement = require('./createElement');
17 |
18 | var _createElement2 = _interopRequireDefault(_createElement);
19 |
20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21 |
22 | exports.default = {
23 | Component: _Component2.default,
24 | createElement: _createElement2.default,
25 | render: _render2.default
26 | }; /**
27 | * Created by apple on 2017/8/21.
28 | */
29 |
30 | exports.Component = _Component2.default;
31 | exports.createElement = _createElement2.default;
--------------------------------------------------------------------------------
/lib/render.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /**
8 | * Created by apple on 2017/8/21.
9 | */
10 |
11 |
12 | exports.default = render;
13 | exports.renderInner = renderInner;
14 |
15 | var _util = require('./util');
16 |
17 | var _Component = require('./Component');
18 |
19 | var _Component2 = _interopRequireDefault(_Component);
20 |
21 | var _events = require('./events');
22 |
23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
24 |
25 | /**
26 | * 渲染vnode成实际的dom
27 | * @param vnode 虚拟dom表示
28 | * @param parent 实际渲染出来的dom,挂载的父元素
29 | */
30 | function render(vnode, parent) {
31 | parent.__rendered = [];
32 |
33 | //events init
34 | (0, _events.init)();
35 |
36 | renderInner(vnode, parent, null, null, 0);
37 | }
38 |
39 | /**
40 | * 渲染vnode成实际的dom
41 | * @param vnode 虚拟dom表示
42 | * @param parent 实际渲染出来的dom,挂载的父元素
43 | * @param comp 谁渲染了我
44 | * @param olddomOrComp 老的dom/组件实例
45 | * @param myIndex 在dom节点的位置
46 | */
47 | function renderInner(vnode, parent, comp, olddomOrComp, myIndex) {
48 | var dom = void 0;
49 | if (typeof vnode === "string" || typeof vnode === "number") {
50 | if (olddomOrComp && olddomOrComp.splitText) {
51 | if (olddomOrComp.nodeValue !== vnode) {
52 | olddomOrComp.nodeValue = vnode;
53 | }
54 | } else {
55 | if (olddomOrComp) {
56 | recoveryComp(olddomOrComp);
57 | }
58 |
59 | dom = document.createTextNode(vnode);
60 | parent.__rendered[myIndex] = dom; //comp 一定是null
61 |
62 | setNewDom(parent, dom, myIndex);
63 | }
64 | } else if (typeof vnode.nodeName === "string") {
65 | if (!olddomOrComp || olddomOrComp.nodeName !== vnode.nodeName.toUpperCase()) {
66 | createNewDom(vnode, parent, comp, olddomOrComp, myIndex);
67 | } else {
68 | diffDOM(vnode, parent, comp, olddomOrComp, myIndex);
69 | }
70 | } else if (typeof vnode.nodeName === "function") {
71 | var func = vnode.nodeName;
72 | var inst = void 0;
73 | if (olddomOrComp && olddomOrComp instanceof func) {
74 | inst = olddomOrComp;
75 | inst.componentWillReceiveProps && inst.componentWillReceiveProps(vnode.props);
76 |
77 | var shoudUpdate = void 0;
78 | if (inst.shouldComponentUpdate) {
79 | shoudUpdate = inst.shouldComponentUpdate(vnode.props, olddomOrComp.state);
80 | } else {
81 | shoudUpdate = true;
82 | }
83 |
84 | shoudUpdate && inst.componentWillUpdate && inst.componentWillUpdate(vnode.props, olddomOrComp.state);
85 | inst.props = vnode.props;
86 |
87 | if (!shoudUpdate) {
88 | return; // do nothing just return
89 | }
90 | } else {
91 | if (olddomOrComp) {
92 | recoveryComp(olddomOrComp);
93 | }
94 |
95 | inst = new func(vnode.props);
96 | inst.componentWillMount && inst.componentWillMount();
97 |
98 | if (comp) {
99 | comp.__rendered = inst;
100 | } else {
101 | parent.__rendered[myIndex] = inst;
102 | }
103 | }
104 |
105 | var innerVnode = inst.render();
106 | renderInner(innerVnode, parent, inst, inst.__rendered, myIndex);
107 |
108 | if (olddomOrComp && olddomOrComp instanceof func) {
109 | inst.componentDidUpdate && inst.componentDidUpdate();
110 | } else {
111 | inst.componentDidMount && inst.componentDidMount();
112 | }
113 | }
114 | }
115 |
116 | /**
117 | * 替换新的Dom, 如果没有在最后插入
118 | * @param parent
119 | * @param newDom
120 | * @param myIndex
121 | */
122 | function setNewDom(parent, newDom, myIndex) {
123 | var old = parent.childNodes[myIndex];
124 | if (old) {
125 | parent.replaceChild(newDom, old);
126 | } else {
127 | parent.appendChild(newDom);
128 | }
129 | }
130 |
131 | function setAttrs(dom, props) {
132 | var allKeys = Object.keys(props);
133 | allKeys.forEach(function (k) {
134 | var v = props[k];
135 |
136 | if (k == "className") {
137 | dom.setAttribute("class", v);
138 | return;
139 | }
140 |
141 | if (k == "value") {
142 | dom.value = v;
143 | return;
144 | }
145 |
146 | if (k == "style") {
147 | if (typeof v == "string") {
148 | dom.style.cssText = v; //IE
149 | }
150 |
151 | if ((typeof v === 'undefined' ? 'undefined' : _typeof(v)) == "object") {
152 | for (var i in v) {
153 | dom.style[i] = v[i];
154 | }
155 | }
156 | return;
157 | }
158 |
159 | if (k[0] == "o" && k[1] == "n") {
160 |
161 | var key = k.substring(2, 3).toLowerCase() + k.substring(3);
162 | dom.__events[key] = v;
163 | return;
164 | }
165 |
166 | dom.setAttribute(k, v);
167 | });
168 | }
169 |
170 | function removeAttrs(dom, props) {
171 | for (var k in props) {
172 | if (k == "className") {
173 | dom.removeAttribute("class");
174 | continue;
175 | }
176 |
177 | if (k == "value") {
178 | dom.value = "";
179 | continue;
180 | }
181 |
182 | if (k == "style") {
183 | dom.style.cssText = ""; //IE
184 | continue;
185 | }
186 |
187 | if (k[0] == "o" && k[1] == "n") {
188 | var key = k.substring(2, 3).toLowerCase() + k.substring(3);
189 | dom.__events[key] = null;
190 | continue;
191 | }
192 |
193 | dom.removeAttribute(k);
194 | }
195 | }
196 |
197 | /**
198 | * 调用者保证newProps 与 oldProps 的keys是相同的
199 | * @param dom
200 | * @param newProps
201 | * @param oldProps
202 | */
203 | function diffAttrs(dom, newProps, oldProps) {
204 | for (var k in newProps) {
205 | var v = newProps[k];
206 | var ov = oldProps[k];
207 | if (v === ov) continue;
208 |
209 | if (k == "className") {
210 | dom.setAttribute("class", v);
211 | continue;
212 | }
213 |
214 | if (k == "value") {
215 | dom.value = v;
216 | continue;
217 | }
218 |
219 | if (k == "style") {
220 | if (typeof v == "string") {
221 | dom.style.cssText = v;
222 | } else if ((typeof v === 'undefined' ? 'undefined' : _typeof(v)) == "object" && (typeof ov === 'undefined' ? 'undefined' : _typeof(ov)) == "object") {
223 | for (var vk in v) {
224 | if (v[vk] !== ov[vk]) {
225 | dom.style[vk] = v[vk];
226 | }
227 | }
228 |
229 | for (var ovk in ov) {
230 | if (v[ovk] === undefined) {
231 | dom.style[ovk] = "";
232 | }
233 | }
234 | } else {
235 | //typeof v == "object" && typeof ov == "string"
236 | dom.style = {};
237 | for (var _vk in v) {
238 | dom.style[_vk] = v[_vk];
239 | }
240 | }
241 | continue;
242 | }
243 |
244 | if (k[0] == "o" && k[1] == "n") {
245 | var key = k.substring(2, 3).toLowerCase() + k.substring(3);
246 | dom.__events[key] = v;
247 | continue;
248 | }
249 |
250 | dom.setAttribute(k, v);
251 | }
252 | }
253 |
254 | function createNewDom(vnode, parent, comp, olddomOrComp, myIndex) {
255 | if (olddomOrComp) {
256 | recoveryComp(olddomOrComp);
257 | }
258 |
259 | var dom = document.createElement(vnode.nodeName);
260 |
261 | dom.__rendered = [];
262 | dom.__vnode = vnode;
263 | dom.__myIndex = myIndex; // 方便 getDOMIndex 方法
264 | dom.__events = {};
265 |
266 | if (comp) {
267 | comp.__rendered = dom;
268 | } else {
269 | parent.__rendered[myIndex] = dom;
270 | }
271 |
272 | setAttrs(dom, vnode.props);
273 |
274 | setNewDom(parent, dom, myIndex);
275 |
276 | for (var i = 0; i < vnode.children.length; i++) {
277 | renderInner(vnode.children[i], dom, null, null, i);
278 | }
279 | }
280 |
281 | function diffDOM(vnode, parent, comp, olddom) {
282 | var _diffObject = (0, _util.diffObject)(vnode.props, olddom.__vnode.props),
283 | onlyInLeft = _diffObject.onlyInLeft,
284 | bothIn = _diffObject.bothIn,
285 | onlyInRight = _diffObject.onlyInRight;
286 |
287 | setAttrs(olddom, onlyInLeft);
288 | removeAttrs(olddom, onlyInRight);
289 | diffAttrs(olddom, bothIn.left, bothIn.right);
290 |
291 | var willRemoveArr = olddom.__rendered.slice(vnode.children.length);
292 | var renderedArr = olddom.__rendered.slice(0, vnode.children.length);
293 | olddom.__rendered = renderedArr;
294 | for (var i = 0; i < vnode.children.length; i++) {
295 | renderInner(vnode.children[i], olddom, null, renderedArr[i], i);
296 | }
297 |
298 | willRemoveArr.forEach(function (element) {
299 | recoveryComp(element);
300 | olddom.removeChild((0, _util.getDOM)(element));
301 | });
302 |
303 | olddom.__vnode = vnode;
304 | }
305 |
306 | function recoveryComp(comp) {
307 | if (comp instanceof _Component2.default) {
308 | comp.componentWillUnmount && comp.componentWillUnmount();
309 | recoveryComp(comp.__rendered);
310 | } else if (comp.__rendered instanceof Array) {
311 | comp.__rendered.forEach(function (element) {
312 | recoveryComp(element);
313 | });
314 | } else {
315 | // do nothing
316 | }
317 | }
--------------------------------------------------------------------------------
/lib/renderVDOM.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.default = renderVDOM;
7 | /**
8 | * Created by apple on 2017/8/21.
9 | */
10 | function renderVDOM(vnode) {
11 | if (typeof vnode == "string") {
12 | return vnode;
13 | } else if (typeof vnode.nodeName == "string") {
14 | var result = {
15 | nodeName: vnode.nodeName,
16 | props: vnode.props,
17 | children: []
18 | };
19 | for (var i = 0; i < vnode.children.length; i++) {
20 | result.children.push(renderVDOM(vnode.children[i]));
21 | }
22 | return result;
23 | } else if (typeof vnode.nodeName == "function") {
24 | var func = vnode.nodeName;
25 | var inst = new func(vnode.props);
26 | var innerVnode = func.prototype.render.call(inst);
27 | return renderVDOM(innerVnode);
28 | }
29 | }
--------------------------------------------------------------------------------
/lib/renderWithChildReorder.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /**
8 | * Created by apple on 2017/9/6.
9 | */
10 |
11 |
12 | exports.default = render;
13 |
14 | var _util = require("./util");
15 |
16 | /**
17 | * 渲染vnode成实际的dom
18 | * @param vnode 虚拟dom表示
19 | * @param parent 实际渲染出来的dom,挂载的父元素
20 | * @param comp 谁渲染了我
21 | * @param olddom 老的dom
22 | */
23 | function render(vnode, parent, comp, olddom, myIndex) {
24 | var dom = void 0;
25 | if (typeof vnode == "string" || typeof vnode == "number") {
26 | if (olddom && olddom.splitText) {
27 | if (olddom.nodeValue !== vnode) {
28 | olddom.nodeValue = vnode;
29 | }
30 | } else {
31 | dom = document.createTextNode(vnode);
32 | if (olddom) {
33 | parent.replaceChild(dom, olddom);
34 | } else {
35 | var target = typeof myIndex == "number" ? parent.childNodes[myIndex] : null;
36 | parent.insertBefore(dom, target);
37 | }
38 | }
39 | } else if (typeof vnode.nodeName == "string") {
40 | if (!olddom || olddom.nodeName != vnode.nodeName.toUpperCase()) {
41 | createNewDom(vnode, parent, comp, olddom, myIndex);
42 | } else {
43 | diffDOM(vnode, parent, comp, olddom);
44 | }
45 | } else if (typeof vnode.nodeName == "function") {
46 | var func = vnode.nodeName;
47 | var inst = new func(vnode.props);
48 |
49 | comp && (comp.__rendered = inst);
50 |
51 | var innerVnode = inst.render(inst);
52 | render(innerVnode, parent, inst, olddom);
53 | }
54 | }
55 |
56 | function setAttrs(dom, props) {
57 | var allKeys = Object.keys(props);
58 | allKeys.forEach(function (k) {
59 | var v = props[k];
60 |
61 | if (k == "className") {
62 | dom.setAttribute("class", v);
63 | return;
64 | }
65 |
66 | if (k == "style") {
67 | if (typeof v == "string") {
68 | dom.style.cssText = v; //IE
69 | }
70 |
71 | if ((typeof v === "undefined" ? "undefined" : _typeof(v)) == "object") {
72 | for (var i in v) {
73 | dom.style[i] = v[i];
74 | }
75 | }
76 | return;
77 | }
78 |
79 | if (k[0] == "o" && k[1] == "n") {
80 | var capture = k.indexOf("Capture") != -1;
81 | dom.addEventListener(k.substring(2).toLowerCase(), v, capture);
82 | return;
83 | }
84 |
85 | dom.setAttribute(k, v);
86 | });
87 | }
88 |
89 | function removeAttrs(dom, props) {
90 | for (var k in props) {
91 | if (k == "className") {
92 | dom.removeAttribute("class");
93 | continue;
94 | }
95 |
96 | if (k == "style") {
97 | dom.style.cssText = ""; //IE
98 | continue;
99 | }
100 |
101 | if (k[0] == "o" && k[1] == "n") {
102 | var capture = k.indexOf("Capture") != -1;
103 | var v = props[k];
104 | dom.removeEventListener(k.substring(2).toLowerCase(), v, capture);
105 | continue;
106 | }
107 |
108 | dom.removeAttribute(k);
109 | }
110 | }
111 |
112 | /**
113 | * 调用者保证newProps 与 oldProps 的keys是相同的
114 | * @param dom
115 | * @param newProps
116 | * @param oldProps
117 | */
118 | function diffAttrs(dom, newProps, oldProps) {
119 | for (var k in newProps) {
120 | var v = newProps[k];
121 | var ov = oldProps[k];
122 | if (v === ov) continue;
123 |
124 | if (k == "className") {
125 | dom.setAttribute("class", v);
126 | continue;
127 | }
128 |
129 | if (k == "style") {
130 | if (typeof v == "string") {
131 | dom.style.cssText = v;
132 | } else if ((typeof v === "undefined" ? "undefined" : _typeof(v)) == "object" && (typeof ov === "undefined" ? "undefined" : _typeof(ov)) == "object") {
133 | for (var vk in v) {
134 | if (v[vk] !== ov[vk]) {
135 | dom.style[vk] = v[vk];
136 | }
137 | }
138 |
139 | for (var ovk in ov) {
140 | if (v[ovk] === undefined) {
141 | dom.style[ovk] = "";
142 | }
143 | }
144 | } else {
145 | //typeof v == "object" && typeof ov == "string"
146 | dom.style = {};
147 | for (var _vk in v) {
148 | dom.style[_vk] = v[_vk];
149 | }
150 | }
151 | continue;
152 | }
153 |
154 | if (k[0] == "o" && k[1] == "n") {
155 | var capture = k.indexOf("Capture") != -1;
156 | var eventKey = k.substring(2).toLowerCase();
157 | dom.removeEventListener(eventKey, ov, capture);
158 | dom.addEventListener(eventKey, v, capture);
159 | continue;
160 | }
161 |
162 | dom.setAttribute(k, v);
163 | }
164 | }
165 |
166 | function createNewDom(vnode, parent, comp, olddom, myIndex) {
167 | var dom = document.createElement(vnode.nodeName);
168 |
169 | dom.__vnode = vnode;
170 | comp && (comp.__rendered = dom);
171 | setAttrs(dom, vnode.props);
172 |
173 | if (olddom) {
174 | parent.replaceChild(dom, olddom);
175 | } else {
176 | var target = typeof myIndex == "number" ? parent.childNodes[myIndex] : null;
177 | parent.insertBefore(dom, target);
178 | }
179 |
180 | for (var i = 0; i < vnode.children.length; i++) {
181 | render(vnode.children[i], dom, null, null);
182 | }
183 | }
184 |
185 | function diffDOM(vnode, parent, comp, olddom) {
186 | var _diffObject = (0, _util.diffObject)(vnode.props, olddom.__vnode.props),
187 | onlyInLeft = _diffObject.onlyInLeft,
188 | bothIn = _diffObject.bothIn,
189 | onlyInRight = _diffObject.onlyInRight;
190 |
191 | setAttrs(olddom, onlyInLeft);
192 | removeAttrs(olddom, onlyInRight);
193 | diffAttrs(olddom, bothIn.left, bothIn.right);
194 |
195 | //reorder(olddom, vnode.props, olddom.__vnode.props)
196 | var olddomChild = olddom.firstChild;
197 | for (var i = 0; i < vnode.children.length; i++) {
198 | render(vnode.children[i], olddom, null, olddomChild);
199 | olddomChild = olddomChild && olddomChild.nextSibling;
200 | }
201 |
202 | while (olddomChild) {
203 | //删除多余的子节点
204 | var next = olddomChild.nextSibling;
205 | olddom.removeChild(olddomChild);
206 | olddomChild = next;
207 | }
208 | olddom.__vnode = vnode;
209 | }
210 |
211 | function myReorder(oldChildNodes, vchildrens, olddom) {
212 | var ocnKeyMap = {}; // key值和 child的映射, 方便根据key确定child
213 | for (var i = 0; i < oldChildNodes.length; i++) {
214 | var key = oldChildNodes[i].__vnode.props.key;
215 | ocnKeyMap[key] = oldChildNodes[i];
216 | }
217 |
218 | vchildrens.forEach(function (element, index) {
219 | var key = element.props.key;
220 | if (ocnKeyMap[key] === undefined) {
221 | // 如果之前不存在这个key值, 直接在对应位置 插入一个新的
222 | render(element, olddom, null, null, index);
223 | } else {
224 | // 如果可以复用, 则直接diff。 如果不可以复用, render内部的 replaceChild 会插入新的dom, 并且移除之前的
225 | var ocdom = ocnKeyMap[key];
226 | render(element, olddom, null, ocdom, index);
227 |
228 | if (key === oldList[index].key) {
229 | // 如果 新的key和老的key位置相同 donothing
230 | var _ocdom = ocnKeyMap[key];
231 | render(element, olddom, null, _ocdom, index); //尝试复用 oldchild
232 | } else {
233 | var _ocdom2 = ocnKeyMap[key];
234 | render(element, olddom, null, _ocdom2, index); //尝试复用 oldchild
235 | }
236 | }
237 | });
238 |
239 | //删除多余节点
240 | while (oldChildNodes.length !== vchildrens.length) {
241 | olddom.removeChild(olddom.lastChild);
242 | }
243 | }
--------------------------------------------------------------------------------
/lib/util.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.diffObject = diffObject;
7 | exports.getDOM = getDOM;
8 | exports.getDOMIndex = getDOMIndex;
9 |
10 | var _Component = require('./Component');
11 |
12 | var _Component2 = _interopRequireDefault(_Component);
13 |
14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15 |
16 | function diffObject(leftProps, rightProps) {
17 | var onlyInLeft = {};
18 | var bothLeft = {};
19 | var bothRight = {};
20 | var onlyInRight = {};
21 |
22 | for (var key in leftProps) {
23 | if (rightProps[key] === undefined) {
24 | onlyInLeft[key] = leftProps[key];
25 | } else {
26 | bothLeft[key] = leftProps[key];
27 | bothRight[key] = rightProps[key];
28 | }
29 | }
30 |
31 | for (var _key in rightProps) {
32 | if (leftProps[_key] === undefined) {
33 | onlyInRight[_key] = rightProps[_key];
34 | }
35 | }
36 |
37 | return {
38 | onlyInRight: onlyInRight,
39 | onlyInLeft: onlyInLeft,
40 | bothIn: {
41 | left: bothLeft,
42 | right: bothRight
43 | }
44 | };
45 | } /**
46 | * Created by apple on 2017/8/30.
47 | */
48 | function getDOM(comp) {
49 | var rendered = comp;
50 | while (rendered instanceof _Component2.default) {
51 | //判断对象是否是dom
52 | rendered = rendered.__rendered;
53 | }
54 | return rendered;
55 | }
56 |
57 | function getDOMIndex(dom) {
58 | return dom.__myIndex;
59 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tinyreact",
3 | "version": "1.0.5",
4 | "description": "a react-like library",
5 | "main": "./lib/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "genePic": "dot -Tpng -o hello.png tmp.dot",
9 | "compile": "babel src --out-dir lib"
10 | },
11 | "author": "yankang",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "babel-core": "~6.25.0",
15 | "babel-loader": "~7.1.1",
16 | "babel-preset-es2015": "^6.24.1",
17 | "webpack": "^3.0.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Component.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/7/20.
3 | */
4 | import { renderInner } from './render'
5 | import { getDOM, getDOMIndex } from './util'
6 |
7 | export default class Component {
8 | constructor(props) {
9 | this.props = props
10 | }
11 |
12 | setState(state) {
13 | setTimeout(() => {
14 | let shoudUpdate
15 | if(this.shouldComponentUpdate) {
16 | shoudUpdate = this.shouldComponentUpdate(this.props, state)
17 | } else {
18 | shoudUpdate = true
19 | }
20 |
21 | shoudUpdate && this.componentWillUpdate && this.componentWillUpdate(this.props, state)
22 | this.state = Object.assign(this.state, state)
23 |
24 | if (!shoudUpdate) {
25 | return // do nothing just return
26 | }
27 |
28 | const vnode = this.render()
29 | let olddom = getDOM(this)
30 | const myIndex = getDOMIndex(olddom)
31 | renderInner(vnode, olddom.parentNode, this, this.__rendered, myIndex)
32 | this.componentDidUpdate && this.componentDidUpdate()
33 | }, 0)
34 | }
35 | }
36 |
37 |
38 | var a = {
39 | "nodeName": "div",
40 | "props": {},
41 | "children": ["i", {"nodeName": "div", "props": {}, "children": ["am"]}, {
42 | "nodeName": "div",
43 | "props": {},
44 | "children": ["grandson"]
45 | }]
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/src/createElement.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/7/16.
3 | */
4 |
5 | /**
6 | *
7 | * @param comp func or div/p/span/..
8 | * @param props {}
9 | * @param children
10 | */
11 | export default function createElement(comp, props, ...args) {
12 | let children = []
13 | for(let i = 0; i< args.length;i++){
14 | if (typeof args[i] === 'boolean' || args[i] === undefined || args === null) continue
15 | if(args[i] instanceof Array) {
16 | children = children.concat(args[i])
17 | } else {
18 | children.push(args[i])
19 | }
20 | }
21 |
22 | return {
23 | nodeName: comp,
24 | props: props || {},
25 | children
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/events.js:
--------------------------------------------------------------------------------
1 | const supportEventType = ['keydown', 'keypress', 'keyup', 'click', 'mouseenter', 'mouseover', 'mousemove', 'change']
2 |
3 | function getSyntheticEvent(e) {
4 | e.stopPropagation = function () {
5 | e.__notup = true // 不冒泡
6 | }
7 | return e
8 | }
9 |
10 |
11 | function getRelatedDOMList(e) {
12 | const path = e.path || e.deepPath
13 | if(path) return path
14 |
15 | const result = []
16 |
17 | let node = e.target
18 | while (node !== window.document) {
19 |
20 | result.push(node)
21 | node = node.parentNode
22 | }
23 |
24 | return result
25 | }
26 |
27 |
28 | function superHandle(e) {
29 |
30 | const syntheticEvent = getSyntheticEvent(e)
31 |
32 | const domList = getRelatedDOMList(e)
33 |
34 | const eventType = e.type
35 |
36 | for(let i = domList.length - 1; i >= 0; i-- ) { // 捕获期
37 | const eleDom = domList[i]
38 |
39 | if(!eleDom.__events) break
40 |
41 | const handle = eleDom.__events[eventType + 'Capture']
42 | handle && handle(syntheticEvent)
43 | }
44 |
45 | for(let i = 0; i < domList.length; i++) { // 冒泡期
46 | const eleDom = domList[i]
47 |
48 | if(!eleDom.__events) break
49 |
50 | const handle = eleDom.__events[eventType]
51 | handle && handle(syntheticEvent)
52 | if (syntheticEvent.__notup) {
53 | break
54 | }
55 | }
56 | }
57 |
58 |
59 | export function init() {
60 | supportEventType.forEach(eventType => {
61 | document.addEventListener(eventType, superHandle)
62 | })
63 | }
64 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/8/21.
3 | */
4 | import Component from './Component'
5 | import render from './render'
6 | import createElement from './createElement'
7 |
8 | export default {
9 | Component,
10 | createElement,
11 | render
12 | }
13 |
14 | export {
15 | Component,
16 | createElement
17 | }
--------------------------------------------------------------------------------
/src/render.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/8/21.
3 | */
4 | import { diffObject, getDOM } from './util'
5 | import Component from './Component'
6 |
7 | import { init } from './events'
8 |
9 | /**
10 | * 渲染vnode成实际的dom
11 | * @param vnode 虚拟dom表示
12 | * @param parent 实际渲染出来的dom,挂载的父元素
13 | */
14 | export default function render(vnode, parent) {
15 | parent.__rendered =[]
16 |
17 | //events init
18 | init()
19 |
20 | renderInner(vnode, parent, null, null, 0)
21 | }
22 |
23 | /**
24 | * 渲染vnode成实际的dom
25 | * @param vnode 虚拟dom表示
26 | * @param parent 实际渲染出来的dom,挂载的父元素
27 | * @param comp 谁渲染了我
28 | * @param olddomOrComp 老的dom/组件实例
29 | * @param myIndex 在dom节点的位置
30 | */
31 | export function renderInner(vnode, parent, comp, olddomOrComp, myIndex) {
32 | let dom
33 | if(typeof vnode === "string" || typeof vnode === "number" ) {
34 | if(olddomOrComp && olddomOrComp.splitText) {
35 | if(olddomOrComp.nodeValue !== vnode) {
36 | olddomOrComp.nodeValue = vnode
37 | }
38 | } else {
39 | if(olddomOrComp) {
40 | recoveryComp(olddomOrComp)
41 | }
42 |
43 | dom = document.createTextNode(vnode)
44 | parent.__rendered[myIndex] = dom //comp 一定是null
45 |
46 | setNewDom(parent, dom, myIndex)
47 | }
48 | } else if(typeof vnode.nodeName === "string") {
49 | if(!olddomOrComp || olddomOrComp.nodeName !== vnode.nodeName.toUpperCase()) {
50 | createNewDom(vnode, parent, comp, olddomOrComp, myIndex)
51 | } else {
52 | diffDOM(vnode, parent, comp, olddomOrComp, myIndex)
53 | }
54 | } else if (typeof vnode.nodeName === "function") {
55 | let func = vnode.nodeName
56 | let inst
57 | if(olddomOrComp && olddomOrComp instanceof func) {
58 | inst = olddomOrComp
59 | inst.componentWillReceiveProps && inst.componentWillReceiveProps(vnode.props)
60 |
61 | let shoudUpdate
62 | if(inst.shouldComponentUpdate) {
63 | shoudUpdate = inst.shouldComponentUpdate(vnode.props, olddomOrComp.state)
64 | } else {
65 | shoudUpdate = true
66 | }
67 |
68 |
69 | shoudUpdate && inst.componentWillUpdate && inst.componentWillUpdate(vnode.props, olddomOrComp.state)
70 | inst.props = vnode.props
71 |
72 | if (!shoudUpdate) {
73 | return // do nothing just return
74 | }
75 | } else {
76 | if (olddomOrComp) {
77 | recoveryComp(olddomOrComp)
78 | }
79 |
80 | inst = new func(vnode.props)
81 | inst.componentWillMount && inst.componentWillMount()
82 |
83 |
84 | if (comp) {
85 | comp.__rendered = inst
86 | } else {
87 | parent.__rendered[myIndex] = inst
88 | }
89 | }
90 |
91 | let innerVnode = inst.render()
92 | renderInner(innerVnode, parent, inst, inst.__rendered, myIndex)
93 |
94 | if(olddomOrComp && olddomOrComp instanceof func) {
95 | inst.componentDidUpdate && inst.componentDidUpdate()
96 | } else {
97 | inst.componentDidMount && inst.componentDidMount()
98 | }
99 | }
100 | }
101 |
102 | /**
103 | * 替换新的Dom, 如果没有在最后插入
104 | * @param parent
105 | * @param newDom
106 | * @param myIndex
107 | */
108 | function setNewDom(parent, newDom, myIndex) {
109 | const old = parent.childNodes[myIndex]
110 | if (old) {
111 | parent.replaceChild(newDom, old)
112 | } else {
113 | parent.appendChild(newDom)
114 | }
115 | }
116 |
117 | function setAttrs(dom, props) {
118 | const allKeys = Object.keys(props)
119 | allKeys.forEach(k => {
120 | const v = props[k]
121 |
122 | if(k == "className") {
123 | dom.setAttribute("class", v)
124 | return
125 | }
126 |
127 | if(k == "value") {
128 | dom.value = v
129 | return
130 | }
131 |
132 | if(k == "style") {
133 | if(typeof v == "string") {
134 | dom.style.cssText = v //IE
135 | }
136 |
137 | if(typeof v == "object") {
138 | for (let i in v) {
139 | dom.style[i] = v[i]
140 | }
141 | }
142 | return
143 |
144 | }
145 |
146 | if(k[0] == "o" && k[1] == "n") {
147 |
148 | const key = k.substring(2, 3).toLowerCase() + k.substring(3)
149 | dom.__events[key] = v
150 | return
151 | }
152 |
153 | dom.setAttribute(k, v)
154 | })
155 | }
156 |
157 | function removeAttrs(dom, props) {
158 | for(let k in props) {
159 | if(k == "className") {
160 | dom.removeAttribute("class")
161 | continue
162 | }
163 |
164 | if(k == "value") {
165 | dom.value = ""
166 | continue
167 | }
168 |
169 | if(k == "style") {
170 | dom.style.cssText = "" //IE
171 | continue
172 | }
173 |
174 |
175 | if(k[0] == "o" && k[1] == "n") {
176 | const key = k.substring(2, 3).toLowerCase() + k.substring(3)
177 | dom.__events[key] = null
178 | continue
179 | }
180 |
181 | dom.removeAttribute(k)
182 | }
183 | }
184 |
185 | /**
186 | * 调用者保证newProps 与 oldProps 的keys是相同的
187 | * @param dom
188 | * @param newProps
189 | * @param oldProps
190 | */
191 | function diffAttrs(dom, newProps, oldProps) {
192 | for(let k in newProps) {
193 | let v = newProps[k]
194 | let ov = oldProps[k]
195 | if(v === ov) continue
196 |
197 | if(k == "className") {
198 | dom.setAttribute("class", v)
199 | continue
200 | }
201 |
202 | if(k == "value") {
203 | dom.value = v
204 | continue
205 | }
206 |
207 | if(k == "style") {
208 | if(typeof v == "string") {
209 | dom.style.cssText = v
210 | } else if( typeof v == "object" && typeof ov == "object") {
211 | for(let vk in v) {
212 | if(v[vk] !== ov[vk]) {
213 | dom.style[vk] = v[vk]
214 | }
215 | }
216 |
217 | for(let ovk in ov) {
218 | if(v[ovk] === undefined){
219 | dom.style[ovk] = ""
220 | }
221 | }
222 | } else { //typeof v == "object" && typeof ov == "string"
223 | dom.style = {}
224 | for(let vk in v) {
225 | dom.style[vk] = v[vk]
226 | }
227 | }
228 | continue
229 | }
230 |
231 | if(k[0] == "o" && k[1] == "n") {
232 | const key = k.substring(2, 3).toLowerCase() + k.substring(3)
233 | dom.__events[key] = v
234 | continue
235 | }
236 |
237 | dom.setAttribute(k, v)
238 | }
239 | }
240 |
241 | function createNewDom(vnode, parent, comp, olddomOrComp, myIndex) {
242 | if(olddomOrComp) {
243 | recoveryComp(olddomOrComp)
244 | }
245 |
246 | let dom = document.createElement(vnode.nodeName)
247 |
248 | dom.__rendered = []
249 | dom.__vnode = vnode
250 | dom.__myIndex = myIndex // 方便 getDOMIndex 方法
251 | dom.__events = {}
252 |
253 | if (comp) {
254 | comp.__rendered = dom
255 | } else {
256 | parent.__rendered[myIndex] = dom
257 | }
258 |
259 | setAttrs(dom, vnode.props)
260 |
261 | setNewDom(parent, dom, myIndex)
262 |
263 | for(let i = 0; i < vnode.children.length; i++) {
264 | renderInner(vnode.children[i], dom, null, null, i)
265 | }
266 | }
267 |
268 | function diffDOM(vnode, parent, comp, olddom) {
269 | const {onlyInLeft, bothIn, onlyInRight} = diffObject(vnode.props, olddom.__vnode.props)
270 | setAttrs(olddom, onlyInLeft)
271 | removeAttrs(olddom, onlyInRight)
272 | diffAttrs(olddom, bothIn.left, bothIn.right)
273 |
274 |
275 | const willRemoveArr = olddom.__rendered.slice(vnode.children.length)
276 | const renderedArr = olddom.__rendered.slice(0, vnode.children.length)
277 | olddom.__rendered = renderedArr
278 | for(let i = 0; i < vnode.children.length; i++) {
279 | renderInner(vnode.children[i], olddom, null, renderedArr[i], i)
280 | }
281 |
282 | willRemoveArr.forEach(element => {
283 | recoveryComp(element)
284 | olddom.removeChild(getDOM(element))
285 | })
286 |
287 | olddom.__vnode = vnode
288 | }
289 |
290 | function recoveryComp(comp) {
291 | if (comp instanceof Component) {
292 | comp.componentWillUnmount && comp.componentWillUnmount()
293 | recoveryComp(comp.__rendered)
294 | } else if (comp.__rendered instanceof Array) {
295 | comp.__rendered.forEach(element => {
296 | recoveryComp(element)
297 | })
298 | } else {
299 | // do nothing
300 | }
301 | }
302 |
--------------------------------------------------------------------------------
/src/renderVDOM.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/8/21.
3 | */
4 | export default function renderVDOM(vnode) {
5 | if(typeof vnode == "string") {
6 | return vnode
7 | } else if(typeof vnode.nodeName == "string") {
8 | let result = {
9 | nodeName: vnode.nodeName,
10 | props: vnode.props,
11 | children: []
12 | }
13 | for(let i = 0; i < vnode.children.length; i++) {
14 | result.children.push(renderVDOM(vnode.children[i]))
15 | }
16 | return result
17 | } else if (typeof vnode.nodeName == "function") {
18 | let func = vnode.nodeName
19 | let inst = new func(vnode.props)
20 | let innerVnode = func.prototype.render.call(inst)
21 | return renderVDOM(innerVnode)
22 | }
23 | }
--------------------------------------------------------------------------------
/src/renderWithChildReorder.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/9/6.
3 | */
4 | import { diffObject } from './util'
5 |
6 | /**
7 | * 渲染vnode成实际的dom
8 | * @param vnode 虚拟dom表示
9 | * @param parent 实际渲染出来的dom,挂载的父元素
10 | * @param comp 谁渲染了我
11 | * @param olddom 老的dom
12 | */
13 | export default function render(vnode, parent, comp, olddom, myIndex) {
14 | let dom
15 | if(typeof vnode == "string" || typeof vnode == "number") {
16 | if(olddom && olddom.splitText) {
17 | if(olddom.nodeValue !== vnode) {
18 | olddom.nodeValue = vnode
19 | }
20 | } else {
21 | dom = document.createTextNode(vnode)
22 | if(olddom) {
23 | parent.replaceChild(dom, olddom)
24 | } else {
25 | const target = typeof myIndex == "number" ? parent.childNodes[myIndex] : null
26 | parent.insertBefore(dom, target)
27 | }
28 | }
29 | } else if(typeof vnode.nodeName == "string") {
30 | if(!olddom || olddom.nodeName != vnode.nodeName.toUpperCase()) {
31 | createNewDom(vnode, parent, comp, olddom, myIndex)
32 | } else {
33 | diffDOM(vnode, parent, comp, olddom)
34 | }
35 | } else if (typeof vnode.nodeName == "function") {
36 | let func = vnode.nodeName
37 | let inst = new func(vnode.props)
38 |
39 | comp && (comp.__rendered = inst)
40 |
41 | let innerVnode = inst.render(inst)
42 | render(innerVnode, parent, inst, olddom)
43 | }
44 | }
45 |
46 | function setAttrs(dom, props) {
47 | const allKeys = Object.keys(props)
48 | allKeys.forEach(k => {
49 | const v = props[k]
50 |
51 | if(k == "className") {
52 | dom.setAttribute("class", v)
53 | return
54 | }
55 |
56 | if(k == "style") {
57 | if(typeof v == "string") {
58 | dom.style.cssText = v //IE
59 | }
60 |
61 | if(typeof v == "object") {
62 | for (let i in v) {
63 | dom.style[i] = v[i]
64 | }
65 | }
66 | return
67 |
68 | }
69 |
70 | if(k[0] == "o" && k[1] == "n") {
71 | const capture = (k.indexOf("Capture") != -1)
72 | dom.addEventListener(k.substring(2).toLowerCase(), v, capture)
73 | return
74 | }
75 |
76 | dom.setAttribute(k, v)
77 | })
78 | }
79 |
80 | function removeAttrs(dom, props) {
81 | for(let k in props) {
82 | if(k == "className") {
83 | dom.removeAttribute("class")
84 | continue
85 | }
86 |
87 | if(k == "style") {
88 | dom.style.cssText = "" //IE
89 | continue
90 | }
91 |
92 |
93 | if(k[0] == "o" && k[1] == "n") {
94 | const capture = (k.indexOf("Capture") != -1)
95 | const v = props[k]
96 | dom.removeEventListener(k.substring(2).toLowerCase(), v, capture)
97 | continue
98 | }
99 |
100 | dom.removeAttribute(k)
101 | }
102 | }
103 |
104 | /**
105 | * 调用者保证newProps 与 oldProps 的keys是相同的
106 | * @param dom
107 | * @param newProps
108 | * @param oldProps
109 | */
110 | function diffAttrs(dom, newProps, oldProps) {
111 | for(let k in newProps) {
112 | let v = newProps[k]
113 | let ov = oldProps[k]
114 | if(v === ov) continue
115 |
116 | if(k == "className") {
117 | dom.setAttribute("class", v)
118 | continue
119 | }
120 |
121 | if(k == "style") {
122 | if(typeof v == "string") {
123 | dom.style.cssText = v
124 | } else if( typeof v == "object" && typeof ov == "object") {
125 | for(let vk in v) {
126 | if(v[vk] !== ov[vk]) {
127 | dom.style[vk] = v[vk]
128 | }
129 | }
130 |
131 | for(let ovk in ov) {
132 | if(v[ovk] === undefined){
133 | dom.style[ovk] = ""
134 | }
135 | }
136 | } else { //typeof v == "object" && typeof ov == "string"
137 | dom.style = {}
138 | for(let vk in v) {
139 | dom.style[vk] = v[vk]
140 | }
141 | }
142 | continue
143 | }
144 |
145 | if(k[0] == "o" && k[1] == "n") {
146 | const capture = (k.indexOf("Capture") != -1)
147 | let eventKey = k.substring(2).toLowerCase()
148 | dom.removeEventListener(eventKey, ov, capture)
149 | dom.addEventListener(eventKey, v, capture)
150 | continue
151 | }
152 |
153 | dom.setAttribute(k, v)
154 | }
155 | }
156 |
157 | function createNewDom(vnode, parent, comp, olddom, myIndex) {
158 | let dom = document.createElement(vnode.nodeName)
159 |
160 | dom.__vnode = vnode
161 | comp && (comp.__rendered = dom)
162 | setAttrs(dom, vnode.props)
163 |
164 | if(olddom) {
165 | parent.replaceChild(dom, olddom)
166 | } else {
167 | const target = typeof myIndex == "number" ? parent.childNodes[myIndex] : null
168 | parent.insertBefore(dom, target)
169 | }
170 |
171 | for(let i = 0; i < vnode.children.length; i++) {
172 | render(vnode.children[i], dom, null, null)
173 | }
174 | }
175 |
176 | function diffDOM(vnode, parent, comp, olddom) {
177 | const {onlyInLeft, bothIn, onlyInRight} = diffObject(vnode.props, olddom.__vnode.props)
178 | setAttrs(olddom, onlyInLeft)
179 | removeAttrs(olddom, onlyInRight)
180 | diffAttrs(olddom, bothIn.left, bothIn.right)
181 |
182 |
183 | //reorder(olddom, vnode.props, olddom.__vnode.props)
184 | let olddomChild = olddom.firstChild
185 | for(let i = 0; i < vnode.children.length; i++) {
186 | render(vnode.children[i], olddom, null, olddomChild)
187 | olddomChild = olddomChild && olddomChild.nextSibling
188 | }
189 |
190 | while (olddomChild) { //删除多余的子节点
191 | let next = olddomChild.nextSibling
192 | olddom.removeChild(olddomChild)
193 | olddomChild = next
194 | }
195 | olddom.__vnode = vnode
196 | }
197 |
198 |
199 | function myReorder(oldChildNodes, vchildrens, olddom) {
200 | let ocnKeyMap = {} // key值和 child的映射, 方便根据key确定child
201 | for(let i = 0; i < oldChildNodes.length; i ++) {
202 | const key = oldChildNodes[i].__vnode.props.key
203 | ocnKeyMap[key] = oldChildNodes[i]
204 | }
205 |
206 | vchildrens.forEach((element, index) => {
207 | const key = element.props.key
208 | if(ocnKeyMap[key] === undefined) { // 如果之前不存在这个key值, 直接在对应位置 插入一个新的
209 | render(element, olddom, null, null, index)
210 | } else {
211 | // 如果可以复用, 则直接diff。 如果不可以复用, render内部的 replaceChild 会插入新的dom, 并且移除之前的
212 | let ocdom = ocnKeyMap[key]
213 | render(element, olddom, null, ocdom, index)
214 |
215 |
216 | if (key === oldList[index].key) {
217 | // 如果 新的key和老的key位置相同 donothing
218 | let ocdom = ocnKeyMap[key]
219 | render(element, olddom, null, ocdom, index) //尝试复用 oldchild
220 |
221 | } else {
222 | let ocdom = ocnKeyMap[key]
223 | render(element, olddom, null, ocdom, index) //尝试复用 oldchild
224 | }
225 | }
226 | })
227 |
228 | //删除多余节点
229 | while (oldChildNodes.length !== vchildrens.length) {
230 | olddom.removeChild(olddom.lastChild)
231 | }
232 | }
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by apple on 2017/8/30.
3 | */
4 | import Component from './Component'
5 | export function diffObject(leftProps, rightProps) {
6 | const onlyInLeft = {}
7 | const bothLeft = {}
8 | const bothRight = {}
9 | const onlyInRight = {}
10 |
11 | for(let key in leftProps) {
12 | if(rightProps[key] === undefined) {
13 | onlyInLeft[key] = leftProps[key]
14 | } else {
15 | bothLeft[key] = leftProps[key]
16 | bothRight[key] = rightProps[key]
17 | }
18 | }
19 |
20 | for(let key in rightProps) {
21 | if(leftProps[key] === undefined) {
22 | onlyInRight[key] = rightProps[key]
23 | }
24 | }
25 |
26 | return {
27 | onlyInRight,
28 | onlyInLeft,
29 | bothIn: {
30 | left: bothLeft,
31 | right: bothRight
32 | }
33 | }
34 | }
35 |
36 |
37 | export function getDOM(comp) {
38 | let rendered = comp
39 | while (rendered instanceof Component) { //判断对象是否是dom
40 | rendered = rendered.__rendered
41 | }
42 | return rendered
43 | }
44 |
45 |
46 | export function getDOMIndex(dom) {
47 | return dom.__myIndex
48 | }
--------------------------------------------------------------------------------
/test.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yk",
3 | "age": 18,
4 | "code": ["java", "javascript", "python"]
5 | }
--------------------------------------------------------------------------------