react.js
import $ from 'jquery';
import {createUnit} from './unit';
import {createElement} from './element';
import {Component} from './component';
let React = {
render,
createElement,
Component
}
//此元素可能是一个文本节点、DOM节点(div)、或者 自定义组件Counter
function 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 Bytes
class 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元素 虚拟DOM
childUnit._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);
}
//此处要把新的儿子们传过来,然后后我老的儿子们进行对比,然后找出差异,进行修改DOM
updateDOMChildren(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=老的unit
let 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=A
let 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,千万不要让它走内的索引key
let newKey = (newElement.props&&newElement.props.key)||index.toString();
let oldUnit = oldChildrenUnitMap[newKey];//找到老的unit
let 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属性等于当前的unit
componentInstance._currentUnit = this;
//如果有组件将要渲染的函数的话让它执行
componentInstance.componentWillMount&&componentInstance.componentWillMount();
//调用组件的render方法,获得要渲染的元素
let renderedElement =componentInstance.render();//0
//得到这个元素对应的unit
let renderedUnitInstance = this._renderedUnitInstance = createUnit(renderedElement);
//通过unit可以获得它的html 标记markup
let 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
}