一个简易版的代码实现

schedule

  1. import { TAG_ROOT, TAG_TEXT, TAG_HOST, ELEMENT_TEXT, PLACEMENT, DELETION, UPDATE, TAG_CLASS, TAG_FUNCTION_COMPONENT } from "./constant";
  2. import { setProps } from './utils';
  3. import { UpdateQueue, Update } from "./UpdateQueue";
  4. /**
  5. * 从根节点开始渲染和调度,
  6. * 两个阶段:
  7. * 1. diff阶段(render阶段):对比新旧的虚拟DOM,进行增量更新或者创建
  8. * 根据虚拟DOM进行任务拆分。这个阶段可以中断。
  9. * render阶段的任务: (1) 生成Fiber (2) 收集Effect List
  10. * 2. commit阶段,进行DOM更新
  11. */
  12. let nextUnitOfWork = null; // 下一个工作单元,
  13. let workInProgressRoot = null; // RootFiber 应用的根 // 正在渲染的根Root Fiber
  14. let currentRoot = null;
  15. let deletions = []; // 删除的节点,我们并不放在effect list里面, 所以需要单独记录并且执行。
  16. let workingProgressFiber = null; // 正在工作中的fiber
  17. let hookIndex = 0; // hooks 索引
  18. export function scheduleRoot(rootFiber) { // {tag: TAG_ROOT, stateNode:container, props:{children:[element]}}
  19. if (currentRoot && currentRoot.alternate) { // 第二次之后的更新
  20. workInProgressRoot = currentRoot.alternate; // 第一次渲染出来的那个fiber tree
  21. workInProgressRoot.alternate = currentRoot; // 让这个树的替身指向当前的currentRoot;
  22. if (rootFiber) {
  23. workInProgressRoot.props = rootFiber.props; // 让它的props更新成新的props
  24. }
  25. } else if (currentRoot) { // 说明至少已经渲染过一次了
  26. if (rootFiber) {
  27. rootFiber.alternate = currentRoot;
  28. workInProgressRoot = rootFiber; // 挂载后不变。
  29. } else {
  30. workInProgressRoot = {
  31. ...currentRoot,
  32. alternate: currentRoot
  33. }
  34. }
  35. } else { // 如果是第一次渲染
  36. workInProgressRoot = rootFiber; // 挂载后不变。
  37. }
  38. workInProgressRoot.firstEffect = workInProgressRoot.lastEffect = workInProgressRoot.nextEffect = null;
  39. nextUnitOfWork = workInProgressRoot; // 一直在变。
  40. }
  41. //
  42. function performUnitOfWork(currentFiber) {
  43. beginWork(currentFiber);
  44. // 由于经过beginWork的调和,如果currentFiber的props.children有值,就会在currentFiber加上了child,指向第一个Fiber子元素。
  45. if (currentFiber.child) {
  46. return currentFiber.child;
  47. }
  48. // 如果不存在child, 那么这个任务就已经完成了,需要继续查看sibling,
  49. while (currentFiber) {
  50. completeUnitOfWork(currentFiber);
  51. if (currentFiber.sibling) {
  52. return currentFiber.sibling;
  53. } else {
  54. currentFiber = currentFiber.return; // 先找到父亲,然后让父亲完成。
  55. }
  56. }
  57. }
  58. // 在完成的时候需要收集有副作用的Fiber,然后组成effect list
  59. // 每个fiber有两个属性,
  60. // firstEffect指向第一个有副作用的子Fiber
  61. // lastEffect指向最后一个有副作用的子Fiber
  62. // 中间的用nextEffect做成一个单链表
  63. function completeUnitOfWork(currentFiber) { // 第一个完成的A1TEXT
  64. console.log('收集副作用:', currentFiber.tag, currentFiber.stateNode);
  65. // 准备形成Effect List
  66. let returnFiber = currentFiber.return; // A1
  67. if (returnFiber) {
  68. // 1.把自己儿子的effect链挂到父亲身上
  69. if (!returnFiber.firstEffect) {
  70. returnFiber.firstEffect = currentFiber.firstEffect;
  71. }
  72. if (currentFiber.lastEffect) {
  73. if (returnFiber.lastEffect) {
  74. // TODO: 这边为什么是这样的???
  75. returnFiber.lastEffect.nextEffect = currentFiber.firstEffect;
  76. }
  77. returnFiber.lastEffect = currentFiber.lastEffect;
  78. }
  79. // 2. 把自己挂到父亲身上
  80. const effectTag = currentFiber.effectTag;
  81. if (effectTag) {// 自己有副作用
  82. if (returnFiber.lastEffect) {
  83. returnFiber.lastEffect.nextEffect = currentFiber;
  84. } else {
  85. returnFiber.firstEffect = currentFiber;
  86. }
  87. returnFiber.lastEffect = currentFiber;
  88. }
  89. }
  90. }
  91. /**
  92. * beiginWork
  93. * 1. 创建真实DOM元素
  94. * 2. 创建子Fiber
  95. */
  96. function beginWork(currentFiber) {
  97. if (currentFiber.tag === TAG_ROOT) { // 根fiber
  98. updateHostRoot(currentFiber);
  99. } else if (currentFiber.tag === TAG_TEXT) { // 文本fiber
  100. updateHostText(currentFiber);
  101. } else if (currentFiber.tag === TAG_HOST) { // 原生DOM节点
  102. updateHost(currentFiber);
  103. } else if (currentFiber.tag === TAG_CLASS) { // 类组件
  104. updateClassComponent(currentFiber);
  105. } else if (currentFiber.tag === TAG_FUNCTION_COMPONENT) {
  106. updateFunctionComponent(currentFiber);
  107. }
  108. }
  109. /**
  110. * 因为是根Fiber, 所以本身就存在真实DOM元素 #root;
  111. * 直接开始遍历children,创建子Fiber;
  112. */
  113. function updateHostRoot(currentFiber) {
  114. let newChildren = currentFiber.props.children; // [element]
  115. reconcileChildren(currentFiber, newChildren);
  116. }
  117. function updateHostText(currentFiber) {
  118. // 如果此Fiber没有创建DOM节点,那么就需要创建
  119. if (!currentFiber.stateNode) {
  120. currentFiber.stateNode = createDOM(currentFiber);
  121. }
  122. }
  123. function updateHost(currentFiber) {
  124. if (!currentFiber.stateNode) {
  125. currentFiber.stateNode = createDOM(currentFiber);
  126. }
  127. let newChildren = currentFiber.props.children; // [element]
  128. reconcileChildren(currentFiber, newChildren);
  129. }
  130. function updateClassComponent(currentFiber) {
  131. if (!currentFiber.stateNode) { // 类组件 stateNode 是组件的实例
  132. // new ClassCounter();
  133. // fiber和类组件实例 双向指向
  134. currentFiber.stateNode = new currentFiber.type(currentFiber.props);
  135. currentFiber.stateNode.internalFiber = currentFiber;
  136. currentFiber.updateQueue = new UpdateQueue();
  137. }
  138. // 给组件的实例的state赋值
  139. currentFiber.stateNode.state = currentFiber.updateQueue.forceUpdate(currentFiber.stateNode.state);
  140. let newElement = currentFiber.stateNode.render();
  141. const newChildren = [newElement];
  142. reconcileChildren(currentFiber, newChildren);
  143. }
  144. function updateFunctionComponent(currentFiber) {
  145. workingProgressFiber = currentFiber;
  146. hookIndex = 0;
  147. workingProgressFiber.hooks = [];
  148. const newChildren = [currentFiber.type(currentFiber.props)]
  149. reconcileChildren(currentFiber, newChildren);
  150. }
  151. function createDOM(currentFiber) {
  152. if (currentFiber.tag === TAG_TEXT) {
  153. return document.createTextNode(currentFiber.props.text);
  154. } else if (currentFiber.tag === TAG_HOST) {
  155. let stateNode = document.createElement(currentFiber.type);
  156. updateDOM(stateNode, {}, currentFiber.props);
  157. return stateNode;
  158. }
  159. }
  160. function updateDOM(stateNode, oldProps, newProps) {
  161. if (stateNode && stateNode.setAttribute) {
  162. setProps(stateNode, oldProps, newProps);
  163. }
  164. }
  165. // newChildren是一个虚拟DOM的数组, 把虚拟DOM转成Fiber
  166. function reconcileChildren(currentFiber, newChildren) {
  167. let newChildIndex = 0; // 新子节点的索引,
  168. let prevSibling;
  169. // 如果说currentFiber有alternate并且alternate有child属性;
  170. let oldFiber = currentFiber.alternate && currentFiber.alternate.child;
  171. if (oldFiber) {
  172. oldFiber.firstEffect = oldFiber.lastEffect = oldFiber.nextEffect = null;
  173. }
  174. while (newChildIndex < newChildren.length || oldFiber) {
  175. let newChild = newChildren[newChildIndex]; // 取出虚拟DOM节点。
  176. let tag;
  177. let newFiber; // 新的Fiber;
  178. const sameType = oldFiber && newChild && oldFiber.type === newChild.type;
  179. if (newChild && newChild.type === ELEMENT_TEXT) {
  180. // 文本节点
  181. tag = TAG_TEXT;
  182. } else if (newChild && typeof newChild.type === 'string') {
  183. //原生dom节点 div p span
  184. tag = TAG_HOST;
  185. } else if (newChild && typeof newChild.type === 'function' && newChild.type.prototype.isReactComponent) {
  186. tag = TAG_CLASS;
  187. } else if (newChild && typeof newChild.type === 'function') {
  188. tag = TAG_FUNCTION_COMPONENT;
  189. }
  190. if (sameType) { // 说明老fiber和新虚拟DOM类型是一样的, 可以直接复用老的DOM节点,更新即可。
  191. if (oldFiber.alternate) { // 说明至少已经更新一次了,复用,减少对象的创建。
  192. // 上上棵树的tag,type等属性肯定是一样的, 因为alternate的赋值是在sameType中。
  193. newFiber = oldFiber.alternate;
  194. newFiber.props = newChild.props;
  195. newFiber.alternate = oldFiber;
  196. newFiber.effectTag = UPDATE;
  197. newFiber.nextEffect = null;
  198. newFiber.updateQueue = oldFiber.updateQueue || new UpdateQueue();
  199. //
  200. // newFiber.stateNode = oldFiber.stateNode; //
  201. // newFiber.return = currentFiber; // 父Fiber,
  202. } else {
  203. newFiber = {
  204. tag: oldFiber.tag,
  205. type: oldFiber.type,
  206. props: newChild.props,
  207. stateNode: oldFiber.stateNode, //
  208. return: currentFiber, // 父Fiber,
  209. effectTag: UPDATE, // 副作用标识, render我们要收集副作用,
  210. alternate: oldFiber,
  211. nextEffect: null,
  212. updateQueue: oldFiber.updateQueue || new UpdateQueue()
  213. }
  214. }
  215. } else {
  216. // 如果两两比较时候不一样,那就删除老的,添加新的。
  217. if (newChild) {
  218. newFiber = {
  219. tag,
  220. type: newChild.type,
  221. props: newChild.props,
  222. stateNode: null,
  223. return: currentFiber, // 父Fiber,
  224. effectTag: PLACEMENT, // 副作用标识, render我们要收集副作用,
  225. nextEffect: null,
  226. updateQueue: new UpdateQueue()
  227. }
  228. }
  229. if (oldFiber) {
  230. oldFiber.effectTag = DELETION;
  231. deletions.push(oldFiber);
  232. }
  233. }
  234. if (oldFiber) {
  235. oldFiber = oldFiber.sibling;
  236. }
  237. if (newFiber) {
  238. if (newChildIndex === 0) {
  239. currentFiber.child = newFiber;
  240. } else {
  241. prevSibling.sibling = newFiber;
  242. }
  243. prevSibling = newFiber;
  244. }
  245. newChildIndex++;
  246. }
  247. }
  248. // 循环执行工作
  249. function workLoop(deadline) {
  250. let shouldYield = false;
  251. while (nextUnitOfWork && !shouldYield) {
  252. nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
  253. shouldYield = deadline.timeRemaining() < 1; // 没有时间的话就要让出控制权。
  254. }
  255. if (!nextUnitOfWork && workInProgressRoot) {
  256. console.log('render阶段结束');
  257. console.log("最终形成的Fiber链:", workInProgressRoot);
  258. commitRoot();
  259. } else {
  260. }
  261. // 如果时间片到期后还有任务没有完成, 就需要请求浏览器再次调度。
  262. requestIdleCallback(workLoop, { timeout: 500 });
  263. }
  264. function commitRoot() {
  265. console.log('commitRoot', workInProgressRoot);
  266. deletions.forEach(commitWork); // 执行effect list之前,先把该删除的元素删除
  267. let currentFiber = workInProgressRoot.firstEffect;
  268. while (currentFiber) {
  269. commitWork(currentFiber);
  270. currentFiber = currentFiber.nextEffect;
  271. }
  272. deletions.length = 0; // 提交之后要清空deletion数组
  273. currentRoot = workInProgressRoot; // 把当前渲染成功的根Fiber赋给currentRoot;
  274. workInProgressRoot = null;
  275. }
  276. function commitWork(currentFiber) {
  277. if (!currentFiber) return;
  278. let returnFiber = currentFiber.return;
  279. while (
  280. returnFiber.tag !== TAG_HOST &&
  281. returnFiber.tag !== TAG_ROOT &&
  282. returnFiber.tag !== TAG_TEXT
  283. ) {
  284. returnFiber = returnFiber.return;
  285. }
  286. let returnDOM = returnFiber.stateNode;
  287. if (currentFiber.effectTag === PLACEMENT) { // 新增节点
  288. let nextFiber = currentFiber;
  289. // 优化: 如果是类组件,其实可以直接return
  290. if (nextFiber.tag === TAG_CLASS) {
  291. return;
  292. }
  293. // 如果要挂载的节点不是DOM节点,比如说是类组件Fiber,一直找第一个儿子,直到找到一个真实DOM节点为止。
  294. while (
  295. nextFiber.tag !== TAG_HOST &&
  296. nextFiber.tag !== TAG_ROOT &&
  297. nextFiber.tag !== TAG_TEXT
  298. ) {
  299. nextFiber = nextFiber.child;
  300. }
  301. returnDOM.appendChild(nextFiber.stateNode);
  302. } else if (currentFiber.effectTag === DELETION) {// 删除节点
  303. return commitDeletion(currentFiber, returnDOM);
  304. // returnDOM.removeChild(currentFiber.stateNode);
  305. } else if (currentFiber.effectTag === UPDATE) {
  306. if (currentFiber.type === ELEMENT_TEXT) {
  307. if (currentFiber.alternate.props.text != currentFiber.props.text) {
  308. currentFiber.stateNode.textContent = currentFiber.props.text;
  309. } else {
  310. updateDOM(currentFiber.stateNode, currentFiber.alternate.props, currentFiber.props);
  311. }
  312. }
  313. }
  314. currentFiber.effectTag = null;
  315. }
  316. function commitDeletion(currentFiber, domReturn) {
  317. if (currentFiber.tag == TAG_HOST || currentFiber.tag == TAG_TEXT) {
  318. domReturn.removeChild(currentFiber.stateNode);
  319. } else {
  320. commitDeletion(currentFiber.child, domReturn);
  321. }
  322. }
  323. /**
  324. *
  325. * workingProgressFiber = currentFiber;
  326. * hookIndex = 0;
  327. * workingProgressFiber.hooks = [];
  328. */
  329. export function useReducer(reducer, initialValue) {
  330. let newHook = workingProgressFiber.alternate && workingProgressFiber.alternate.hooks && workingProgressFiber.alternate.hooks[hookIndex];
  331. if (newHook) {
  332. console.log(hookIndex, newHook);
  333. // 第二次渲染
  334. newHook.state = newHook.updateQueue.forceUpdate(newHook.state);
  335. } else {
  336. newHook = {
  337. state: initialValue,
  338. updateQueue: new UpdateQueue()
  339. }
  340. }
  341. // const dispatch = null;
  342. const dispatch = action => { // {type: 'ADD'}
  343. let payload = reducer ? reducer(newHook.state, action) : action;
  344. newHook.updateQueue.enqueueUpdate(
  345. new Update(payload)
  346. );
  347. scheduleRoot();
  348. }
  349. workingProgressFiber.hooks[hookIndex++] = newHook;
  350. return [newHook.state, dispatch];
  351. }
  352. export function useState (initialValue){
  353. return useReducer(null, initialValue);
  354. }
  355. requestIdleCallback(workLoop, { timeout: 500 });

