react.js
import $ from 'jquery';import {createUnit} from './unit';import {createElement} from './element';import {Component} from './component';let React = {render,createElement,Component}//此元素可能是一个文本节点、DOM节点(div)、或者 自定义组件Counterfunction render(element,container){//container.innerHTML=`<span data-reactid="${React.rootIndex}">${element}</span>`;//unit单元就是用来负责渲染的,负责把元素转换成可以在页面上显示的HTML字符串let unit = createUnit(element);let markUp = unit.getMarkUp('0');//用来返回HTML标记$(container).html(markUp);$(document).trigger('mounted');}export default React
// component.js
table File 12 lines (12 sloc) 261 Bytesclass Component{constructor(props){this.props = props;}setState(partialState){//第一个参数是新的元素 第二个参数是新的状态this._currentUnit.update(null,partialState);}}export {Component}
// element,js
class Element{constructor(type,props){this.type = type;this.props = props;}}function createElement(type,props={},...children){props.children = children||[];//children也是props的一个属性return new Element(type,props);}export {Element,createElement}
// types.js
export default {MOVE:'MOVE', //移动INSERT:"INSERT", //插入REMOVE:'REMOVE' //删除}
// unit.js
import {Element,createElement} from './element';import $ from 'jquery';import types from './types';let diffQueue=[];//差异队列let updateDepth=0;//更新的级别class Unit{constructor(element){//凡是挂载到私有属性上的都以_开头this._currentElement = element;}getMarkUp(){throw Error('此方法不能被调用');}}class TextUnit extends Unit{getMarkUp(reactid){this._reactid = reactid;return `<span data-reactid="${reactid}">${this._currentElement}</span>`;}update(nextElement){if(this._currentElement !== nextElement){this._currentElement = nextElement;$(`[data-reactid="${this._reactid}"]`).html(this._currentElement);}}}/**{type:'button',props:{id:'sayHello'},children:['say',{type:'b',{},'Hello'}]}<button id="sayHello" style="color:red;background-color:'green" onclick="sayHello()"><span>say</span><b>Hello</b></button>*/class NativeUnit extends Unit{getMarkUp(reactid){this._reactid = reactid;let {type,props} = this._currentElement;let tagStart = `<${type} data-reactid="${this._reactid}"`;let childString= '';let tagEnd = `</${type}>`;this._renderedChildrenUnits=[];//{id:'sayHello',onClick:sayHello,style:{color:'red',backgroundColor:'green'}},children:['say',{type:'b',{},'Hello'}]for(let propName in props){if(/^on[A-Z]/.test(propName)){//这说明要绑定事件了let eventName = propName.slice(2).toLowerCase();//click$(document).delegate(`[data-reactid="${this._reactid}"]`,`${eventName}.${this._reactid}`,props[propName]);}else if(propName === 'style'){//如果是一个样式对象let styleObj = props[propName];let styles = Object.entries(styleObj).map(([attr,value])=>{return `${attr.replace(/[A-Z]/g,m=>`-${m.toLowerCase()}`)}:${value}`;}).join(';');tagStart+=(` style="${styles}" `);}else if(propName === 'className'){//如果是一个类名的话tagStart+=(` class="${props[propName]}" `);}else if(propName == 'children'){let children = props[propName];children.forEach((child,index)=>{let childUnit = createUnit(child);//可能是一个字符中,也可以也是一个react元素 虚拟DOMchildUnit._mountIndex = index;//每个unit有一个_mountIndex 属性,指向自己在父节点中的索引位置this._renderedChildrenUnits.push(childUnit);let childMarkUp = childUnit.getMarkUp(`${this._reactid}.${index}`);childString+=childMarkUp;});}else{tagStart += (` ${propName}=${props[propName]} `);}}return tagStart+">"+childString+tagEnd;}update(nextElement){let oldProps = this._currentElement.props;let newProps = nextElement.props;this.updateDOMProperties(oldProps,newProps);this.updateDOMChildren(nextElement.props.children);}//此处要把新的儿子们传过来,然后后我老的儿子们进行对比,然后找出差异,进行修改DOMupdateDOMChildren(newChildrenElements){updateDepth++;this.diff(diffQueue,newChildrenElements);updateDepth--;if(updateDepth === 0){console.log(diffQueue)this.patch(diffQueue);diffQueue = [];}}patch(diffQueue){let deleteChildren = [];//这里要放着所有将要删除的节点let deleteMap = {};//这里暂存能复用的节点for(let i=0;i<diffQueue.length;i++){let difference = diffQueue[i];if(difference.type === types.MOVE || difference.type === types.REMOVE){let fromIndex = difference.fromIndex;let oldChild = $(difference.parentNode.children().get(fromIndex));if(!deleteMap[difference.parentId]){deleteMap[difference.parentId]={}}deleteMap[difference.parentId][fromIndex]=oldChild;deleteChildren.push(oldChild);}}$.each(deleteChildren,(idx,item)=>$(item).remove());for(let i=0;i<diffQueue.length;i++){let difference = diffQueue[i];switch(difference.type){case types.INSERT:this.insertChildAt(difference.parentNode,difference.toIndex,$(difference.markUp));break;case types.MOVE:this.insertChildAt(difference.parentNode,difference.toIndex,deleteMap[difference.parentId][difference.fromIndex]);break;default:break;}}}insertChildAt(parentNode,index,newNode){let oldChild = parentNode.children().get(index);oldChild?newNode.insertBefore(oldChild):newNode.appendTo(parentNode);}diff(diffQueue,newChildrenElements){//第一生成一个map,key=老的unitlet oldChildrenUnitMap = this.getOldChildrenMap(this._renderedChildrenUnits);//第二步生成一个新的儿子unit的数组let {newChildrenUnitMap,newChildrenUnits} = this.getNewChildren(oldChildrenUnitMap,newChildrenElements);let lastIndex = 0;//上一个已经确定位置的索引for(let i=0;i<newChildrenUnits.length;i++){let newUnit = newChildrenUnits[i];//第一个拿 到的就是newKey=Alet newKey = (newUnit._currentElement.props&&newUnit._currentElement.props.key)||i.toString();let oldChildUnit = oldChildrenUnitMap[newKey];if(oldChildUnit === newUnit){//如果说新老一致的话说明复用了老节点if(oldChildUnit._mountIndex < lastIndex){diffQueue.push({parentId:this._reactid,parentNode:$(`[data-reactid="${this._reactid}"]`),type:types.MOVE,fromIndex:oldChildUnit._mountIndex,toIndex:i});}lastIndex = Math.max(lastIndex,oldChildUnit._mountIndex);}else{if(oldChildUnit){diffQueue.push({parentId:this._reactid,parentNode:$(`[data-reactid="${this._reactid}"]`),type:types.REMOVE,fromIndex:oldChildUnit._mountIndex});this._renderedChildrenUnits=this._renderedChildrenUnits.filter(item=>item!=oldChildUnit);$(document).undelegate(`.${oldChildUnit._reactid}`);}diffQueue.push({parentId:this._reactid,parentNode:$(`[data-reactid="${this._reactid}"]`),type:types.INSERT,toIndex:i,markUp:newUnit.getMarkUp(`${this._reactid}.${i}`)});}newUnit._mountIndex = i;}for(let oldKey in oldChildrenUnitMap){let oldChild = oldChildrenUnitMap[oldKey];if(!newChildrenUnitMap.hasOwnProperty(oldKey)){diffQueue.push({parentId:this._reactid,parentNode:$(`[data-reactid="${this._reactid}"]`),type:types.REMOVE,fromIndex:oldChild._mountIndex});//如果要删除掉某一个节点,则要把它对应的unit也删除掉this._renderedChildrenUnits=this._renderedChildrenUnits.filter(item=>item!=oldChild);//还要把这个节点地应的事件委托也删除掉$(document).undelegate(`.${oldChild._reactid}`);}}}getNewChildren(oldChildrenUnitMap,newChildrenElements){let newChildrenUnits =[];let newChildrenUnitMap = {};newChildrenElements.forEach((newElement,index)=>{//一定要给定key,千万不要让它走内的索引keylet newKey = (newElement.props&&newElement.props.key)||index.toString();let oldUnit = oldChildrenUnitMap[newKey];//找到老的unitlet oldElement = oldUnit&&oldUnit._currentElement;//获取老元素if(shouldDeepCompare(oldElement,newElement)){oldUnit.update(newElement);newChildrenUnits.push(oldUnit);newChildrenUnitMap[newKey]=oldUnit;}else{let nextUnit = createUnit(newElement);newChildrenUnits.push(nextUnit);newChildrenUnitMap[newKey]=nextUnit;this._renderedChildrenUnits[index]=nextUnit;}});return {newChildrenUnitMap,newChildrenUnits};}getOldChildrenMap(childrenUnits=[]){let map = {};for(let i=0;i<childrenUnits.length;i++){let unit = childrenUnits[i];let key = (unit._currentElement.props&&unit._currentElement.props.key)||i.toString();map[key] = unit;}return map;}updateDOMProperties(oldProps,newProps){let propName;for(propName in oldProps){//循环老的属性集合if(!newProps.hasOwnProperty(propName)){$(`[data-reactid="${this._reactid}"]`).removeAttr(propName);}if(/^on[A-Z]/.test(propName)){$(document).undelegate(`.${this._reactid}`);}}for(propName in newProps){if(propName == 'children'){//如果儿子属性的话,我们先不处理continue;}else if(/^on[A-Z]/.test(propName)){let eventName = propName.slice(2).toLowerCase();//click$(document).delegate(`[data-reactid="${this._reactid}"]`,`${eventName}.${this._reactid}`,newProps[propName]);}else if(propName === 'className'){//如果是一个类名的话//$(`[data-reactid="${this._reactid}"]`)[0].className = newProps[propName];$(`[data-reactid="${this._reactid}"]`).attr('class',newProps[propName]);}else if(propName == 'style'){let styleObj = newProps[propName];Object.entries(styleObj).map(([attr,value])=>{$(`[data-reactid="${this._reactid}"]`).css(attr,value);})}else{$(`[data-reactid="${this._reactid}"]`).prop(propName,newProps[propName]);}}}}// dom.dataset.reactid $(dom).data('reactid');class CompositeUnit extends Unit{//这里负责处理组件的更新操作update(nextElement,partialState){//先获取到新的元素this._currentElement = nextElement||this._currentElement;//获取新的状态,不管要不要更新组件,组件的状态一定要修改let nextState =Object.assign(this._componentInstance.state,partialState);//新的属性对象let nextProps = this._currentElement.props;if(this._componentInstance.shouldComponentUpdate&&!this._componentInstance.shouldComponentUpdate(nextProps,nextState)){return;}// 下面要进行比较更新 先得到上次渲染的单元let preRenderedUnitInstance = this._renderedUnitInstance;//得到上次渲染的元素let preRenderedElement = preRenderedUnitInstance._currentElement;let nextRenderElement = this._componentInstance.render();//如果新旧两个元素类型一样,则可以进行深度比较,如果不一样,直接干掉老的元素,新建新的if(shouldDeepCompare(preRenderedElement,nextRenderElement)){//如果可以进行深比较,则把更新的工作交给上次渲染出来的那个element元素对应的unit来处理preRenderedUnitInstance.update(nextRenderElement);this._componentInstance.componentDidUpdate&&this._componentInstance.componentDidUpdate();}else{this._renderedUnitInstance = createUnit(nextRenderElement);let nextMarkUp = this._renderedUnitInstance.getMarkUp();$(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp);}}getMarkUp(reactid){this._reactid = reactid;let {type:Component,props} = this._currentElement;let componentInstance = this._componentInstance = new Component(props);//让组件的实例的currentUnit属性等于当前的unitcomponentInstance._currentUnit = this;//如果有组件将要渲染的函数的话让它执行componentInstance.componentWillMount&&componentInstance.componentWillMount();//调用组件的render方法,获得要渲染的元素let renderedElement =componentInstance.render();//0//得到这个元素对应的unitlet renderedUnitInstance = this._renderedUnitInstance = createUnit(renderedElement);//通过unit可以获得它的html 标记markuplet renderedMarkUp = renderedUnitInstance.getMarkUp(this._reactid);//在这个时候绑定一个事件$(document).on('mounted',()=>{componentInstance.componentDidMount&&componentInstance.componentDidMount();});return renderedMarkUp;}}//判断两个元素的类型一样不一样function shouldDeepCompare(oldElement,newElement){if(oldElement != null && newElement !=null){let oldType = typeof oldElement;let newType = typeof newElement;if((oldType==='string'||oldType=='number')&&(newType==='string'||newType=='number')){return true;}if(oldElement instanceof Element && newElement instanceof Element){return oldElement.type == newElement.type;}}return false;}function createUnit(element){if(typeof element === 'string' || typeof element === 'number'){return new TextUnit(element);}if(element instanceof Element && typeof element.type === 'string'){return new NativeUnit(element);}if(element instanceof Element && typeof element.type === 'function'){return new CompositeUnit(element);}}export {createUnit}
