├── components ├── funcComponent.js ├── pureComponent.js └── component.js ├── .gitignore ├── .babelrc ├── index.js ├── package.json ├── react └── index.js ├── README.md ├── index.html ├── reactDom ├── handleAttrs.js └── index.js ├── app.js └── test.js /components/funcComponent.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/pureComponent.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | /.cache -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": [ 4 | ["transform-react-jsx", { 5 | "pragma": "React.createElement" 6 | }] 7 | ] 8 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React from './react'; 2 | import ReactDom from './reactDom'; 3 | import App from './app' 4 | ReactDom.render( 5 | , 6 | document.querySelector('#root') 7 | ); 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spa", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "@babel/core": "^7.5.5", 8 | "babel-plugin-transform-react-jsx": "^6.24.1", 9 | "babel-preset-env": "^1.7.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /react/index.js: -------------------------------------------------------------------------------- 1 | import { Component } from '../components/component'; 2 | const React = {}; 3 | React.Component = Component; 4 | React.createElement = function(tag, attrs, ...children) { 5 | return { 6 | tag, 7 | attrs, 8 | children 9 | }; 10 | }; 11 | export default React; 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mini-react 2 | 这是一个迷你版 react,This is a mini-react 3 | 4 | # 欢迎来到`mini-react` 5 | 6 | 使用步骤 : 7 | 8 | * `npm install -g parcel-bundler` 9 | 10 | * `parcel index.html` 11 | 12 | 本仓库一共三个分支: 13 | 14 | * `master` - 最简单的版本 15 | 16 | * `diff` - 加入`diff`算法版本 17 | 18 | * `diff-async` 异步`state`和`diff`算法 19 | 20 | 21 | 22 | 如果有任何问题欢迎联系 `453089136@qq.com` 23 | 24 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 17 | 18 | -------------------------------------------------------------------------------- /reactDom/handleAttrs.js: -------------------------------------------------------------------------------- 1 | export default function setAttribute(dom, name, value) { 2 | if (name === 'className') name = 'class'; 3 | if (/on\w+/.test(name)) { 4 | name = name.toLowerCase(); 5 | dom[name] = value || ''; 6 | } else if (name === 'style') { 7 | if (!value || typeof value === 'string') { 8 | dom.style.cssText = value || ''; 9 | } else if (value && typeof value === 'object') { 10 | for (let name in value) { 11 | dom.style[name] = 12 | typeof value[name] === 'number' ? value[name] + 'px' : value[name]; 13 | } 14 | } 15 | } else { 16 | if (name in dom) { 17 | dom[name] = value || ''; 18 | } 19 | if (value) { 20 | dom.setAttribute(name, value); 21 | } else { 22 | dom.removeAttribute(name); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import React from './react/index'; 2 | export default class App extends React.Component { 3 | constructor(props) { 4 | super(props); 5 | this.state = { 6 | test: 'init' 7 | }; 8 | this.test=[1,2,3] 9 | } 10 | 11 | handleClick(e){ 12 | this.setState({ 13 | test:'test' 14 | }) 15 | } 16 | componentDidMount(){ 17 | console.log('mount') 18 | this.setState({ 19 | test:'mount' 20 | }) 21 | } 22 | componentWillMount(){ 23 | console.log('willMount') 24 | } 25 | componentWillUpdate(){ 26 | console.log('willupdate') 27 | } 28 | componentDidUpdate(){ 29 | console.log('didupdate') 30 | } 31 | render() { 32 | return
33 | {this.state.test} 34 | 35 |
; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /reactDom/index.js: -------------------------------------------------------------------------------- 1 | import handleAttrs from './handleAttrs'; 2 | import { createComponent, setComponentProps } from '../components/component'; 3 | const ReactDom = {}; 4 | 5 | const render = function(vnode, container) { 6 | return container.appendChild(_render(vnode)); 7 | }; 8 | 9 | ReactDom.render = render; 10 | export function _render(vnode) { 11 | console.log('_render'); 12 | if (vnode === undefined || vnode === null || typeof vnode === 'boolean') 13 | vnode = ''; 14 | 15 | if (typeof vnode === 'number') vnode = String(vnode); 16 | 17 | if (typeof vnode === 'string') { 18 | let textNode = document.createTextNode(vnode); 19 | return textNode; 20 | } 21 | if (typeof vnode.tag === 'function') { 22 | const component = createComponent(vnode.tag, vnode.attrs); 23 | setComponentProps(component, vnode.attrs); 24 | return component.base; 25 | } 26 | 27 | const dom = document.createElement(vnode.tag); 28 | 29 | if (vnode.attrs) { 30 | Object.keys(vnode.attrs).forEach(key => { 31 | const value = vnode.attrs[key]; 32 | handleAttrs(dom, key, value); 33 | }); 34 | } 35 | 36 | vnode.children && vnode.children.forEach(child => render(child, dom)); // 递归渲染子节点 37 | 38 | return dom; 39 | } 40 | 41 | 42 | 43 | 44 | export default ReactDom; 45 | -------------------------------------------------------------------------------- /components/component.js: -------------------------------------------------------------------------------- 1 | import { _render } from '../reactDom/index'; 2 | export class Component { 3 | constuctor(props = {}) { 4 | this.state = {}; 5 | this.props = props; 6 | } 7 | setState(stateChange) { 8 | // 将修改合并到state 9 | console.log('setstate'); 10 | const result = Object.assign(this.state, stateChange); 11 | console.log('state:', result); 12 | renderComponent(this); 13 | } 14 | } 15 | export function createComponent(component, props) { 16 | let inst; 17 | // 如果是类定义组件,则直接返回实例 18 | if (component.prototype && component.prototype.render) { 19 | inst = new component(props); 20 | // 如果是函数定义组件,则将其扩展为类定义组件 21 | } else { 22 | inst = new Component(props); 23 | inst.constructor = component; 24 | inst.render = function() { 25 | return this.constructor(props); 26 | }; 27 | } 28 | 29 | return inst; 30 | } 31 | export function setComponentProps(component, props) { 32 | if (!component.base) { 33 | if (component.componentWillMount) component.componentWillMount(); 34 | } else if (component.base && component.componentWillReceiveProps) { 35 | component.componentWillReceiveProps(props); 36 | } 37 | 38 | component.props = props; 39 | 40 | renderComponent(component); 41 | } 42 | export function renderComponent(component) { 43 | console.log('renderComponent'); 44 | let base; 45 | 46 | const renderer = component.render(); 47 | 48 | if (component.base && component.componentWillUpdate) { 49 | component.componentWillUpdate(); 50 | } 51 | 52 | base = _render(renderer); 53 | 54 | if (component.base) { 55 | if (component.componentDidUpdate) component.componentDidUpdate(); 56 | } else { 57 | component.base = base; 58 | component.componentDidMount && component.componentDidMount(); 59 | if (component.base && component.base.parentNode) { 60 | component.base.parentNode.replaceChild(base, component.base); 61 | } 62 | return; 63 | } 64 | if (component.base && component.base.parentNode) { 65 | component.base.parentNode.replaceChild(base, component.base); 66 | } 67 | 68 | component.base = base; 69 | base._component = component; 70 | } 71 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import { Componet } from '../react'; 2 | import { setAttribute } from './dom'; 3 | 4 | /* 5 | * @param {HTMLElement} dom 真实DOM 6 | * @param {vnode} vnode 虚拟DOM 7 | * @param {HTMLElement} container 容器 8 | * @returns {HTMLElement} 更新后的DOM 9 | */ 10 | export function diff(dom, vnode, container) { 11 | const ret = diffNode(dom, vnode); 12 | 13 | if (container && ret.parentNode !== container) { 14 | container.appendChild(ret); 15 | } 16 | 17 | return ret; 18 | } 19 | 20 | function diffNode(dom, vnode) { 21 | let out = dom; 22 | 23 | if (vnode === undefined || vnode === null || typeof vnode === 'boolean') 24 | vnode = ''; 25 | 26 | if (typeof vnode === 'number') vnode = String(vnode); 27 | 28 | // diff text node 29 | if (typeof vnode === 'string') { 30 | // 如果当前的DOM就是文本节点,则直接更新内容 31 | if (dom && dom.nodeType === 3) { 32 | // nodeType: https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType 33 | if (dom.textContent !== vnode) { 34 | dom.textContent = vnode; 35 | } 36 | // 如果DOM不是文本节点,则新建一个文本节点DOM,并移除掉原来的 37 | } else { 38 | out = document.createTextNode(vnode); 39 | if (dom && dom.parentNode) { 40 | dom.parentNode.replaceChild(out, dom); 41 | } 42 | } 43 | return out; 44 | } 45 | 46 | if (typeof vnode.tag === 'function') { 47 | return diffComponent(dom, vnode); 48 | } 49 | 50 | // 51 | if (!dom || !isSameNodeType(dom, vnode)) { 52 | out = document.createElement(vnode.tag); 53 | 54 | if (dom) { 55 | [...dom.childNodes].map(out.appendChild); // 将原来的子节点移到新节点下 56 | 57 | if (dom.parentNode) { 58 | dom.parentNode.replaceChild(out, dom); // 移除掉原来的DOM对象 59 | } 60 | } 61 | } 62 | 63 | if ( 64 | (vnode.children && vnode.children.length > 0) || 65 | (out.childNodes && out.childNodes.length > 0) 66 | ) { 67 | diffChildren(out, vnode.children); 68 | } 69 | 70 | diffAttributes(out, vnode); 71 | 72 | return out; 73 | } 74 | 75 | function diffChildren(dom, vchildren) { 76 | const domChildren = dom.childNodes; 77 | const children = []; 78 | 79 | const keyed = {}; 80 | 81 | if (domChildren.length > 0) { 82 | for (let i = 0; i < domChildren.length; i++) { 83 | const child = domChildren[i]; 84 | const key = child.key; 85 | if (key) { 86 | keyedLen++; 87 | keyed[key] = child; 88 | } else { 89 | children.push(child); 90 | } 91 | } 92 | } 93 | 94 | if (vchildren && vchildren.length > 0) { 95 | let min = 0; 96 | let childrenLen = children.length; 97 | 98 | for (let i = 0; i < vchildren.length; i++) { 99 | const vchild = vchildren[i]; 100 | const key = vchild.key; 101 | let child; 102 | 103 | if (key) { 104 | if (keyed[key]) { 105 | child = keyed[key]; 106 | keyed[key] = undefined; 107 | } 108 | } else if (min < childrenLen) { 109 | for (let j = min; j < childrenLen; j++) { 110 | let c = children[j]; 111 | 112 | if (c && isSameNodeType(c, vchild)) { 113 | child = c; 114 | children[j] = undefined; 115 | 116 | if (j === childrenLen - 1) childrenLen--; 117 | if (j === min) min++; 118 | break; 119 | } 120 | } 121 | } 122 | 123 | child = diffNode(child, vchild); 124 | 125 | const f = domChildren[i]; 126 | if (child && child !== dom && child !== f) { 127 | if (!f) { 128 | dom.appendChild(child); 129 | } else if (child === f.nextSibling) { 130 | removeNode(f); 131 | } else { 132 | dom.insertBefore(child, f); 133 | } 134 | } 135 | } 136 | } 137 | } 138 | 139 | function diffComponent(dom, vnode) { 140 | let c = dom && dom._component; 141 | let oldDom = dom; 142 | 143 | // 如果组件类型没有变化,则重新set props 144 | if (c && c.constructor === vnode.tag) { 145 | setComponentProps(c, vnode.attrs); 146 | dom = c.base; 147 | // 如果组件类型变化,则移除掉原来组件,并渲染新的组件 148 | } else { 149 | if (c) { 150 | unmountComponent(c); 151 | oldDom = null; 152 | } 153 | 154 | c = createComponent(vnode.tag, vnode.attrs); 155 | 156 | setComponentProps(c, vnode.attrs); 157 | dom = c.base; 158 | 159 | if (oldDom && dom !== oldDom) { 160 | oldDom._component = null; 161 | removeNode(oldDom); 162 | } 163 | } 164 | 165 | return dom; 166 | } 167 | 168 | function setComponentProps(component, props) { 169 | if (!component.base) { 170 | if (component.componentWillMount) component.componentWillMount(); 171 | } else if (component.componentWillReceiveProps) { 172 | component.componentWillReceiveProps(props); 173 | } 174 | 175 | component.props = props; 176 | 177 | renderComponent(component); 178 | } 179 | 180 | export function renderComponent(component) { 181 | let base; 182 | 183 | const renderer = component.render(); 184 | 185 | if (component.base && component.componentWillUpdate) { 186 | component.componentWillUpdate(); 187 | } 188 | 189 | base = diffNode(component.base, renderer); 190 | 191 | component.base = base; 192 | base._component = component; 193 | 194 | if (component.base) { 195 | if (component.componentDidUpdate) component.componentDidUpdate(); 196 | } else if (component.componentDidMount) { 197 | component.componentDidMount(); 198 | } 199 | 200 | component.base = base; 201 | base._component = component; 202 | } 203 | 204 | function createComponent(component, props) { 205 | let inst; 206 | 207 | if (component.prototype && component.prototype.render) { 208 | inst = new component(props); 209 | } else { 210 | inst = new Component(props); 211 | inst.constructor = component; 212 | inst.render = function() { 213 | return this.constructor(props); 214 | }; 215 | } 216 | 217 | return inst; 218 | } 219 | 220 | function unmountComponent(component) { 221 | if (component.componentWillUnmount) component.componentWillUnmount(); 222 | removeNode(component.base); 223 | } 224 | 225 | function isSameNodeType(dom, vnode) { 226 | if (typeof vnode === 'string' || typeof vnode === 'number') { 227 | return dom.nodeType === 3; 228 | } 229 | 230 | if (typeof vnode.tag === 'string') { 231 | return dom.nodeName.toLowerCase() === vnode.tag.toLowerCase(); 232 | } 233 | 234 | return dom && dom._component && dom._component.constructor === vnode.tag; 235 | } 236 | 237 | function diffAttributes(dom, vnode) { 238 | const old = {}; // 当前DOM的属性 239 | const attrs = vnode.attrs; // 虚拟DOM的属性 240 | 241 | for (let i = 0; i < dom.attributes.length; i++) { 242 | const attr = dom.attributes[i]; 243 | old[attr.name] = attr.value; 244 | } 245 | 246 | // 如果原来的属性不在新的属性当中,则将其移除掉(属性值设为undefined) 247 | for (let name in old) { 248 | if (!(name in attrs)) { 249 | setAttribute(dom, name, undefined); 250 | } 251 | } 252 | 253 | // 更新新的属性值 254 | for (let name in attrs) { 255 | if (old[name] !== attrs[name]) { 256 | setAttribute(dom, name, attrs[name]); 257 | } 258 | } 259 | } 260 | 261 | function removeNode(dom) { 262 | if (dom && dom.parentNode) { 263 | dom.parentNode.removeChild(dom); 264 | } 265 | } 266 | --------------------------------------------------------------------------------