├── .babelrc ├── .gitignore ├── README.md ├── blog ├── __rendered.dot ├── __rendered.png ├── __rendered2.dot ├── __rendered3.dot ├── __rendered3.png ├── __rendered3_1.dot ├── __rendered3_1.png ├── __rendered3_2.dot ├── __rendered3_2.png ├── __rendered4.dot ├── __rendered4.png ├── 从0实现一个tiny react(一).md ├── 从0实现一个tiny react(三).md ├── 从0实现一个tiny react(二).md └── 从0实现一个tiny react(四).md ├── example ├── helloworld │ ├── .babelrc │ ├── build │ │ └── index.js │ ├── index.html │ ├── index.js │ ├── package.json │ └── webpack.config.js ├── lifecycle │ ├── .babelrc │ ├── ComplexComp.js │ ├── ComplexComp2.js │ ├── build │ │ └── index.js │ ├── index.html │ ├── index.js │ ├── package.json │ └── webpack.config.js ├── my-vdom │ ├── .babelrc │ ├── diff.js │ ├── index.html │ ├── index.js │ ├── package.json │ └── webpack.config.js ├── propsAndState │ ├── .babelrc │ ├── build │ │ └── index.js │ ├── index.html │ ├── index.js │ ├── package.json │ └── webpack.config.js ├── renderVDOM │ ├── .babelrc │ ├── build │ │ └── index.js │ ├── index.js │ ├── package.json │ └── webpack.config.js ├── todo │ ├── .babelrc │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── Footer.js │ │ ├── HeaderInput.js │ │ └── TaskList.js │ ├── build │ │ └── index.js │ ├── index.html │ ├── index.js │ ├── package.json │ └── webpack.config.js └── yiwandiv │ ├── .babelrc │ ├── build │ └── index.js │ ├── index.html │ ├── index.js │ ├── innerDiffNode.js │ ├── myReorder.js │ ├── package.json │ ├── reorder.js │ └── webpack.config.js ├── lib ├── Component.js ├── RenderedHelper.js ├── createElement.js ├── events.js ├── index.js ├── render.js ├── renderVDOM.js ├── renderWithChildReorder.js └── util.js ├── package.json ├── src ├── Component.js ├── createElement.js ├── events.js ├── index.js ├── render.js ├── renderVDOM.js ├── renderWithChildReorder.js └── util.js └── test.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | 4 | .idea 5 | *.iml 6 | 7 | **/node_modules 8 | **/build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tinyreact 2 | tinyreact is tiny, simple and clear react-like lib. It will help you understand react better. 3 | 4 | so first, let's play [todoList](https://ykforerlang.github.io/todo/index.html) written by tinyreact 5 | 6 | ### Install 7 | ``` 8 | npm install tinyreact --save 9 | ``` 10 | 11 | ### Getting Started 12 | let's write 'Hello World': 13 | 1. .babelrc 14 | ```jsx harmony 15 | { 16 | "presets": [ 17 | "es2015" 18 | ], 19 | "plugins": [ 20 | ["transform-react-jsx", { 21 | "pragma": "createElement"// default pragma is React.createElement, we should change! 22 | }] 23 | ] 24 | } 25 | ``` 26 | 2. npm install tinyreact --save 27 | 28 | 3. write your code like React 29 | ```jsx harmony 30 | import Tinyreact, { createElement, Component } from 'tinyreact' 31 | 32 | class HelloWorld extends Component { 33 | render() { 34 | return
Hello World
35 | } 36 | } 37 | 38 | Tinyreact.render(, document.getElementById("root")) 39 | ``` 40 | 41 | ### Feature 42 | * virtual-dom 43 | * life-cycle 44 | * SyntheticEvent 45 | 46 | ### TODO 47 | * batchUpdate 48 | * high-performance virtual-dom 49 | * fiber 50 | 51 | ### Example 52 | 1. [helloword](https://github.com/ykforerlang/tinyreact/tree/master/example/helloworld) 53 | 2. [lifecycle](https://github.com/ykforerlang/tinyreact/tree/master/example/lifecycle) 54 | 3. [yiwandiv](https://github.com/ykforerlang/tinyreact/tree/master/example/yiwandiv) 55 | 4. [TodoList](https://github.com/ykforerlang/todo) 56 | 57 | ### Blog 58 | 59 | 1. [从0实现一个tiny react(一)](https://github.com/ykforerlang/tinyreact/blob/master/blog/%E4%BB%8E0%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAtiny%20react(%E4%B8%80).md) 60 | 2. [从0实现一个tiny react(二) virtual-dom](https://github.com/ykforerlang/tinyreact/blob/master/blog/%E4%BB%8E0%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAtiny%20react(%E4%BA%8C).md) 61 | 3. [从0实现一个tiny react(三) 生命周期](https://github.com/ykforerlang/tinyreact/blob/master/blog/%E4%BB%8E0%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAtiny%20react(%E4%B8%89).md) 62 | -------------------------------------------------------------------------------- /blog/__rendered.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | Father -> Son 3 | Son -> Grandson 4 | Grandson -> div 5 | } -------------------------------------------------------------------------------- /blog/__rendered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykforerlang/tinyreact/7a9bb19b8cf2aa419048c5f7a37323c5d8bac200/blog/__rendered.png -------------------------------------------------------------------------------- /blog/__rendered2.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | Father -> Son 3 | Son -> Grandson 4 | Grandson -> div 5 | 6 | div -> Gs1 -> Gss1 -> Gsss1 7 | div -> Gs2 8 | div -> Gs3 9 | } -------------------------------------------------------------------------------- /blog/__rendered3.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | Father -> Son 3 | Son -> Grandson 4 | Grandson -> div 5 | 6 | div -> Grandsonson1 7 | div -> Grandsonson2 8 | div -> Grandsonson3 9 | 10 | Grandsonson1 -> Gsss1 -> Gssss1 -> a 11 | Grandsonson2 -> span 12 | span -> Gsss2_1 -> Gssss2_1 13 | span -> Gsss2_2 -> Gssss2_2 14 | Grandsonson3 -> p 15 | 16 | } -------------------------------------------------------------------------------- /blog/__rendered3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykforerlang/tinyreact/7a9bb19b8cf2aa419048c5f7a37323c5d8bac200/blog/__rendered3.png -------------------------------------------------------------------------------- /blog/__rendered3_1.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | Father -> Son[color= "red" label= "__rendered"] 3 | Son -> Grandson[color= "red" label= "__rendered"] 4 | Grandson -> div[color= "red" label= "__rendered"] 5 | 6 | div -> Grandsonson1 7 | div -> Grandsonson2 8 | div -> Grandsonson3 9 | 10 | Grandsonson1 -> Gsss1 -> Gssss1 -> a[color= "red" label= "__rendered"] 11 | Grandsonson2 -> span[color= "red" label= "__rendered"] 12 | span -> Gsss2_1 13 | Gsss2_1 -> Gssss2_1[color= "red" label= "__rendered"] 14 | span -> Gsss2_2 15 | Gsss2_2 -> Gssss2_2[color= "red" label= "__rendered"] 16 | Grandsonson3 -> p[color= "red" label= "__rendered"] 17 | } -------------------------------------------------------------------------------- /blog/__rendered3_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykforerlang/tinyreact/7a9bb19b8cf2aa419048c5f7a37323c5d8bac200/blog/__rendered3_1.png -------------------------------------------------------------------------------- /blog/__rendered3_2.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | Father -> Son[color= "red" label= "__rendered"] 3 | Son -> Grandson[color= "red" label= "__rendered"] 4 | Grandson -> div[color= "red" label= "__rendered"] 5 | 6 | div -> Grandsonson1[color= "red" label= "__rendered[0]"] 7 | div -> Grandsonson2[color= "red" label= "__rendered[1]"] 8 | div -> Grandsonson3[color= "red" label= "__rendered[2]"] 9 | 10 | Grandsonson1 -> Gsss1 -> Gssss1 -> a[color= "red" label= "__rendered"] 11 | Grandsonson2 -> span[color= "red" label= "__rendered"] 12 | span -> Gsss2_1[color= "red" label= "__rendered[0]"] 13 | Gsss2_1 -> Gssss2_1[color= "red" label= "__rendered"] 14 | span -> Gsss2_2[color= "red" label= "__rendered[1]"] 15 | Gsss2_2 -> Gssss2_2[color= "red" label= "__rendered"] 16 | Grandsonson3 -> p[color= "red" label= "__rendered"] 17 | } -------------------------------------------------------------------------------- /blog/__rendered3_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykforerlang/tinyreact/7a9bb19b8cf2aa419048c5f7a37323c5d8bac200/blog/__rendered3_2.png -------------------------------------------------------------------------------- /blog/__rendered4.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | Father -> Son1 3 | Father -> Son2 4 | } -------------------------------------------------------------------------------- /blog/__rendered4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykforerlang/tinyreact/7a9bb19b8cf2aa419048c5f7a37323c5d8bac200/blog/__rendered4.png -------------------------------------------------------------------------------- /blog/从0实现一个tiny react(一).md: -------------------------------------------------------------------------------- 1 | ## 从0实现一个tiny react(一) 2 | 学习一个库的最好的方法就是实现一个, 注: 实际react的代码可能相去甚远。 3 | ### 支持JSX 4 | react组件可以完全不用JSX, 用纯js来写。 JSX语法经过babel转化就是纯js代码, 譬如: 5 | ```javascript 6 | const hw =
Hello World
7 | 8 | const hw = React.createElement('div', null, "Hello World") 9 | ``` 10 | 这两种是等效的。 babel 通过[babylon](https://github.com/babel/babylon) 来把JSX转化为js 11 | 配置如下([transform-react-jsx](http://babeljs.io/docs/plugins/transform-react-jsx/)): 12 | ```json 13 | { 14 | "presets": [ 15 | "es2015" 16 | ], 17 | "plugins": [ 18 | ["transform-react-jsx", { 19 | "pragma": "createElement" // default pragma is React.createElement 20 | }] 21 | ] 22 | } 23 | ``` 24 | 所以对于react库本身的, 是不需要关心jsx语法的 25 | 26 | 27 | ### 渲染 28 | react 中virtual-dom的概念, 使用一个 js的结构vnode来描述DOM 节点。 然后, 从vnode渲染出DOM树。 29 | 这个 vnode由3个属性描述:nodeName(div, Son...), props, children(vnode 组成的数组), 所以 createElement的最简实现 30 | ```javascript 31 | function createElement(comp, props, ...args) { 32 | let children = [] 33 | for(let i = 0; i< args.length;i++){ 34 | if(args[i] instanceof Array) { 35 | children = children.concat(args[i]) 36 | } else { 37 | children.push(args[i]) 38 | } 39 | } 40 | return { 41 | nodeName: comp, 42 | props: props || {}, 43 | children 44 | } 45 | } 46 | ``` 47 | 从vnode 怎么渲染到dom? 先想一下我们在react里面书写下面的组件的时候 48 | ```javascript 49 | class Father extends Component { 50 | render() { 51 | return () // React.createElement(Son) --> {nodeName: Son, props:{}, children:[]} 52 | } 53 | } 54 | 55 | class Son extends Component { 56 | render() { 57 | return () // React.createElement(Grandson) --> {nodeName: Grandson, props:{}, children:[]} 58 | } 59 | } 60 | 61 | /** 62 | *React.createElement( 63 | * "div", 64 | * null, 65 | * "i", 66 | * React.createElement( 67 | * "div", 68 | * null, 69 | * "am" 70 | * ), 71 | * React.createElement(GrandText, null) 72 | * ); 73 | */ 74 | class Grandson extends Component { 75 | render() { 76 | return ( 77 |
78 | i 79 |
am
80 | 81 |
82 | ) 83 | } 84 | } 85 | 86 | class GrandText extends Component { 87 | render() { 88 | return ( 89 |
grandson
// React.createElement(Grandson) 90 | ) 91 | } 92 | } 93 | 94 | 95 | render(, document.getElementById('root')) 96 | ``` 97 | 在react里, 最终渲染出来的就是一个i am grandson。 98 | 渲染的过程就是: 渲染Father的Vnode -> 渲染Son的Vnode -> 渲染Grandson的Vnode -> 渲染div -> 渲染i -> 渲染
am
-> 渲染GrandText。 99 | 显然这是一个递归的过程:递归的中止条件是 渲染html标签。 100 | 1. 当 nodeName 是 html标签, 直接操作dom 101 | 2. 当 nodeName 是 react组件 递归操作 组件render返回的vnode 102 | 103 | 暂时先不考虑 dom操作, 只考虑这个递归方法, 代码如下: 104 | ```javascript 105 | function renderVDOM(vnode) { 106 | if(typeof vnode == "string") { // 字符串 "i an grandson" 107 | return vnode 108 | } else if(typeof vnode.nodeName == "string") { 109 | let result = { 110 | nodeName: vnode.nodeName, 111 | props: vnode.props, 112 | children: [] 113 | } 114 | for(let i = 0; i < vnode.children.length; i++) { 115 | result.children.push(renderVDOM(vnode.children[i])) 116 | } 117 | return result 118 | } else if (typeof vnode.nodeName == "function") { // 如果是function 119 | let func = vnode.nodeName 120 | let inst = new func(vnode.props) 121 | let innerVnode = inst.render() 122 | return renderVDOM(innerVnode) 123 | } 124 | 125 | ``` 126 | 执行上面的结构将返回 ([jsfiddle演示地址](http://jsfiddle.net/yankang/y9jwy5dr/))): 127 | ```json 128 | { 129 | "nodeName": "div", 130 | "props": {}, 131 | "children": ["i", {"nodeName": "div", "props": {}, "children": ["am"]}, { 132 | "nodeName": "div", 133 | "props": {}, 134 | "children": ["grandson"] 135 | }] 136 | } 137 | ``` 138 | 139 | 加入实际DOM操作, 代码如下: 140 | ```javascript 141 | function render(vnode, parent) { 142 | let dom 143 | if(typeof vnode == "string") { 144 | dom = document.createTextNode(vnode) 145 | parent.appendChild(dom) 146 | } else if(typeof vnode.nodeName == "string") { 147 | dom = document.createElement(vnode.nodeName) 148 | setAttrs(dom, vnode.props) 149 | parent.appendChild(dom) 150 | 151 | for(let i = 0; i < vnode.children.length; i++) { 152 | render(vnode.children[i], dom) 153 | } 154 | } else if (typeof vnode.nodeName == "function") { 155 | let func = vnode.nodeName 156 | 157 | let inst = new func(vnode.props) 158 | let innerVnode = inst.render() 159 | render(innerVnode, parent) 160 | } 161 | } 162 | function setAttrs(dom, props) { 163 | const allKeys = Object.keys(props) 164 | allKeys.forEach(k => { 165 | const v = props[k] 166 | 167 | if(k == "className") { 168 | dom.setAttribute("class", v) 169 | return 170 | } 171 | 172 | if(k == "style") { 173 | if(typeof v == "string") { 174 | dom.style.cssText = v 175 | } 176 | 177 | if(typeof v == "object") { 178 | for (let i in v) { 179 | dom.style[i] = v[i] 180 | } 181 | } 182 | return 183 | 184 | } 185 | 186 | if(k[0] == "o" && k[1] == "n") { 187 | const capture = (k.indexOf("Capture") != -1) 188 | dom.addEventListener(k.substring(2).toLowerCase(), v, capture) 189 | return 190 | } 191 | 192 | dom.setAttribute(k, v) 193 | }) 194 | } 195 | ``` 196 | 渲染实际Hello World([jsfiddle演示地址](http://jsfiddle.net/yankang/955u1xvt/)) 197 | 总结一下: 198 | 1. createElement 方法负责创建 vnode 199 | 2. render 方法负责根据生成的vnode, 渲染到实际的dom的一个递归方法 (由于组件 最终一定会render html的标签。 所以这个递归一定是能够正常返回的) 200 | * vnode是字符串的是, 创建textNode节点 201 | * 当vnode.nodeName是 字符串的时候, 创建dom节点, 根据props设置节点属性, 遍历render children 202 | * 当vnode.nodeName是 function的时候, 获取render方法的返回值 vnode', 执行render(vnode') 203 | 204 | ### props 和 state 205 | v = f(props, state)。 组件的渲染结果由 render方法, props, state共同决定,之前只是讨论了render, 现在引入 props, state。 206 | 207 | 对于props, 父组件传递过来, 不可变。 设置到属性上面。 由基类Component 设置props 208 | ```javascript 209 | class Component { 210 | constructor(props) { 211 | this.props = props 212 | } 213 | } 214 | ``` 215 | 216 | 对于 state, 在组件的生命期内是可以修改的,当调用组件的setState方法的时候, 其实就是重新渲染 用一个新DOM树替换老的DOM: 217 | `parent.replaceChild (newdom, olddom ) `, 218 | 比如当我在 GrandText 上调用setState。 就是父div 把GrandText渲染出来的dom 替换一下。 219 | 所以 220 | 1. 组件实例 必须有机制获取到 olddom 221 | 2. 同时 render方法的第二个参数是 parent。 组件实例必须有机制获取到 parentDOM 222 | 这2个问题其实是一个问题。 parent = olddom.parentNode, 所以 `olddom.parentNode.replaceChild (newdom, olddom )` 。 现在的关键就是获取到olddom, 223 | 这里采用的机制是 每个组件实例 记住 直接渲染出的组件实例/DOM(通过__rendered属性)。 下图: 224 | ![实例引用关系](__rendered.png) 225 | 代码实现: 226 | ```javascript 227 | function render (vnode, parent, comp) { 228 | let dom 229 | if(typeof vnode == "string") { 230 | const dom = ... // 创建文本节点 231 | comp && (comp.__rendered = dom) 232 | ... // other op 233 | } else if(typeof vnode.nodeName == "string") { 234 | const dom = ... // 创建 dom节点 235 | comp && (comp.__rendered = dom) 236 | ... // other op 237 | } else if (typeof vnode.nodeName == "function") { 238 | const inst = ... // 创建 组件实例 239 | comp && (comp.__rendered = inst) 240 | ... // other op 241 | } 242 | } 243 | ``` 244 | 其中 comp 参数代表 "我是被谁渲染的"。 获取olddom的代码实现: 245 | ```javascript 246 | function getDOM(comp) { 247 | let rendered = comp.__rendered 248 | while (rendered instanceof Component) { //判断对象是否是dom 249 | rendered = rendered.__rendered 250 | } 251 | return rendered 252 | } 253 | ``` 254 | 调用 setState 使用olddom替换老的dom 代码如下: 255 | ```javascript 256 | function render(vnode, parent, comp, olddom) { 257 | let dom 258 | if(typeof vnode == "string") { 259 | ... 260 | if(olddom) { 261 | parent.replaceChild(dom, olddom) 262 | } else { 263 | parent.appendChild(dom) 264 | } 265 | ... 266 | } else if(typeof vnode.nodeName == "string") { 267 | ... 268 | if(olddom) { 269 | parent.replaceChild(dom, olddom) 270 | } else { 271 | parent.appendChild(dom) 272 | } 273 | ... 274 | } else if (typeof vnode.nodeName == "function") { 275 | ... 276 | render(innerVnode, parent, inst, olddom) 277 | } 278 | } 279 | ``` 280 | 拼凑一下以上功能, 完整代码实现: 281 | ```javascript 282 | ///Component 283 | class Component { 284 | constructor(props) { 285 | this.props = props 286 | } 287 | 288 | setState(state) { 289 | setTimeout(() => { 290 | this.state = state 291 | const vnode = this.render() 292 | let olddom = getDOM(this) 293 | render(vnode, olddom.parentNode, this, olddom) 294 | }, 0) 295 | } 296 | } 297 | 298 | 299 | function getDOM(comp) { 300 | let rendered = comp.__rendered 301 | while (rendered instanceof Component) { //判断对象是否是dom 302 | rendered = rendered.__rendered 303 | } 304 | return rendered 305 | } 306 | 307 | ///render 308 | function render (vnode, parent, comp, olddom) { 309 | let dom 310 | if(typeof vnode == "string" || typeof vnode == "number") { 311 | dom = document.createTextNode(vnode) 312 | comp && (comp.__rendered = dom) 313 | parent.appendChild(dom) 314 | 315 | if(olddom) { 316 | parent.replaceChild(dom, olddom) 317 | } else { 318 | parent.appendChild(dom) 319 | } 320 | } else if(typeof vnode.nodeName == "string") { 321 | dom = document.createElement(vnode.nodeName) 322 | 323 | comp && (comp.__rendered = dom) 324 | setAttrs(dom, vnode.props) 325 | 326 | if(olddom) { 327 | parent.replaceChild(dom, olddom) 328 | } else { 329 | parent.appendChild(dom) 330 | } 331 | 332 | for(let i = 0; i < vnode.children.length; i++) { 333 | render(vnode.children[i], dom, null, null) 334 | } 335 | } else if (typeof vnode.nodeName == "function") { 336 | let func = vnode.nodeName 337 | let inst = new func(vnode.props) 338 | 339 | comp && (comp.__rendered = inst) 340 | 341 | let innerVnode = inst.render(inst) 342 | render(innerVnode, parent, inst, olddom) 343 | } 344 | } 345 | ``` 346 | [有状态组件 演示地址](http://jsfiddle.net/yankang/ufhf1fqx/), have fun! 347 | 348 | 总结一下: render方法负责把vnode渲染到实际的DOM, 如果组件渲染的DOM已经存在, 就替换, 并且保持一个 __rendered的引用链 349 | 350 | ### 其他 351 | 代码托管在[github](https://github.com/ykforerlang/tinyreact)。 觉得有帮助,点个star。哈哈哈。。。 352 | 本文所讲的代码部分在 propsAndState 这个tag上: 353 | ``` 354 | git clone https://github.com/ykforerlang/tinyreact.git 355 | git branch [yourbranchname] propsAndState 356 | ``` 357 | 358 | ### 相关文章 359 | * [从0实现一个tiny react(一)](https://segmentfault.com/a/1190000010822571) 360 | * [从0实现一个tiny react(二)](https://segmentfault.com/a/1190000011052656) 361 | * [从0实现一个tiny react(三)生命周期](https://segmentfault.com/a/1190000011156505) 362 | * [从0开始实现 react-router](https://segmentfault.com/a/1190000012696920) 363 | * [从0实现一个tinyredux](https://segmentfault.com/a/1190000011304634) 364 | * [从0实现一个tiny react-redux](https://segmentfault.com/a/1190000011633971) 365 | * [为什么我们需要reselect](https://segmentfault.com/a/1190000011936772) 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | -------------------------------------------------------------------------------- /blog/从0实现一个tiny react(三).md: -------------------------------------------------------------------------------- 1 | # 从0实现一个tiny react(三)生命周期 2 | 在给tinyreact加生命周期之前,先考虑 组件实例的复用 这个前置问题 3 | 4 | 5 | ### 复用组件实例 6 | render函数 只能返回一个根 7 | ```jsx harmony 8 | class A extends Component{ 9 | render() { 10 | return (...) 11 | } 12 | } 13 | class C extends Component { 14 | render() { 15 | return ( 16 |
17 | ... 18 | ... 19 | ... 20 |
21 | ) 22 | } 23 | } 24 | ``` 25 | 所以 最终的组件树一定是类似这种的 (首字母大写的代表组件, div/span/a...代表原生DOM类型) 26 | ![Fater_Tree](__rendered3_1.png) 27 | 28 | 是绝对不可能 出现下图这种树结构 (与render函数返回单根的特性矛盾) 29 | 30 | ![Error_Tree](__rendered4.png) 31 | 32 | 注意 __rendered引用 指向了一个inst/dom。 所以可以通过__rendered来复用实例。 33 |
下面我们讨论怎么根据__rendered 复用inst 34 | 35 | 假如在 Father里面调用 setState? 按照现在render 函数的做法: 36 | ```javascript 1.7 37 | else if (typeof vnode.nodeName == "function") { 38 | let func = vnode.nodeName 39 | let inst = new func(vnode.props) 40 | ... 41 | } 42 | ``` 43 | 44 | 1. 新建 Son 实例 45 | 2. 新建 Grandson 实例 46 | 3. diff 渲染 div 47 | 48 | 再次setState呢? 好吧, 再来一次: 49 | 1. 新建 Son 实例 50 | 2. 新建 Grandson 实例 51 | 3. diff 渲染 div 52 | 53 | 第 3步 就是 [(二)](https://segmentfault.com/a/1190000011052656) 讨论的内容, 会用"最少"的dom操作, 来更新dom到最新的状态。 54 | 对于1, 2 每次setState的时候都会新建inst, 在这里是可以复用之前创建好的inst实例的。 55 | 56 | 但是如果一个组件 初始渲染为 '\', setState 之后渲染为 '\' 这种情况呢? 那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 | ![Father_Tree](__rendered3_2.png) 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 |
11 |

p1

12 |

p2

13 |
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 | } --------------------------------------------------------------------------------
38 | { 39 | this.setState({}) 40 | }}>click me 41 | {this.testApp3()} 42 |