react.js

    1. import $ from 'jquery';
    2. import {createUnit} from './unit';
    3. import {createElement} from './element';
    4. import {Component} from './component';
    5. let React = {
    6. render,
    7. createElement,
    8. Component
    9. }
    10. //此元素可能是一个文本节点、DOM节点(div)、或者 自定义组件Counter
    11. function render(element,container){
    12. //container.innerHTML=`<span data-reactid="${React.rootIndex}">${element}</span>`;
    13. //unit单元就是用来负责渲染的,负责把元素转换成可以在页面上显示的HTML字符串
    14. let unit = createUnit(element);
    15. let markUp = unit.getMarkUp('0');//用来返回HTML标记
    16. $(container).html(markUp);
    17. $(document).trigger('mounted');
    18. }
    19. export default React

    // component.js

    1. table File 12 lines (12 sloc) 261 Bytes
    2. class Component{
    3. constructor(props){
    4. this.props = props;
    5. }
    6. setState(partialState){
    7. //第一个参数是新的元素 第二个参数是新的状态
    8. this._currentUnit.update(null,partialState);
    9. }
    10. }
    11. export {
    12. Component
    13. }

    // element,js

    1. class Element{
    2. constructor(type,props){
    3. this.type = type;
    4. this.props = props;
    5. }
    6. }
    7. function createElement(type,props={},...children){
    8. props.children = children||[];//children也是props的一个属性
    9. return new Element(type,props);
    10. }
    11. export {
    12. Element,
    13. createElement
    14. }

    // types.js

    1. export default {
    2. MOVE:'MOVE', //移动
    3. INSERT:"INSERT", //插入
    4. REMOVE:'REMOVE' //删除
    5. }

    // unit.js

    1. import {Element,createElement} from './element';
    2. import $ from 'jquery';
    3. import types from './types';
    4. let diffQueue=[];//差异队列
    5. let updateDepth=0;//更新的级别
    6. class Unit{
    7. constructor(element){
    8. //凡是挂载到私有属性上的都以_开头
    9. this._currentElement = element;
    10. }
    11. getMarkUp(){
    12. throw Error('此方法不能被调用');
    13. }
    14. }
    15. class TextUnit extends Unit{
    16. getMarkUp(reactid){
    17. this._reactid = reactid;
    18. return `<span data-reactid="${reactid}">${this._currentElement}</span>`;
    19. }
    20. update(nextElement){
    21. if(this._currentElement !== nextElement){
    22. this._currentElement = nextElement;
    23. $(`[data-reactid="${this._reactid}"]`).html(this._currentElement);
    24. }
    25. }
    26. }
    27. /**
    28. {type:'button',props:{id:'sayHello'},children:['say',{type:'b',{},'Hello'}]}
    29. <button id="sayHello" style="color:red;background-color:'green" onclick="sayHello()">
    30. <span>say</span>
    31. <b>Hello</b>
    32. </button>
    33. */
    34. class NativeUnit extends Unit{
    35. getMarkUp(reactid){
    36. this._reactid = reactid;
    37. let {type,props} = this._currentElement;
    38. let tagStart = `<${type} data-reactid="${this._reactid}"`;
    39. let childString= '';
    40. let tagEnd = `</${type}>`;
    41. this._renderedChildrenUnits=[];
    42. //{id:'sayHello',onClick:sayHello,style:{color:'red',backgroundColor:'green'}},children:['say',{type:'b',{},'Hello'}]
    43. for(let propName in props){
    44. if(/^on[A-Z]/.test(propName)){//这说明要绑定事件了
    45. let eventName = propName.slice(2).toLowerCase();//click
    46. $(document).delegate(`[data-reactid="${this._reactid}"]`,`${eventName}.${this._reactid}`,props[propName]);
    47. }else if(propName === 'style'){//如果是一个样式对象
    48. let styleObj = props[propName];
    49. let styles = Object.entries(styleObj).map(([attr,value])=>{
    50. return `${attr.replace(/[A-Z]/g,m=>`-${m.toLowerCase()}`)}:${value}`;
    51. }).join(';');
    52. tagStart+=(` style="${styles}" `);
    53. }else if(propName === 'className'){//如果是一个类名的话
    54. tagStart+=(` class="${props[propName]}" `);
    55. }else if(propName == 'children'){
    56. let children = props[propName];
    57. children.forEach((child,index)=>{
    58. let childUnit = createUnit(child);//可能是一个字符中,也可以也是一个react元素 虚拟DOM
    59. childUnit._mountIndex = index;//每个unit有一个_mountIndex 属性,指向自己在父节点中的索引位置
    60. this._renderedChildrenUnits.push(childUnit);
    61. let childMarkUp = childUnit.getMarkUp(`${this._reactid}.${index}`);
    62. childString+=childMarkUp;
    63. });
    64. }else{
    65. tagStart += (` ${propName}=${props[propName]} `);
    66. }
    67. }
    68. return tagStart+">"+childString+tagEnd;
    69. }
    70. update(nextElement){
    71. let oldProps = this._currentElement.props;
    72. let newProps = nextElement.props;
    73. this.updateDOMProperties(oldProps,newProps);
    74. this.updateDOMChildren(nextElement.props.children);
    75. }
    76. //此处要把新的儿子们传过来,然后后我老的儿子们进行对比,然后找出差异,进行修改DOM
    77. updateDOMChildren(newChildrenElements){
    78. updateDepth++;
    79. this.diff(diffQueue,newChildrenElements);
    80. updateDepth--;
    81. if(updateDepth === 0){
    82. console.log(diffQueue)
    83. this.patch(diffQueue);
    84. diffQueue = [];
    85. }
    86. }
    87. patch(diffQueue){
    88. let deleteChildren = [];//这里要放着所有将要删除的节点
    89. let deleteMap = {};//这里暂存能复用的节点
    90. for(let i=0;i<diffQueue.length;i++){
    91. let difference = diffQueue[i];
    92. if(difference.type === types.MOVE || difference.type === types.REMOVE){
    93. let fromIndex = difference.fromIndex;
    94. let oldChild = $(difference.parentNode.children().get(fromIndex));
    95. if(!deleteMap[difference.parentId]){
    96. deleteMap[difference.parentId]={}
    97. }
    98. deleteMap[difference.parentId][fromIndex]=oldChild;
    99. deleteChildren.push(oldChild);
    100. }
    101. }
    102. $.each(deleteChildren,(idx,item)=>$(item).remove());
    103. for(let i=0;i<diffQueue.length;i++){
    104. let difference = diffQueue[i];
    105. switch(difference.type){
    106. case types.INSERT:
    107. this.insertChildAt(difference.parentNode,difference.toIndex,$(difference.markUp));
    108. break;
    109. case types.MOVE:
    110. this.insertChildAt(difference.parentNode,difference.toIndex,deleteMap[difference.parentId][difference.fromIndex]);
    111. break;
    112. default:
    113. break;
    114. }
    115. }
    116. }
    117. insertChildAt(parentNode,index,newNode){
    118. let oldChild = parentNode.children().get(index);
    119. oldChild?newNode.insertBefore(oldChild):newNode.appendTo(parentNode);
    120. }
    121. diff(diffQueue,newChildrenElements){
    122. //第一生成一个map,key=老的unit
    123. let oldChildrenUnitMap = this.getOldChildrenMap(this._renderedChildrenUnits);
    124. //第二步生成一个新的儿子unit的数组
    125. let {newChildrenUnitMap,newChildrenUnits} = this.getNewChildren(oldChildrenUnitMap,newChildrenElements);
    126. let lastIndex = 0;//上一个已经确定位置的索引
    127. for(let i=0;i<newChildrenUnits.length;i++){
    128. let newUnit = newChildrenUnits[i];
    129. //第一个拿 到的就是newKey=A
    130. let newKey = (newUnit._currentElement.props&&newUnit._currentElement.props.key)||i.toString();
    131. let oldChildUnit = oldChildrenUnitMap[newKey];
    132. if(oldChildUnit === newUnit){//如果说新老一致的话说明复用了老节点
    133. if(oldChildUnit._mountIndex < lastIndex){
    134. diffQueue.push({
    135. parentId:this._reactid,
    136. parentNode:$(`[data-reactid="${this._reactid}"]`),
    137. type:types.MOVE,
    138. fromIndex:oldChildUnit._mountIndex,
    139. toIndex:i
    140. });
    141. }
    142. lastIndex = Math.max(lastIndex,oldChildUnit._mountIndex);
    143. }else{
    144. if(oldChildUnit){
    145. diffQueue.push({
    146. parentId:this._reactid,
    147. parentNode:$(`[data-reactid="${this._reactid}"]`),
    148. type:types.REMOVE,
    149. fromIndex:oldChildUnit._mountIndex
    150. });
    151. this._renderedChildrenUnits=this._renderedChildrenUnits.filter(item=>item!=oldChildUnit);
    152. $(document).undelegate(`.${oldChildUnit._reactid}`);
    153. }
    154. diffQueue.push({
    155. parentId:this._reactid,
    156. parentNode:$(`[data-reactid="${this._reactid}"]`),
    157. type:types.INSERT,
    158. toIndex:i,
    159. markUp:newUnit.getMarkUp(`${this._reactid}.${i}`)
    160. });
    161. }
    162. newUnit._mountIndex = i;
    163. }
    164. for(let oldKey in oldChildrenUnitMap){
    165. let oldChild = oldChildrenUnitMap[oldKey];
    166. if(!newChildrenUnitMap.hasOwnProperty(oldKey)){
    167. diffQueue.push({
    168. parentId:this._reactid,
    169. parentNode:$(`[data-reactid="${this._reactid}"]`),
    170. type:types.REMOVE,
    171. fromIndex:oldChild._mountIndex
    172. });
    173. //如果要删除掉某一个节点,则要把它对应的unit也删除掉
    174. this._renderedChildrenUnits=this._renderedChildrenUnits.filter(item=>item!=oldChild);
    175. //还要把这个节点地应的事件委托也删除掉
    176. $(document).undelegate(`.${oldChild._reactid}`);
    177. }
    178. }
    179. }
    180. getNewChildren(oldChildrenUnitMap,newChildrenElements){
    181. let newChildrenUnits =[];
    182. let newChildrenUnitMap = {};
    183. newChildrenElements.forEach((newElement,index)=>{
    184. //一定要给定key,千万不要让它走内的索引key
    185. let newKey = (newElement.props&&newElement.props.key)||index.toString();
    186. let oldUnit = oldChildrenUnitMap[newKey];//找到老的unit
    187. let oldElement = oldUnit&&oldUnit._currentElement;//获取老元素
    188. if(shouldDeepCompare(oldElement,newElement)){
    189. oldUnit.update(newElement);
    190. newChildrenUnits.push(oldUnit);
    191. newChildrenUnitMap[newKey]=oldUnit;
    192. }else{
    193. let nextUnit = createUnit(newElement);
    194. newChildrenUnits.push(nextUnit);
    195. newChildrenUnitMap[newKey]=nextUnit;
    196. this._renderedChildrenUnits[index]=nextUnit;
    197. }
    198. });
    199. return {newChildrenUnitMap,newChildrenUnits};
    200. }
    201. getOldChildrenMap(childrenUnits=[]){
    202. let map = {};
    203. for(let i=0;i<childrenUnits.length;i++){
    204. let unit = childrenUnits[i];
    205. let key = (unit._currentElement.props&&unit._currentElement.props.key)||i.toString();
    206. map[key] = unit;
    207. }
    208. return map;
    209. }
    210. updateDOMProperties(oldProps,newProps){
    211. let propName;
    212. for(propName in oldProps){//循环老的属性集合
    213. if(!newProps.hasOwnProperty(propName)){
    214. $(`[data-reactid="${this._reactid}"]`).removeAttr(propName);
    215. }
    216. if(/^on[A-Z]/.test(propName)){
    217. $(document).undelegate(`.${this._reactid}`);
    218. }
    219. }
    220. for(propName in newProps){
    221. if(propName == 'children'){//如果儿子属性的话,我们先不处理
    222. continue;
    223. }else if(/^on[A-Z]/.test(propName)){
    224. let eventName = propName.slice(2).toLowerCase();//click
    225. $(document).delegate(`[data-reactid="${this._reactid}"]`,`${eventName}.${this._reactid}`,newProps[propName]);
    226. }else if(propName === 'className'){//如果是一个类名的话
    227. //$(`[data-reactid="${this._reactid}"]`)[0].className = newProps[propName];
    228. $(`[data-reactid="${this._reactid}"]`).attr('class',newProps[propName]);
    229. }else if(propName == 'style'){
    230. let styleObj = newProps[propName];
    231. Object.entries(styleObj).map(([attr,value])=>{
    232. $(`[data-reactid="${this._reactid}"]`).css(attr,value);
    233. })
    234. }else{
    235. $(`[data-reactid="${this._reactid}"]`).prop(propName,newProps[propName]);
    236. }
    237. }
    238. }
    239. }
    240. // dom.dataset.reactid $(dom).data('reactid');
    241. class CompositeUnit extends Unit{
    242. //这里负责处理组件的更新操作
    243. update(nextElement,partialState){
    244. //先获取到新的元素
    245. this._currentElement = nextElement||this._currentElement;
    246. //获取新的状态,不管要不要更新组件,组件的状态一定要修改
    247. let nextState =Object.assign(this._componentInstance.state,partialState);
    248. //新的属性对象
    249. let nextProps = this._currentElement.props;
    250. if(this._componentInstance.shouldComponentUpdate&&!this._componentInstance.shouldComponentUpdate(nextProps,nextState)){
    251. return;
    252. }
    253. // 下面要进行比较更新 先得到上次渲染的单元
    254. let preRenderedUnitInstance = this._renderedUnitInstance;
    255. //得到上次渲染的元素
    256. let preRenderedElement = preRenderedUnitInstance._currentElement;
    257. let nextRenderElement = this._componentInstance.render();
    258. //如果新旧两个元素类型一样,则可以进行深度比较,如果不一样,直接干掉老的元素,新建新的
    259. if(shouldDeepCompare(preRenderedElement,nextRenderElement)){
    260. //如果可以进行深比较,则把更新的工作交给上次渲染出来的那个element元素对应的unit来处理
    261. preRenderedUnitInstance.update(nextRenderElement);
    262. this._componentInstance.componentDidUpdate&&this._componentInstance.componentDidUpdate();
    263. }else{
    264. this._renderedUnitInstance = createUnit(nextRenderElement);
    265. let nextMarkUp = this._renderedUnitInstance.getMarkUp();
    266. $(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp);
    267. }
    268. }
    269. getMarkUp(reactid){
    270. this._reactid = reactid;
    271. let {type:Component,props} = this._currentElement;
    272. let componentInstance = this._componentInstance = new Component(props);
    273. //让组件的实例的currentUnit属性等于当前的unit
    274. componentInstance._currentUnit = this;
    275. //如果有组件将要渲染的函数的话让它执行
    276. componentInstance.componentWillMount&&componentInstance.componentWillMount();
    277. //调用组件的render方法,获得要渲染的元素
    278. let renderedElement =componentInstance.render();//0
    279. //得到这个元素对应的unit
    280. let renderedUnitInstance = this._renderedUnitInstance = createUnit(renderedElement);
    281. //通过unit可以获得它的html 标记markup
    282. let renderedMarkUp = renderedUnitInstance.getMarkUp(this._reactid);
    283. //在这个时候绑定一个事件
    284. $(document).on('mounted',()=>{
    285. componentInstance.componentDidMount&&componentInstance.componentDidMount();
    286. });
    287. return renderedMarkUp;
    288. }
    289. }
    290. //判断两个元素的类型一样不一样
    291. function shouldDeepCompare(oldElement,newElement){
    292. if(oldElement != null && newElement !=null){
    293. let oldType = typeof oldElement;
    294. let newType = typeof newElement;
    295. if((oldType==='string'||oldType=='number')&&(newType==='string'||newType=='number')){
    296. return true;
    297. }
    298. if(oldElement instanceof Element && newElement instanceof Element){
    299. return oldElement.type == newElement.type;
    300. }
    301. }
    302. return false;
    303. }
    304. function createUnit(element){
    305. if(typeof element === 'string' || typeof element === 'number'){
    306. return new TextUnit(element);
    307. }
    308. if(element instanceof Element && typeof element.type === 'string'){
    309. return new NativeUnit(element);
    310. }
    311. if(element instanceof Element && typeof element.type === 'function'){
    312. return new CompositeUnit(element);
    313. }
    314. }
    315. export {
    316. createUnit
    317. }