UpdateQueue

  1. export class Update {
  2. constructor(payload) {
  3. this.payload = payload
  4. }
  5. }
  6. // 数据结构:是一个单链表
  7. export class UpdateQueue {
  8. constructor() {
  9. this.firstUpdate = null;
  10. this.lastUpdate = null;
  11. }
  12. enqueueUpdate(update) {
  13. if (this.lastUpdate === null) {
  14. this.firstUpdate = this.lastUpdate = update;
  15. } else {
  16. this.lastUpdate.nextUpdate = update;
  17. this.lastUpdate = update;
  18. }
  19. }
  20. forceUpdate(state) {
  21. let currentUpdate = this.firstUpdate;
  22. // TODO:
  23. while(currentUpdate){
  24. console.log(currentUpdate,'~~~~')
  25. let nextState = typeof currentUpdate.payload === 'function' ? currentUpdate.payload(state) : currentUpdate.payload;
  26. state = {...state, ...nextState};
  27. currentUpdate = currentUpdate.nextUpdate;
  28. }
  29. this.firstUpdate = this.lastUpdate = null;
  30. return state;
  31. }
  32. }

react-dom

  1. import { TAG_ROOT } from "./constant";
  2. import { scheduleRoot} from './schedule';
  3. function render(element, container){
  4. let rootFiber = {
  5. tag:TAG_ROOT, // 每个Fiber会有一个tag标识此元素的类型
  6. stateNode: container, // 指向真实的DOM元素
  7. props: {
  8. children: [element] // 放置需要渲染的React元素,即虚拟DOM元素。
  9. }
  10. }
  11. scheduleRoot(rootFiber);
  12. }
  13. const ReactDOM = {
  14. render
  15. }
  16. export default ReactDOM;

