├── .gitignore ├── README.md ├── package.json ├── patch ├── .babelrc.js ├── dist │ ├── dong.js │ └── index.js ├── dong.js ├── index.html ├── index.js └── package.json ├── render-component ├── .babelrc.js ├── dist │ ├── dong.js │ └── index.js ├── dong.js ├── index.html ├── index.js └── package.json ├── render-fiber ├── .babelrc.js ├── README.md ├── dist │ ├── dong.js │ └── index.js ├── dong.js ├── index.html ├── index.js └── package.json ├── render-jsx ├── .babelrc.js ├── README.md ├── dist │ ├── dong.js │ └── index.js ├── dong.js ├── index.html ├── index.js └── package.json └── render-vdom ├── dong.js ├── index.html ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *-lock.json 3 | yarn-* 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # frontend-framework-exercize 2 | 3 | 4 | [实现 vdom 的渲染](./render-vdom) 5 | 6 | [jsx 的编译,然后渲染](./render-vdom) 7 | 8 | [组件渲染](./render-component) 9 | 10 | [fiber 版 react](./render-jsx) 11 | ## run 12 | ``` 13 | npx http-server . 14 | ``` 15 | 然后访问对应目录 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend-framework-exercize", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/QuarkGluonPlasma/frontend-framework-exercize.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/QuarkGluonPlasma/frontend-framework-exercize/issues" 18 | }, 19 | "homepage": "https://github.com/QuarkGluonPlasma/frontend-framework-exercize#readme", 20 | "devDependencies": { 21 | "@babel/cli": "^7.16.8", 22 | "@babel/core": "^7.16.10", 23 | "@babel/preset-react": "^7.16.7" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /patch/.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-react', 5 | { 6 | pragma: 'createElement' 7 | } 8 | ] 9 | ] 10 | } -------------------------------------------------------------------------------- /patch/dist/dong.js: -------------------------------------------------------------------------------- 1 | function isTextVdom(vdom) { 2 | return typeof vdom == 'string' || typeof vdom == 'number'; 3 | } 4 | 5 | function isElementVdom(vdom) { 6 | return typeof vdom == 'object' && typeof vdom.type == 'string'; 7 | } 8 | 9 | function isComponentVdom(vdom) { 10 | return typeof vdom.type == 'function'; 11 | } 12 | 13 | const render = (vdom, parent = null) => { 14 | const mount = parent ? el => parent.appendChild(el) : el => el; 15 | 16 | if (isTextVdom(vdom)) { 17 | return mount(document.createTextNode(vdom)); 18 | } else if (isElementVdom(vdom)) { 19 | const dom = mount(document.createElement(vdom.type)); 20 | 21 | for (const child of [].concat(...vdom.children)) { 22 | // children 元素也是 数组,要拍平 23 | render(child, dom); 24 | } 25 | 26 | for (const prop in vdom.props) { 27 | setAttribute(dom, prop, vdom.props[prop]); 28 | } 29 | 30 | return dom; 31 | } else if (isComponentVdom(vdom)) { 32 | return renderComponent(vdom, parent); 33 | } else { 34 | throw new Error(`Invalid VDOM: ${vdom}.`); 35 | } 36 | }; 37 | 38 | function renderComponent(vdom, parent) { 39 | const props = Object.assign({}, vdom.props, { 40 | children: vdom.children 41 | }); 42 | 43 | if (Component.isPrototypeOf(vdom.type)) { 44 | const instance = new vdom.type(props); 45 | instance.componentWillMount(); 46 | const componentVdom = instance.render(); 47 | instance.dom = render(componentVdom, parent); 48 | instance.dom.__instance = instance; 49 | instance.dom.__key = vdom.props.key; 50 | instance.componentDidMount(); 51 | return instance.dom; 52 | } else { 53 | const componentVdom = vdom.type(props); 54 | return render(componentVdom, parent); 55 | } 56 | } 57 | 58 | function patch(dom, vdom, parent = dom.parentNode) { 59 | const replace = parent ? el => { 60 | parent.replaceChild(el, dom); 61 | return el; 62 | } : el => el; 63 | 64 | if (isComponentVdom(vdom)) { 65 | const props = Object.assign({}, vdom.props, { 66 | children: vdom.children 67 | }); 68 | 69 | if (dom.__instance && dom.__instance.constructor == vdom.type) { 70 | dom.__instance.componentWillReceiveProps(props); 71 | 72 | dom.__instance.props = props; 73 | return patch(dom, dom.__instance.render(), parent); 74 | } else if (Component.isPrototypeOf(vdom.type)) { 75 | const componentDom = renderComponent(vdom, parent); 76 | 77 | if (parent) { 78 | parent.replaceChild(componentDom, dom); 79 | return componentDom; 80 | } else { 81 | return componentDom; 82 | } 83 | } else if (!Component.isPrototypeOf(vdom.type)) { 84 | return patch(dom, vdom.type(props), parent); 85 | } 86 | } else if (dom instanceof Text) { 87 | if (typeof vdom === 'object') { 88 | return replace(render(vdom, parent)); 89 | } else { 90 | return dom.textContent != vdom ? replace(render(vdom, parent)) : dom; 91 | } 92 | } else if (dom.nodeName !== vdom.type.toUpperCase() && typeof vdom === 'object') { 93 | return replace(render(vdom, parent)); 94 | } else if (dom.nodeName === vdom.type.toUpperCase() && typeof vdom === 'object') { 95 | const active = document.activeElement; 96 | const oldDoms = {}; 97 | [].concat(...dom.childNodes).map((child, index) => { 98 | const key = child.__key || `__index_${index}`; 99 | oldDoms[key] = child; 100 | }); 101 | [].concat(...vdom.children).map((child, index) => { 102 | const key = child.props && child.props.key || `__index_${index}`; 103 | dom.appendChild(oldDoms[key] ? patch(oldDoms[key], child) : render(child, dom)); 104 | delete oldDoms[key]; 105 | }); 106 | 107 | for (const key in oldDoms) { 108 | const instance = oldDoms[key].__instance; 109 | if (instance) instance.componentWillUnmount(); 110 | oldDoms[key].remove(); 111 | } 112 | 113 | for (const attr of dom.attributes) dom.removeAttribute(attr.name); 114 | 115 | for (const prop in vdom.props) setAttribute(dom, prop, vdom.props[prop]); 116 | 117 | active.focus(); 118 | return dom; 119 | } 120 | } 121 | 122 | function isEventListenerAttr(key, value) { 123 | return typeof value == 'function' && key.startsWith('on'); 124 | } 125 | 126 | function isStyleAttr(key, value) { 127 | return key == 'style' && typeof value == 'object'; 128 | } 129 | 130 | function isPlainAttr(key, value) { 131 | return typeof value != 'object' && typeof value != 'function'; 132 | } 133 | 134 | function isRefAttr(key, value) { 135 | return key === 'ref' && typeof value === 'function'; 136 | } 137 | 138 | const setAttribute = (dom, key, value) => { 139 | if (isEventListenerAttr(key, value)) { 140 | const eventType = key.slice(2).toLowerCase(); 141 | dom.__handlers = dom.__handlers || {}; 142 | dom.removeEventListener(eventType, dom.__handlers[eventType]); 143 | dom.__handlers[eventType] = value; 144 | dom.addEventListener(eventType, dom.__handlers[eventType]); 145 | } else if (key == 'checked' || key == 'value' || key == 'className') { 146 | dom[key] = value; 147 | } else if (isRefAttr(key, value)) { 148 | value(dom); 149 | } else if (isStyleAttr(key, value)) { 150 | Object.assign(dom.style, value); 151 | } else if (key == 'key') { 152 | dom.__key = value; 153 | } else if (isPlainAttr(key, value)) { 154 | dom.setAttribute(key, value); 155 | } 156 | }; 157 | 158 | const createElement = (type, props, ...children) => { 159 | if (props === null) props = {}; 160 | return { 161 | type, 162 | props, 163 | children 164 | }; 165 | }; 166 | 167 | class Component { 168 | constructor(props) { 169 | this.props = props || {}; 170 | this.state = null; 171 | } 172 | 173 | setState(nextState) { 174 | this.state = Object.assign(this.state, nextState); 175 | 176 | if (this.dom && this.shouldComponentUpdate(this.props, nextState)) { 177 | patch(this.dom, this.render()); 178 | } 179 | } 180 | 181 | shouldComponentUpdate(nextProps, nextState) { 182 | return nextProps != this.props || nextState != this.state; 183 | } 184 | 185 | componentWillMount() {} 186 | 187 | componentDidMount() {} 188 | 189 | componentWillReceiveProps() {} 190 | 191 | componentWillUnmount() {} 192 | 193 | } -------------------------------------------------------------------------------- /patch/dist/index.js: -------------------------------------------------------------------------------- 1 | function Item(props) { 2 | return createElement("li", { 3 | className: "item", 4 | style: props.style 5 | }, props.children, " ", createElement("a", { 6 | href: "#", 7 | onClick: props.onRemoveItem 8 | }, "X ")); 9 | } 10 | 11 | class List extends Component { 12 | constructor(props) { 13 | super(); 14 | this.state = { 15 | list: [{ 16 | text: 'aaa', 17 | color: 'pink' 18 | }, { 19 | text: 'bbb', 20 | color: 'orange' 21 | }, { 22 | text: 'ccc', 23 | color: 'yellow' 24 | }] 25 | }; 26 | } 27 | 28 | handleItemRemove(index) { 29 | this.setState({ 30 | list: this.state.list.filter((item, i) => i !== index) 31 | }); 32 | } 33 | 34 | handleAdd() { 35 | this.setState({ 36 | list: [...this.state.list, { 37 | text: this.ref.value 38 | }] 39 | }); 40 | } 41 | 42 | render() { 43 | return createElement("div", null, createElement("ul", { 44 | className: "list" 45 | }, this.state.list.map((item, index) => { 46 | return createElement(Item, { 47 | style: { 48 | background: item.color, 49 | color: this.state.textColor 50 | }, 51 | onRemoveItem: () => this.handleItemRemove(index) 52 | }, item.text); 53 | })), createElement("div", null, createElement("input", { 54 | ref: ele => { 55 | this.ref = ele; 56 | } 57 | }), createElement("button", { 58 | onClick: this.handleAdd.bind(this) 59 | }, "add"))); 60 | } 61 | 62 | } 63 | 64 | render(createElement(List, { 65 | textColor: '#000' 66 | }), document.getElementById('root')); -------------------------------------------------------------------------------- /patch/dong.js: -------------------------------------------------------------------------------- 1 | 2 | function isTextVdom(vdom) { 3 | return typeof vdom == 'string' || typeof vdom == 'number'; 4 | } 5 | 6 | function isElementVdom(vdom) { 7 | return typeof vdom == 'object' && typeof vdom.type == 'string'; 8 | } 9 | 10 | function isComponentVdom(vdom) { 11 | return typeof vdom.type == 'function'; 12 | } 13 | 14 | const render = (vdom, parent = null) => { 15 | const mount = parent ? (el => parent.appendChild(el)) : (el => el); 16 | if (isTextVdom(vdom)) { 17 | return mount(document.createTextNode(vdom)); 18 | } else if (isElementVdom(vdom)) { 19 | const dom = mount(document.createElement(vdom.type)); 20 | for (const child of [].concat(...vdom.children)) {// children 元素也是 数组,要拍平 21 | render(child, dom); 22 | } 23 | for (const prop in vdom.props) { 24 | setAttribute(dom, prop, vdom.props[prop]); 25 | } 26 | return dom; 27 | } else if (isComponentVdom(vdom)) { 28 | return renderComponent(vdom, parent); 29 | } else { 30 | throw new Error(`Invalid VDOM: ${vdom}.`); 31 | } 32 | }; 33 | 34 | function renderComponent(vdom, parent) { 35 | const props = Object.assign({}, vdom.props, { 36 | children: vdom.children 37 | }); 38 | 39 | if (Component.isPrototypeOf(vdom.type)) { 40 | const instance = new vdom.type(props); 41 | 42 | instance.componentWillMount(); 43 | 44 | const componentVdom = instance.render(); 45 | instance.dom = render(componentVdom, parent); 46 | instance.dom.__instance = instance; 47 | instance.dom.__key = vdom.props.key; 48 | 49 | instance.componentDidMount(); 50 | 51 | return instance.dom; 52 | } else { 53 | const componentVdom = vdom.type(props); 54 | return render(componentVdom, parent); 55 | } 56 | } 57 | 58 | function patch(dom, vdom, parent = dom.parentNode) { 59 | const replace = parent ? el => { 60 | parent.replaceChild(el, dom); 61 | return el; 62 | } : (el => el); 63 | 64 | if(isComponentVdom(vdom)) { 65 | const props = Object.assign({}, vdom.props, {children: vdom.children}); 66 | if (dom.__instance && dom.__instance.constructor == vdom.type) { 67 | dom.__instance.componentWillReceiveProps(props); 68 | dom.__instance.props = props; 69 | return patch(dom, dom.__instance.render(), parent); 70 | } else if (Component.isPrototypeOf(vdom.type)) { 71 | const componentDom = renderComponent(vdom, parent); 72 | if (parent){ 73 | parent.replaceChild(componentDom, dom); 74 | return componentDom; 75 | } else { 76 | return componentDom 77 | } 78 | } else if (!Component.isPrototypeOf(vdom.type)) { 79 | return patch(dom, vdom.type(props), parent); 80 | } 81 | }else if (dom instanceof Text) { 82 | if (typeof vdom === 'object') { 83 | return replace(render(vdom, parent)); 84 | } else { 85 | return dom.textContent != vdom ? replace(render(vdom, parent)) : dom; 86 | } 87 | } else if (dom.nodeName !== vdom.type.toUpperCase() && typeof vdom === 'object') { 88 | return replace(render(vdom, parent)); 89 | } else if(dom.nodeName === vdom.type.toUpperCase() && typeof vdom === 'object'){ 90 | const active = document.activeElement; 91 | 92 | const oldDoms = {}; 93 | [].concat(...dom.childNodes).map((child, index) => { 94 | const key = child.__key || `__index_${index}`; 95 | oldDoms[key] = child; 96 | }); 97 | [].concat(...vdom.children).map((child, index) => { 98 | const key = child.props && child.props.key || `__index_${index}`; 99 | dom.appendChild(oldDoms[key] ? patch(oldDoms[key], child) : render(child, dom)); 100 | delete oldDoms[key]; 101 | }); 102 | for (const key in oldDoms) { 103 | const instance = oldDoms[key].__instance; 104 | if (instance) instance.componentWillUnmount(); 105 | oldDoms[key].remove(); 106 | } 107 | for (const attr of dom.attributes) dom.removeAttribute(attr.name); 108 | for (const prop in vdom.props) setAttribute(dom, prop, vdom.props[prop]); 109 | 110 | active.focus(); 111 | 112 | return dom; 113 | } 114 | } 115 | 116 | function isEventListenerAttr(key, value) { 117 | return typeof value == 'function' && key.startsWith('on'); 118 | } 119 | 120 | function isStyleAttr(key, value) { 121 | return key == 'style' && typeof value == 'object'; 122 | } 123 | 124 | function isPlainAttr(key, value) { 125 | return typeof value != 'object' && typeof value != 'function'; 126 | } 127 | 128 | function isRefAttr(key, value) { 129 | return key === 'ref' && typeof value === 'function'; 130 | } 131 | 132 | const setAttribute = (dom, key, value) => { 133 | if (isEventListenerAttr(key, value)) { 134 | const eventType = key.slice(2).toLowerCase(); 135 | dom.__handlers = dom.__handlers || {}; 136 | dom.removeEventListener(eventType, dom.__handlers[eventType]); 137 | dom.__handlers[eventType] = value; 138 | dom.addEventListener(eventType, dom.__handlers[eventType]); 139 | } else if (key == 'checked' || key == 'value' || key == 'className') { 140 | dom[key] = value; 141 | } else if(isRefAttr(key, value)) { 142 | value(dom); 143 | } else if (isStyleAttr(key, value)) { 144 | Object.assign(dom.style, value); 145 | } else if (key == 'key') { 146 | dom.__key = value; 147 | } else if (isPlainAttr(key, value)) { 148 | dom.setAttribute(key, value); 149 | } 150 | } 151 | 152 | const createElement = (type, props, ...children) => { 153 | if (props === null) props = {}; 154 | return {type, props, children}; 155 | }; 156 | 157 | class Component { 158 | constructor(props) { 159 | this.props = props || {}; 160 | this.state = null; 161 | } 162 | 163 | setState(nextState) { 164 | this.state = Object.assign(this.state, nextState); 165 | if(this.dom && this.shouldComponentUpdate(this.props, nextState)) { 166 | patch(this.dom, this.render()); 167 | } 168 | } 169 | 170 | shouldComponentUpdate(nextProps, nextState) { 171 | return nextProps != this.props || nextState != this.state; 172 | } 173 | 174 | componentWillMount() {} 175 | 176 | componentDidMount() {} 177 | 178 | componentWillReceiveProps() {} 179 | 180 | componentWillUnmount() {} 181 | } 182 | -------------------------------------------------------------------------------- /patch/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /patch/index.js: -------------------------------------------------------------------------------- 1 | function Item(props) { 2 | return
  • {props.children} X
  • ; 3 | } 4 | 5 | class List extends Component { 6 | constructor(props) { 7 | super(); 8 | this.state = { 9 | list: [ 10 | { 11 | text: 'aaa', 12 | color: 'pink' 13 | }, 14 | { 15 | text: 'bbb', 16 | color: 'orange' 17 | }, 18 | { 19 | text: 'ccc', 20 | color: 'yellow' 21 | } 22 | ] 23 | } 24 | } 25 | 26 | handleItemRemove(index) { 27 | this.setState({ 28 | list: this.state.list.filter((item, i) => i !== index) 29 | }); 30 | } 31 | 32 | handleAdd() { 33 | this.setState({ 34 | list: [ 35 | ...this.state.list, 36 | { 37 | text: this.ref.value 38 | } 39 | ] 40 | }); 41 | } 42 | 43 | render() { 44 | return
    45 | 50 |
    51 | {this.ref = ele}}/> 52 | 53 |
    54 |
    ; 55 | } 56 | } 57 | 58 | render(, document.getElementById('root')); 59 | -------------------------------------------------------------------------------- /patch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "patch", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "babel dong.js index.js -d ./dist", 9 | "dev": "babel dong.js index.js -d ./dist --watch" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@babel/cli": "^7.16.8", 16 | "@babel/core": "^7.16.12", 17 | "@babel/preset-react": "^7.16.7" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /render-component/.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-react', 5 | { 6 | pragma: 'createElement' 7 | } 8 | ] 9 | ] 10 | } -------------------------------------------------------------------------------- /render-component/dist/dong.js: -------------------------------------------------------------------------------- 1 | function isTextVdom(vdom) { 2 | return typeof vdom == 'string' || typeof vdom == 'number'; 3 | } 4 | 5 | function isElementVdom(vdom) { 6 | return typeof vdom == 'object' && typeof vdom.type == 'string'; 7 | } 8 | 9 | function isComponentVdom(vdom) { 10 | return typeof vdom.type == 'function'; 11 | } 12 | 13 | const render = (vdom, parent = null) => { 14 | const mount = parent ? el => parent.appendChild(el) : el => el; 15 | 16 | if (isTextVdom(vdom)) { 17 | return mount(document.createTextNode(vdom)); 18 | } else if (isElementVdom(vdom)) { 19 | const dom = mount(document.createElement(vdom.type)); 20 | 21 | for (const child of [].concat(...vdom.children)) { 22 | render(child, dom); 23 | } 24 | 25 | for (const prop in vdom.props) { 26 | setAttribute(dom, prop, vdom.props[prop]); 27 | } 28 | 29 | return dom; 30 | } else if (isComponentVdom(vdom)) { 31 | const props = Object.assign({}, vdom.props, { 32 | children: vdom.children 33 | }); 34 | 35 | if (Component.isPrototypeOf(vdom.type)) { 36 | const instance = new vdom.type(props); 37 | instance.componentWillMount(); 38 | const componentVdom = instance.render(); 39 | instance.dom = render(componentVdom, parent); 40 | instance.componentDidMount(); 41 | return instance.dom; 42 | } else { 43 | const componentVdom = vdom.type(props); 44 | return render(componentVdom, parent); 45 | } 46 | } else { 47 | throw new Error(`Invalid VDOM: ${vdom}.`); 48 | } 49 | }; 50 | 51 | function isEventListenerAttr(key, value) { 52 | return typeof value == 'function' && key.startsWith('on'); 53 | } 54 | 55 | function isStyleAttr(key, value) { 56 | return key == 'style' && typeof value == 'object'; 57 | } 58 | 59 | function isPlainAttr(key, value) { 60 | return typeof value != 'object' && typeof value != 'function'; 61 | } 62 | 63 | const setAttribute = (dom, key, value) => { 64 | if (isEventListenerAttr(key, value)) { 65 | const eventType = key.slice(2).toLowerCase(); 66 | dom.addEventListener(eventType, value); 67 | } else if (isStyleAttr(key, value)) { 68 | Object.assign(dom.style, value); 69 | } else if (isPlainAttr(key, value)) { 70 | dom.setAttribute(key, value); 71 | } 72 | }; 73 | 74 | const createElement = (type, props, ...children) => { 75 | if (props === null) props = {}; 76 | return { 77 | type, 78 | props, 79 | children 80 | }; 81 | }; 82 | 83 | class Component { 84 | constructor(props) { 85 | this.props = props || {}; 86 | this.state = null; 87 | } 88 | 89 | setState(nextState) { 90 | this.state = nextState; 91 | } 92 | 93 | componentWillMount() { 94 | return undefined; 95 | } 96 | 97 | componentDidMount() { 98 | return undefined; 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /render-component/dist/index.js: -------------------------------------------------------------------------------- 1 | function Item(props) { 2 | return createElement("li", { 3 | className: "item", 4 | style: props.style, 5 | onClick: props.onClick 6 | }, props.children); 7 | } 8 | 9 | class List extends Component { 10 | constructor(props) { 11 | super(); 12 | this.state = { 13 | list: [{ 14 | text: 'aaa', 15 | color: 'blue' 16 | }, { 17 | text: 'bbb', 18 | color: 'orange' 19 | }, { 20 | text: 'ccc', 21 | color: 'red' 22 | }], 23 | textColor: props.textColor 24 | }; 25 | } 26 | 27 | render() { 28 | return createElement("ul", { 29 | className: "list" 30 | }, this.state.list.map((item, index) => { 31 | return createElement(Item, { 32 | style: { 33 | background: item.color, 34 | color: this.state.textColor 35 | }, 36 | onClick: () => alert(item.text) 37 | }, item.text); 38 | })); 39 | } 40 | 41 | } 42 | 43 | render(createElement(List, { 44 | textColor: 'pink' 45 | }), document.getElementById('root')); -------------------------------------------------------------------------------- /render-component/dong.js: -------------------------------------------------------------------------------- 1 | 2 | function isTextVdom(vdom) { 3 | return typeof vdom == 'string' || typeof vdom == 'number'; 4 | } 5 | 6 | function isElementVdom(vdom) { 7 | return typeof vdom == 'object' && typeof vdom.type == 'string'; 8 | } 9 | 10 | function isComponentVdom(vdom) { 11 | return typeof vdom.type == 'function'; 12 | } 13 | 14 | const render = (vdom, parent = null) => { 15 | const mount = parent ? (el => parent.appendChild(el)) : (el => el); 16 | if (isTextVdom(vdom)) { 17 | return mount(document.createTextNode(vdom)); 18 | } else if (isElementVdom(vdom)) { 19 | const dom = mount(document.createElement(vdom.type)); 20 | for (const child of [].concat(...vdom.children)) { 21 | render(child, dom); 22 | } 23 | for (const prop in vdom.props) { 24 | setAttribute(dom, prop, vdom.props[prop]); 25 | } 26 | return dom; 27 | } else if (isComponentVdom(vdom)) { 28 | const props = Object.assign({}, vdom.props, { 29 | children: vdom.children 30 | }); 31 | 32 | if (Component.isPrototypeOf(vdom.type)) { 33 | const instance = new vdom.type(props); 34 | instance.componentWillMount(); 35 | const componentVdom = instance.render(); 36 | instance.dom = render(componentVdom, parent); 37 | instance.componentDidMount(); 38 | return instance.dom; 39 | } else { 40 | const componentVdom = vdom.type(props); 41 | return render(componentVdom, parent); 42 | } 43 | } else { 44 | throw new Error(`Invalid VDOM: ${vdom}.`); 45 | } 46 | }; 47 | 48 | function isEventListenerAttr(key, value) { 49 | return typeof value == 'function' && key.startsWith('on'); 50 | } 51 | 52 | function isStyleAttr(key, value) { 53 | return key == 'style' && typeof value == 'object'; 54 | } 55 | 56 | function isPlainAttr(key, value) { 57 | return typeof value != 'object' && typeof value != 'function'; 58 | } 59 | 60 | const setAttribute = (dom, key, value) => { 61 | if (isEventListenerAttr(key, value)) { 62 | const eventType = key.slice(2).toLowerCase(); 63 | dom.addEventListener(eventType, value); 64 | } else if (isStyleAttr(key, value)) { 65 | Object.assign(dom.style, value); 66 | } else if (isPlainAttr(key, value)) { 67 | dom.setAttribute(key, value); 68 | } 69 | } 70 | 71 | const createElement = (type, props, ...children) => { 72 | if (props === null) props = {}; 73 | return {type, props, children}; 74 | }; 75 | 76 | class Component { 77 | constructor(props) { 78 | this.props = props || {}; 79 | this.state = null; 80 | } 81 | 82 | setState(nextState) { 83 | this.state = nextState; 84 | } 85 | 86 | componentWillMount() { 87 | return undefined; 88 | } 89 | 90 | componentDidMount() { 91 | return undefined; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /render-component/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /render-component/index.js: -------------------------------------------------------------------------------- 1 | function Item(props) { 2 | return
  • {props.children}
  • ; 3 | } 4 | 5 | class List extends Component { 6 | constructor(props) { 7 | super(); 8 | this.state = { 9 | list: [ 10 | { 11 | text: 'aaa', 12 | color: 'blue' 13 | }, 14 | { 15 | text: 'bbb', 16 | color: 'orange' 17 | }, 18 | { 19 | text: 'ccc', 20 | color: 'red' 21 | } 22 | ], 23 | textColor: props.textColor 24 | } 25 | } 26 | 27 | render() { 28 | return ; 33 | } 34 | } 35 | 36 | render(, document.getElementById('root')); 37 | -------------------------------------------------------------------------------- /render-component/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "render-component", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "babel dong.js index.js -d ./dist", 9 | "dev": "babel dong.js index.js -d ./dist --watch" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@babel/cli": "^7.16.8", 16 | "@babel/core": "^7.16.10", 17 | "@babel/preset-react": "^7.16.7" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /render-fiber/.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-react', 5 | { 6 | pragma: 'Dong.createElement' 7 | } 8 | ] 9 | ] 10 | } -------------------------------------------------------------------------------- /render-fiber/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuarkGluonPlasma/frontend-framework-exercize/0613f59a19c0c4539ff491a784ea6dab9e566258/render-fiber/README.md -------------------------------------------------------------------------------- /render-fiber/dist/dong.js: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { ...props, 5 | children: children.map(child => typeof child === "object" ? child : createTextElement(child)) 6 | } 7 | }; 8 | } 9 | 10 | function createTextElement(text) { 11 | return { 12 | type: "TEXT_ELEMENT", 13 | props: { 14 | nodeValue: text, 15 | children: [] 16 | } 17 | }; 18 | } 19 | 20 | let nextFiberReconcileWork = null; 21 | let wipRoot = null; 22 | 23 | function workLoop(deadline) { 24 | let shouldYield = false; 25 | 26 | while (nextFiberReconcileWork && !shouldYield) { 27 | nextFiberReconcileWork = performNextWork(nextFiberReconcileWork); 28 | shouldYield = deadline.timeRemaining() < 1; 29 | } 30 | 31 | if (!nextFiberReconcileWork && wipRoot) { 32 | commitRoot(); 33 | } 34 | 35 | requestIdleCallback(workLoop); 36 | } 37 | 38 | requestIdleCallback(workLoop); 39 | 40 | function render(element, container) { 41 | wipRoot = { 42 | dom: container, 43 | props: { 44 | children: [element] 45 | } 46 | }; 47 | nextFiberReconcileWork = wipRoot; 48 | } 49 | 50 | function performNextWork(fiber) { 51 | reconcile(fiber); 52 | 53 | if (fiber.child) { 54 | return fiber.child; 55 | } 56 | 57 | let nextFiber = fiber; 58 | 59 | while (nextFiber) { 60 | if (nextFiber.sibling) { 61 | return nextFiber.sibling; 62 | } 63 | 64 | nextFiber = nextFiber.return; 65 | } 66 | } 67 | 68 | function reconcile(fiber) { 69 | if (!fiber.dom) { 70 | fiber.dom = createDom(fiber); 71 | } 72 | 73 | reconcileChildren(fiber, fiber.props.children); 74 | } 75 | 76 | function reconcileChildren(wipFiber, elements) { 77 | let index = 0; 78 | let prevSibling = null; 79 | 80 | while (index < elements.length) { 81 | const element = elements[index]; 82 | let newFiber = { 83 | type: element.type, 84 | props: element.props, 85 | dom: null, 86 | return: wipFiber, 87 | effectTag: "PLACEMENT" 88 | }; 89 | 90 | if (index === 0) { 91 | wipFiber.child = newFiber; 92 | } else if (element) { 93 | prevSibling.sibling = newFiber; 94 | } 95 | 96 | prevSibling = newFiber; 97 | index++; 98 | } 99 | } 100 | 101 | function commitRoot() { 102 | commitWork(wipRoot.child); 103 | wipRoot = null; 104 | } 105 | 106 | function commitWork(fiber) { 107 | if (!fiber) { 108 | return; 109 | } 110 | 111 | let domParentFiber = fiber.return; 112 | 113 | while (!domParentFiber.dom) { 114 | domParentFiber = domParentFiber.return; 115 | } 116 | 117 | const domParent = domParentFiber.dom; 118 | 119 | if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) { 120 | domParent.appendChild(fiber.dom); 121 | } 122 | 123 | commitWork(fiber.child); 124 | commitWork(fiber.sibling); 125 | } 126 | 127 | function createDom(fiber) { 128 | const dom = fiber.type == "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(fiber.type); 129 | 130 | for (const prop in fiber.props) { 131 | setAttribute(dom, prop, fiber.props[prop]); 132 | } 133 | 134 | return dom; 135 | } 136 | 137 | function isEventListenerAttr(key, value) { 138 | return typeof value == 'function' && key.startsWith('on'); 139 | } 140 | 141 | function isStyleAttr(key, value) { 142 | return key == 'style' && typeof value == 'object'; 143 | } 144 | 145 | function isPlainAttr(key, value) { 146 | return typeof value != 'object' && typeof value != 'function'; 147 | } 148 | 149 | const setAttribute = (dom, key, value) => { 150 | if (key === 'children') { 151 | return; 152 | } 153 | 154 | if (key === 'nodeValue') { 155 | dom.textContent = value; 156 | } else if (isEventListenerAttr(key, value)) { 157 | const eventType = key.slice(2).toLowerCase(); 158 | dom.addEventListener(eventType, value); 159 | } else if (isStyleAttr(key, value)) { 160 | Object.assign(dom.style, value); 161 | } else if (isPlainAttr(key, value)) { 162 | dom.setAttribute(key, value); 163 | } 164 | }; 165 | 166 | const Dong = { 167 | createElement, 168 | render 169 | }; -------------------------------------------------------------------------------- /render-fiber/dist/index.js: -------------------------------------------------------------------------------- 1 | const data = { 2 | item1: 'bb', 3 | item2: 'cc' 4 | }; 5 | const jsx = Dong.createElement("ul", { 6 | className: "list" 7 | }, Dong.createElement("li", { 8 | className: "item", 9 | style: { 10 | background: 'blue', 11 | color: 'pink' 12 | }, 13 | onClick: () => alert(2) 14 | }, "aa"), Dong.createElement("li", { 15 | className: "item" 16 | }, data.item1, Dong.createElement("i", null, "xxx")), Dong.createElement("li", { 17 | className: "item" 18 | }, data.item2)); 19 | console.log(JSON.stringify(jsx, null, 4)); 20 | Dong.render(jsx, document.getElementById("root")); -------------------------------------------------------------------------------- /render-fiber/dong.js: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | } 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | 26 | let nextFiberReconcileWork = null; 27 | let wipRoot = null; 28 | 29 | function workLoop(deadline) { 30 | let shouldYield = false; 31 | while (nextFiberReconcileWork && !shouldYield) { 32 | nextFiberReconcileWork = performNextWork( 33 | nextFiberReconcileWork 34 | ); 35 | shouldYield = deadline.timeRemaining() < 1; 36 | } 37 | 38 | if (!nextFiberReconcileWork && wipRoot) { 39 | commitRoot(); 40 | } 41 | 42 | requestIdleCallback(workLoop); 43 | } 44 | 45 | requestIdleCallback(workLoop); 46 | 47 | 48 | 49 | function render(element, container) { 50 | wipRoot = { 51 | dom: container, 52 | props: { 53 | children: [element], 54 | } 55 | } 56 | nextFiberReconcileWork = wipRoot 57 | } 58 | 59 | 60 | 61 | function performNextWork(fiber) { 62 | 63 | reconcile(fiber); 64 | 65 | if (fiber.child) { 66 | return fiber.child; 67 | } 68 | let nextFiber = fiber; 69 | while (nextFiber) { 70 | if (nextFiber.sibling) { 71 | return nextFiber.sibling; 72 | } 73 | nextFiber = nextFiber.return; 74 | } 75 | } 76 | 77 | function reconcile(fiber) { 78 | if (!fiber.dom) { 79 | fiber.dom = createDom(fiber) 80 | } 81 | reconcileChildren(fiber, fiber.props.children) 82 | } 83 | 84 | 85 | function reconcileChildren(wipFiber, elements) { 86 | let index = 0 87 | let prevSibling = null 88 | 89 | while ( 90 | index < elements.length 91 | ) { 92 | const element = elements[index] 93 | let newFiber = { 94 | type: element.type, 95 | props: element.props, 96 | dom: null, 97 | return: wipFiber, 98 | effectTag: "PLACEMENT", 99 | } 100 | 101 | if (index === 0) { 102 | wipFiber.child = newFiber 103 | } else if (element) { 104 | prevSibling.sibling = newFiber 105 | } 106 | 107 | prevSibling = newFiber 108 | index++ 109 | } 110 | } 111 | 112 | function commitRoot() { 113 | commitWork(wipRoot.child); 114 | wipRoot = null 115 | } 116 | 117 | function commitWork(fiber) { 118 | if (!fiber) { 119 | return 120 | } 121 | 122 | let domParentFiber = fiber.return 123 | while (!domParentFiber.dom) { 124 | domParentFiber = domParentFiber.return 125 | } 126 | const domParent = domParentFiber.dom 127 | 128 | if ( 129 | fiber.effectTag === "PLACEMENT" && 130 | fiber.dom != null 131 | ) { 132 | domParent.appendChild(fiber.dom) 133 | } 134 | commitWork(fiber.child) 135 | commitWork(fiber.sibling) 136 | } 137 | 138 | 139 | function createDom(fiber) { 140 | const dom = fiber.type == "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(fiber.type); 141 | 142 | for (const prop in fiber.props) { 143 | setAttribute(dom, prop, fiber.props[prop]); 144 | } 145 | 146 | return dom; 147 | } 148 | 149 | function isEventListenerAttr(key, value) { 150 | return typeof value == 'function' && key.startsWith('on'); 151 | } 152 | 153 | function isStyleAttr(key, value) { 154 | return key == 'style' && typeof value == 'object'; 155 | } 156 | 157 | function isPlainAttr(key, value) { 158 | return typeof value != 'object' && typeof value != 'function'; 159 | } 160 | 161 | const setAttribute = (dom, key, value) => { 162 | if (key === 'children') { 163 | return; 164 | } 165 | 166 | if (key === 'nodeValue') { 167 | dom.textContent = value; 168 | } else if (isEventListenerAttr(key, value)) { 169 | const eventType = key.slice(2).toLowerCase(); 170 | dom.addEventListener(eventType, value); 171 | } else if (isStyleAttr(key, value)) { 172 | Object.assign(dom.style, value); 173 | } else if (isPlainAttr(key, value)) { 174 | dom.setAttribute(key, value); 175 | } 176 | }; 177 | 178 | 179 | const Dong = { 180 | createElement, 181 | render 182 | } 183 | -------------------------------------------------------------------------------- /render-fiber/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /render-fiber/index.js: -------------------------------------------------------------------------------- 1 | const data = { 2 | item1: 'bb', 3 | item2: 'cc' 4 | } 5 | 6 | const jsx = ; 11 | 12 | console.log(JSON.stringify(jsx, null, 4)); 13 | 14 | Dong.render(jsx, document.getElementById("root")); 15 | -------------------------------------------------------------------------------- /render-fiber/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "render-fiber", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": ".babelrc.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "babel dong.js index.js -d ./dist", 9 | "dev": "babel dong.js index.js -d ./dist --watch" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@babel/cli": "^7.16.8", 16 | "@babel/core": "^7.16.10", 17 | "@babel/preset-react": "^7.16.7" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /render-jsx/.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-react', 5 | { 6 | pragma: 'createElement' 7 | } 8 | ] 9 | ] 10 | } -------------------------------------------------------------------------------- /render-jsx/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuarkGluonPlasma/frontend-framework-exercize/0613f59a19c0c4539ff491a784ea6dab9e566258/render-jsx/README.md -------------------------------------------------------------------------------- /render-jsx/dist/dong.js: -------------------------------------------------------------------------------- 1 | function isTextVdom(vdom) { 2 | return typeof vdom == 'string' || typeof vdom == 'number'; 3 | } 4 | 5 | function isElementVdom(vdom) { 6 | return typeof vdom == 'object' && typeof vdom.type == 'string'; 7 | } 8 | 9 | const render = (vdom, parent = null) => { 10 | const mount = parent ? el => parent.appendChild(el) : el => el; 11 | 12 | if (isTextVdom(vdom)) { 13 | return mount(document.createTextNode(vdom)); 14 | } else if (isElementVdom(vdom)) { 15 | const dom = mount(document.createElement(vdom.type)); 16 | 17 | for (const child of vdom.children) { 18 | render(child, dom); 19 | } 20 | 21 | for (const prop in vdom.props) { 22 | setAttribute(dom, prop, vdom.props[prop]); 23 | } 24 | 25 | return dom; 26 | } else { 27 | throw new Error(`Invalid VDOM: ${vdom}.`); 28 | } 29 | }; 30 | 31 | function isEventListenerAttr(key, value) { 32 | return typeof value == 'function' && key.startsWith('on'); 33 | } 34 | 35 | function isStyleAttr(key, value) { 36 | return key == 'style' && typeof value == 'object'; 37 | } 38 | 39 | function isPlainAttr(key, value) { 40 | return typeof value != 'object' && typeof value != 'function'; 41 | } 42 | 43 | const setAttribute = (dom, key, value) => { 44 | if (isEventListenerAttr(key, value)) { 45 | const eventType = key.slice(2).toLowerCase(); 46 | dom.addEventListener(eventType, value); 47 | } else if (isStyleAttr(key, value)) { 48 | Object.assign(dom.style, value); 49 | } else if (isPlainAttr(key, value)) { 50 | dom.setAttribute(key, value); 51 | } 52 | }; 53 | 54 | const createElement = (type, props, ...children) => { 55 | if (props === null) props = {}; 56 | return { 57 | type, 58 | props, 59 | children 60 | }; 61 | }; -------------------------------------------------------------------------------- /render-jsx/dist/index.js: -------------------------------------------------------------------------------- 1 | const data = { 2 | item1: 'bbb', 3 | item2: 'ddd' 4 | }; 5 | const jsx = createElement("ul", { 6 | className: "list" 7 | }, createElement("li", { 8 | className: "item", 9 | style: { 10 | background: 'blue', 11 | color: 'pink' 12 | }, 13 | onClick: () => alert(2) 14 | }, "aaa"), createElement("li", { 15 | className: "item" 16 | }, data.item1, createElement("i", null, "aaa")), createElement("li", { 17 | className: "item" 18 | }, data.item2)); 19 | render(jsx, document.getElementById('root')); -------------------------------------------------------------------------------- /render-jsx/dong.js: -------------------------------------------------------------------------------- 1 | 2 | function isTextVdom(vdom) { 3 | return typeof vdom == 'string' || typeof vdom == 'number'; 4 | } 5 | 6 | function isElementVdom(vdom) { 7 | return typeof vdom == 'object' && typeof vdom.type == 'string'; 8 | } 9 | 10 | const render = (vdom, parent = null) => { 11 | const mount = parent ? (el => parent.appendChild(el)) : (el => el); 12 | if (isTextVdom(vdom)) { 13 | return mount(document.createTextNode(vdom)); 14 | } else if (isElementVdom(vdom)) { 15 | const dom = mount(document.createElement(vdom.type)); 16 | for (const child of [].concat(...vdom.children)) {// children 元素也是 数组,要拍平 17 | render(child, dom); 18 | } 19 | for (const prop in vdom.props) { 20 | setAttribute(dom, prop, vdom.props[prop]); 21 | } 22 | return dom; 23 | } else { 24 | throw new Error(`Invalid VDOM: ${vdom}.`); 25 | } 26 | }; 27 | 28 | function isEventListenerAttr(key, value) { 29 | return typeof value == 'function' && key.startsWith('on'); 30 | } 31 | 32 | function isStyleAttr(key, value) { 33 | return key == 'style' && typeof value == 'object'; 34 | } 35 | 36 | function isPlainAttr(key, value) { 37 | return typeof value != 'object' && typeof value != 'function'; 38 | } 39 | 40 | const setAttribute = (dom, key, value) => { 41 | if (isEventListenerAttr(key, value)) { 42 | const eventType = key.slice(2).toLowerCase(); 43 | dom.addEventListener(eventType, value); 44 | } else if (isStyleAttr(key, value)) { 45 | Object.assign(dom.style, value); 46 | } else if (isPlainAttr(key, value)) { 47 | dom.setAttribute(key, value); 48 | } 49 | } 50 | 51 | const createElement = (type, props, ...children) => { 52 | if (props === null) props = {}; 53 | return {type, props, children}; 54 | }; 55 | 56 | -------------------------------------------------------------------------------- /render-jsx/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /render-jsx/index.js: -------------------------------------------------------------------------------- 1 | const data = { 2 | item1: 'bbb', 3 | item2: 'ddd' 4 | } 5 | const jsx = 10 | 11 | render(jsx, document.getElementById('root')); 12 | 13 | 14 | const jsx = 18 | 19 | const vdom = { 20 | type: 'ul', 21 | props: { 22 | className: 'list', 23 | children: [ 24 | { 25 | type: 'li', 26 | props: { 27 | className: 'item', 28 | style: { 29 | background: 'blue', 30 | color: 'pink' 31 | }, 32 | onClick: () => alert(2), 33 | children: [ 34 | 'aaa' 35 | ] 36 | } 37 | }, 38 | { 39 | type: 'li', 40 | props: { 41 | className: 'item', 42 | children: ['bbb'] 43 | } 44 | } 45 | ] 46 | } 47 | }; 48 | 49 | 50 | const fiberRoot = vdom; 51 | 52 | let currentFiber = fiberRoot; 53 | 54 | 55 | 56 | function reconcileChildren(wipFiber, elements) { 57 | let index = 0 58 | let oldFiber = 59 | wipFiber.alternate && wipFiber.alternate.child 60 | let prevSibling = null 61 | 62 | while ( 63 | index < elements.length || 64 | oldFiber != null 65 | ) { 66 | const element = elements[index] 67 | let newFiber = null 68 | 69 | const sameType = 70 | oldFiber && 71 | element && 72 | element.type == oldFiber.type 73 | 74 | if (sameType) { 75 | newFiber = { 76 | type: oldFiber.type, 77 | props: element.props, 78 | dom: oldFiber.dom, 79 | parent: wipFiber, 80 | alternate: oldFiber, 81 | effectTag: "UPDATE", 82 | } 83 | } 84 | if (element && !sameType) { 85 | newFiber = { 86 | type: element.type, 87 | props: element.props, 88 | dom: null, 89 | parent: wipFiber, 90 | alternate: null, 91 | effectTag: "PLACEMENT", 92 | } 93 | } 94 | if (oldFiber && !sameType) { 95 | oldFiber.effectTag = "DELETION" 96 | deletions.push(oldFiber) 97 | } 98 | 99 | if (oldFiber) { 100 | oldFiber = oldFiber.sibling 101 | } 102 | 103 | if (index === 0) { 104 | wipFiber.child = newFiber 105 | } else if (element) { 106 | prevSibling.sibling = newFiber 107 | } 108 | 109 | prevSibling = newFiber 110 | index++ 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /render-jsx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "render-jsx", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": ".babelrc.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "babel dong.js index.js -d ./dist", 9 | "dev": "babel dong.js index.js -d ./dist --watch" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@babel/cli": "^7.16.8", 16 | "@babel/core": "^7.16.10", 17 | "@babel/preset-react": "^7.16.7" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /render-vdom/dong.js: -------------------------------------------------------------------------------- 1 | 2 | function isTextVdom(vdom) { 3 | return typeof vdom == 'string' || typeof vdom == 'number'; 4 | } 5 | 6 | function isElementVdom(vdom) { 7 | return typeof vdom == 'object' && typeof vdom.type == 'string'; 8 | } 9 | 10 | const render = (vdom, parent = null) => { 11 | const mount = parent ? (el => parent.appendChild(el)) : (el => el); 12 | if (isTextVdom(vdom)) { 13 | return mount(document.createTextNode(vdom)); 14 | } else if (isElementVdom(vdom)) { 15 | const dom = mount(document.createElement(vdom.type)); 16 | for (const child of vdom.children) { 17 | render(child, dom); 18 | } 19 | for (const prop in vdom.props) { 20 | setAttribute(dom, prop, vdom.props[prop]); 21 | } 22 | return dom; 23 | } else { 24 | throw new Error(`Invalid VDOM: ${vdom}.`); 25 | } 26 | }; 27 | 28 | function isEventListenerAttr(key, value) { 29 | return typeof value == 'function' && key.startsWith('on'); 30 | } 31 | 32 | function isStyleAttr(key, value) { 33 | return key == 'style' && typeof value == 'object'; 34 | } 35 | 36 | function isPlainAttr(key, value) { 37 | return typeof value != 'object' && typeof value != 'function'; 38 | } 39 | 40 | const setAttribute = (dom, key, value) => { 41 | if (isEventListenerAttr(key, value)) { 42 | const eventType = key.slice(2).toLowerCase(); 43 | dom.addEventListener(eventType, value); 44 | } else if (isStyleAttr(key, value)) { 45 | Object.assign(dom.style, value); 46 | } else if (isPlainAttr(key, value)) { 47 | dom.setAttribute(key, value); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /render-vdom/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /render-vdom/index.js: -------------------------------------------------------------------------------- 1 | 2 | const vdom = { 3 | type: 'ul', 4 | props: { 5 | className: 'list' 6 | }, 7 | children: [ 8 | { 9 | type: 'li', 10 | props: { 11 | className: 'item', 12 | style: { 13 | background: 'blue', 14 | color: '#fff' 15 | }, 16 | onClick: function() { 17 | alert(1); 18 | } 19 | }, 20 | children: [ 21 | 'aaaa' 22 | ] 23 | }, 24 | { 25 | type: 'li', 26 | props: { 27 | className: 'item' 28 | }, 29 | children: [ 30 | 'bbbbddd' 31 | ] 32 | }, 33 | { 34 | type: 'li', 35 | props: { 36 | className: 'item' 37 | }, 38 | children: [ 39 | 'cccc' 40 | ] 41 | } 42 | ] 43 | }; 44 | 45 | render(vdom, document.getElementById('root')); 46 | -------------------------------------------------------------------------------- /render-vdom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "render-vdom", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "dong.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | --------------------------------------------------------------------------------