react

  1. import { ELEMENT_TEXT } from "./constant";
  2. import { UpdateQueue, Update} from './UpdateQueue';
  3. import { scheduleRoot, useReducer, useState } from "./schedule";
  4. /**
  5. * 创建元素(虚拟DOM)
  6. * @param {} type 元素的类型 div span p
  7. * @param {*} config 配置对象 属性 key ref
  8. * @param {...any} children 放着所有的儿子,做成一个数组。
  9. */
  10. function createElement(type,config, ...children){
  11. // 删除不必要的属性,简化代码
  12. delete config.__self;
  13. delete config.__source;
  14. return {
  15. type,
  16. props: {
  17. ...config,
  18. children:children.map(child => {
  19. return typeof child === 'object'? child: {
  20. type: ELEMENT_TEXT,
  21. props: {
  22. text: child,
  23. children: []
  24. }
  25. }
  26. })
  27. }
  28. }
  29. }
  30. class Component {
  31. constructor(props){
  32. this.props = props;
  33. this.updateQueue = new UpdateQueue();
  34. }
  35. setState(payload){ //可能是对象,也可能是一个函数
  36. let update = new Update(payload);
  37. // updateQueue其实是放在此类组件对应的fiber节点的internalFiber;
  38. this.internalFiber.updateQueue.enqueueUpdate(update);
  39. // this.updateQueue.enqueueUpdate(update);
  40. scheduleRoot(); // 从根节点开始调度;
  41. }
  42. }
  43. Component.prototype.isReactComponent = {}; // 类组件
  44. const React = {
  45. createElement,
  46. Component,
  47. useReducer,
  48. useState
  49. }
  50. export default React;