├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── src ├── reactCode │ ├── ReactSymbols.js │ ├── utils.js │ ├── ReactWorkTags.js │ ├── ReactFiberFlags.js │ ├── ReactFiberReconciler.js │ ├── ReactDOMHostConfig.js │ ├── ReactDOM.js │ ├── ReactUpdateQuene.js │ ├── React.js │ ├── ReactFiberCommitWork.js │ ├── ReactFiber.js │ ├── ReactFiberCompleteWork.js │ ├── ReactFiberHooks.js │ ├── ReactDOMComponent.js │ ├── ReactFiberBeginWork.js │ ├── ReactFiberWorkLoop.js │ └── ReactChildFiber.js └── index.js ├── .eslintrc.js ├── .gitignore ├── README.md ├── package.json └── docs ├── hook.md └── vdom_diff.md /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forturegrant/mini-react/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forturegrant/mini-react/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forturegrant/mini-react/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/reactCode/ReactSymbols.js: -------------------------------------------------------------------------------- 1 | export const REACT_ELEMENT_TYPE = Symbol.for('react.element'); 2 | -------------------------------------------------------------------------------- /src/reactCode/utils.js: -------------------------------------------------------------------------------- 1 | export function isFn() { 2 | return typeof sth === "function"; 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'react-hooks/rules-of-hooks': 'off', 4 | }, 5 | } -------------------------------------------------------------------------------- /src/reactCode/ReactWorkTags.js: -------------------------------------------------------------------------------- 1 | // fiber的根节点,它其实对应的DOM节点就是containerInfo div#root 2 | export const HostRoot = 3; 3 | // 原生组件 4 | export const HostComponent = 6; 5 | // 函数组件 6 | export const FunctionComponent = 0; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 启动 2 | 3 | 直接运行npm start,访问 http://localhost:3000 即可 4 | 5 | ### 目录划分 6 | - docs。react 相关知识文档&源码剖析目录 7 | - src。代码实现 8 | - reactCode。手写 react 源码目录,对应的官方 react 版本为 16.0.1 9 | 10 | ### React源码文档 11 | - [简单实现一个自己的react,了解虚拟dom,fiber和react diff算法](https://github.com/forturegrant/mini-react/blob/master/docs/vdom_diff.md) 12 | - [hook的原理,和useState和useReducer的简单实现](https://github.com/forturegrant/mini-react/blob/master/docs/hook.md) 13 | -------------------------------------------------------------------------------- /src/reactCode/ReactFiberFlags.js: -------------------------------------------------------------------------------- 1 | export const NoFlags = /* */ 0b0000000000000000000000000000;// 二进制0 没有动作 2 | export const Placement = /* */ 0b0000000000000000000000000010;// 二进制2 添加或者创建挂载 3 | export const Update = /* */ 0b0000000000000000000000000100;// 二进制4 更新 4 | export const PlacementAndUpdate = /* */ 0b0000000000000000000000000110;// 二进制6 移动 5 | export const Deletion = /* */ 0b0000000000000000000000001000;// 二进制8 删除 6 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/reactCode/ReactFiberReconciler.js: -------------------------------------------------------------------------------- 1 | import { createUpdate, enqueneUpdate } from './ReactUpdateQuene'; 2 | import { scheduleUpdateOnFiber } from './ReactFiberWorkLoop'; 3 | 4 | /** 5 | * 把虚拟DOMelement变成真实dom插入或者说渲染到container 6 | * @param {*} element 7 | * @param {*} container 8 | */ 9 | export function updateContainer(element, container) { 10 | // 获取hostRootFiber fiber根的根节点 11 | // 正常来说一个fiber节点会对应一个真实的dom节点,hostRootFiber对应的DOM节点就是containerInfo div#root 12 | const current = container.current; 13 | const update = createUpdate(); 14 | update.payload = { element }; 15 | enqueneUpdate(current, update); 16 | scheduleUpdateOnFiber(current); 17 | } 18 | -------------------------------------------------------------------------------- /src/reactCode/ReactDOMHostConfig.js: -------------------------------------------------------------------------------- 1 | import { createElement, setInitialProperties, diffProperties } from './ReactDOMComponent'; 2 | export function shouldSetTextContent(type, props) { 3 | return typeof props.children === 'string' || typeof props.children === 'number'; 4 | } 5 | 6 | export function createInstance(type) { 7 | return createElement(type); 8 | } 9 | 10 | export function finalizeInitialChildren(domElement, type, props) { 11 | setInitialProperties(domElement, type, props) 12 | } 13 | 14 | export function appendChild(parentInstance, child) { 15 | parentInstance.appendChild(child); 16 | } 17 | 18 | export function insertBefore(parentInstance, child, before) { 19 | parentInstance.insertBefore(child, before); 20 | } 21 | 22 | export function removeChild(parentInstance, child) { 23 | parentInstance.removeChild(child); 24 | } 25 | 26 | export function prepareUpdate(domELement, type, oldProps, newProps) { 27 | return diffProperties(domELement, type, oldProps, newProps); 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.17.0", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-scripts": "5.0.1", 12 | "web-vitals": "^2.1.4" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "git@github.com:forturegrant/mini-react.git" 41 | } 42 | } -------------------------------------------------------------------------------- /src/reactCode/ReactDOM.js: -------------------------------------------------------------------------------- 1 | import { createHostRootFiber } from './ReactFiber'; 2 | import { updateContainer } from './ReactFiberReconciler'; 3 | import { initializeUpdateQuene } from './ReactUpdateQuene'; 4 | 5 | // ReactDom.render 开始把虚拟dom渲染到容器中 6 | function render(element, container) { 7 | let fiberRoot = container._reactRootContainer; 8 | if (!fiberRoot) { 9 | fiberRoot = container._reactRootContainer = createFiberRoot(container); 10 | } 11 | updateContainer(element, fiberRoot); 12 | } 13 | 14 | export const ReactDOM = { 15 | render 16 | }; 17 | 18 | // createFiberRoot 创建fiberRootNode(真实dom,id = 'root')和hostRootFiber(stateNode指向fiberRootNode) 19 | 20 | function createFiberRoot(containerInfo) { 21 | const fiberRoot = { containerInfo }; // fiberRoot指的就是容器对象containerInfo div#root 22 | const hostRootFiber = createHostRootFiber(); // 创建fiber树的根节点 这两个对应上面说的 23 | // 当前fiberRoot的current指向这个根fiber 24 | // current当前的意思,它指的是当前跟我们页面中真实dom相同的fiber树 25 | fiberRoot.current = hostRootFiber; 26 | // 让此根fiber的真实节点指向fiberRoot div#root stateNode就是指真实dom的意思 27 | hostRootFiber.stateNode = fiberRoot; 28 | initializeUpdateQuene(hostRootFiber); 29 | return fiberRoot; 30 | } 31 | 32 | export * from './ReactFiberHooks'; -------------------------------------------------------------------------------- /src/reactCode/ReactUpdateQuene.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 初始化更新队列 3 | * 所有的fiber都会把等待更新的内容放在更新队列中 4 | */ 5 | export function initializeUpdateQuene (fiber) { 6 | const updateQuene = { 7 | shared: { 8 | pending: null 9 | } 10 | }; 11 | fiber.updateQuene = updateQuene; 12 | } 13 | 14 | export function createUpdate () { 15 | return {}; 16 | } 17 | 18 | /** 19 | * 20 | * @param {*} fiber 21 | * @param {*} update 22 | */ 23 | export function enqueneUpdate (fiber, update) { 24 | // console.log(fiber, 'fiber'); 25 | const updateQuene = fiber.updateQuene; 26 | const shared = updateQuene.shared; 27 | const pending = shared.pending; 28 | // 环状链表,头尾相接 29 | if (!pending) { 30 | update.next = update; 31 | } else { 32 | update.next = pending.next; 33 | pending.next = update; 34 | } 35 | // pending永远指向最新的更新 36 | shared.pending = update; 37 | } 38 | 39 | // let fiber = { baseState: { number: 0 } }; 40 | // initializeUpdateQuene(fiber); 41 | // const update1 = createUpdate(); 42 | // update1.payload = { number: 1 }; // update1 = { payload: { number: 1 } } 43 | // // 把update1添加到更新队列链表里 44 | // enqueneUpdate(fiber, update1); 45 | 46 | // // 再添加一个update2 47 | // const update2 = createUpdate(); 48 | // update1.payload = { number: 2 }; // update2 = { payload: { number: 2 } } 49 | // // 把update1添加到更新队列链表里 50 | // enqueneUpdate(fiber, update2); 51 | -------------------------------------------------------------------------------- /src/reactCode/React.js: -------------------------------------------------------------------------------- 1 | import { REACT_ELEMENT_TYPE } from "./ReactSymbols"; 2 | 3 | const RESERVED_PROPS = { 4 | key: true, 5 | ref: true, 6 | __self: true, 7 | __source: true 8 | } 9 | 10 | /** 11 | * 创建虚拟DOM 12 | * @param {*} type 元素的类型 13 | * @param {*} config 配置对象 14 | * @param {*} children 第一个儿子,如果有多个儿子的话会依次放在后面 15 | */ 16 | 17 | function createElement(type, config, children) { 18 | let propName; 19 | const props = {}; 20 | let key = null; 21 | let ref = null; 22 | if (config) { 23 | if (config.key) { 24 | key = config.key; 25 | } 26 | if (config.ref) { 27 | ref = config.ref; 28 | } 29 | for (propName in config) { 30 | if (!RESERVED_PROPS.hasOwnProperty(propName)) { 31 | props[propName] = config[propName]; 32 | } 33 | } 34 | } 35 | const childrenLength = arguments.length - 2; 36 | if (childrenLength === 1) { 37 | props.children = children; 38 | } else if (childrenLength > 1) { 39 | const childArray = new Array(childrenLength); 40 | for (let i = 0; i < childrenLength; i++) { 41 | childArray[i] = arguments[i + 2]; 42 | } 43 | props.children = childArray; 44 | } 45 | 46 | return { 47 | $$typeof: REACT_ELEMENT_TYPE, // 类型是一个React元素 48 | type, 49 | ref, 50 | key, 51 | props 52 | } 53 | }; 54 | 55 | const React = { 56 | createElement 57 | }; 58 | 59 | export default React; 60 | -------------------------------------------------------------------------------- /src/reactCode/ReactFiberCommitWork.js: -------------------------------------------------------------------------------- 1 | import { HostComponent, HostRoot } from './ReactWorkTags'; 2 | import { appendChild, removeChild, insertBefore } from './ReactDOMHostConfig'; 3 | import { updateProperties } from './ReactDOMComponent'; 4 | import { Placement } from './ReactFiberFlags'; 5 | 6 | function getParentStateNode(fiber) { 7 | let parent = fiber.return; 8 | do { 9 | if (parent.tag === HostComponent) { 10 | return parent.stateNode; 11 | } else if (parent.tag === HostRoot) { 12 | return parent.stateNode.containerInfo; 13 | } else { 14 | // 函数组件或类组件 15 | parent = parent.return; 16 | } 17 | } while (parent) 18 | } 19 | 20 | export function commitPlacement(nextEffect) { 21 | const stateNode = nextEffect.stateNode; 22 | const parentStateNode = getParentStateNode(nextEffect); 23 | let before = getHostSibling(nextEffect); 24 | if (before) { 25 | insertBefore(parentStateNode, stateNode, before); 26 | } else { 27 | appendChild(parentStateNode, stateNode); 28 | } 29 | } 30 | 31 | function getHostSibling(fiber) { 32 | let node = fiber.sibling; 33 | while (node) { 34 | // 找它的弟弟们,找到最近一个,不是插入的节点,返回。。没有更新 35 | if (!(node.flags & Placement)) { 36 | return node.stateNode; 37 | } 38 | node = node.sibling; 39 | } 40 | return null; 41 | } 42 | 43 | export function commitWork(current, finishedWork) { 44 | const updatePayload = finishedWork.updateQuene; 45 | // finishedWork.updateQuene = null; 46 | if (updatePayload) { 47 | updateProperties(finishedWork.stateNode, updatePayload); 48 | } 49 | } 50 | 51 | export function commitDeletion(fiber) { 52 | if (!fiber) return; 53 | const parentStateNode = getParentStateNode(fiber); 54 | removeChild(parentStateNode, fiber.stateNode); 55 | } 56 | -------------------------------------------------------------------------------- /src/reactCode/ReactFiber.js: -------------------------------------------------------------------------------- 1 | import { NoFlags } from './ReactFiberFlags'; 2 | import { FunctionComponent, HostComponent, HostRoot } from './ReactWorkTags'; 3 | 4 | export function createHostRootFiber() { 5 | return createFiber(HostRoot); 6 | } 7 | 8 | /** 9 | * 创建fiber节点 10 | * @date 2023-04-24 11 | * @param {any} tag fiber的标签 HostRoot指的是根结点(HostRoot的tag是3,对应的真实dom节点 div#root) HostComponent(tag是5,例如div,span) 12 | * @param {any} pendingProps 等待生效的属性对象 13 | * @param {any} key 14 | * @returns {any} 15 | */ 16 | function createFiber(tag, pendingProps, key) { 17 | return new FiberNode(tag, pendingProps, key); 18 | } 19 | 20 | function FiberNode(tag, pendingProps, key) { 21 | this.tag = tag; 22 | this.pendingProps = pendingProps; 23 | this.key = key; 24 | } 25 | 26 | export function createWorkInProgress(current, pendingProps) { 27 | let workInProgress = current.alternate; 28 | if (!workInProgress) { 29 | workInProgress = createFiber(current.tag, pendingProps, current.key) 30 | workInProgress.type = current.type; 31 | workInProgress.stateNode = current.stateNode; 32 | workInProgress.alternate = current; 33 | current.alternate = workInProgress; 34 | } else { 35 | workInProgress.pendingProps = pendingProps; 36 | } 37 | workInProgress.flags = NoFlags; 38 | workInProgress.child = null; 39 | workInProgress.sibling = null; 40 | workInProgress.updateQuene = current.updateQuene; 41 | workInProgress.firstEffect = workInProgress.lastEffect = workInProgress.nextEffect = null; 42 | return workInProgress; 43 | } 44 | 45 | /** 46 | * 根据虚拟DOM元素创建fiber节点 47 | * @param {*} element 48 | * @returns 49 | */ 50 | export function createFiberFromElement(element) { 51 | const { key, type, props } = element; 52 | let tag; 53 | if (typeof type === 'string') { // span div p 54 | tag = HostComponent; // 标签等于原生组建 55 | } 56 | if (typeof type === 'function') { 57 | tag = FunctionComponent; 58 | } 59 | const fiber = createFiber(tag, props, key); 60 | fiber.type = type; 61 | return fiber; 62 | } 63 | -------------------------------------------------------------------------------- /src/reactCode/ReactFiberCompleteWork.js: -------------------------------------------------------------------------------- 1 | import { FunctionComponent, HostComponent } from "./ReactWorkTags"; 2 | import { 3 | appendChild, 4 | createInstance, 5 | finalizeInitialChildren, 6 | prepareUpdate, 7 | } from "./ReactDOMHostConfig"; 8 | import { Update } from "./ReactFiberFlags"; 9 | 10 | export function completeWork(current, workInProgress) { 11 | const newProps = workInProgress.pendingProps; 12 | switch (workInProgress.tag) { 13 | case HostComponent: 14 | if (current && workInProgress.stateNode) { 15 | updateHostComponent( 16 | current, 17 | workInProgress, 18 | workInProgress.tag, 19 | newProps 20 | ); 21 | } else { 22 | // 创建真实的DOM节点 23 | const type = workInProgress.type; // div p span 24 | // 创建此fiber的真实DOM 25 | const instance = createInstance(type, newProps); 26 | appendAllChildren(instance, workInProgress); 27 | // 让此fiber的真实DOM属性指向instance 28 | workInProgress.stateNode = instance; 29 | // 给真实DOM添加属性 30 | finalizeInitialChildren(instance, type, newProps); 31 | } 32 | break; 33 | default: 34 | break; 35 | } 36 | } 37 | 38 | function appendAllChildren(parent, workInProgress) { 39 | let node = workInProgress.child; 40 | while (node) { 41 | if (node.tag === HostComponent) { 42 | appendChild(parent, node.stateNode); 43 | } 44 | if (node.tag === FunctionComponent) { 45 | appendChild(parent, node.child.stateNode); 46 | } 47 | node = node.sibling; 48 | } 49 | } 50 | 51 | function updateHostComponent(current, workInProgress, tag, newProps) { 52 | const oldProps = current.memorizedProps; 53 | const instance = workInProgress.stateNode; 54 | const updatePayload = prepareUpdate(instance, tag, oldProps, newProps); 55 | workInProgress.updateQuene = updatePayload; 56 | if (updatePayload) { 57 | workInProgress.flags |= Update; 58 | } 59 | } 60 | 61 | /** 62 | * 根fiber rootFiber updateQuene.shared.pending 上面是一个环状链表 update {payload: element} 63 | * 原生组件fiber HostComponent updateQuene=updatePayload 数组[ key1, value1, key2, value2] 64 | */ 65 | -------------------------------------------------------------------------------- /src/reactCode/ReactFiberHooks.js: -------------------------------------------------------------------------------- 1 | import { scheduleUpdateOnFiber } from './ReactFiberWorkLoop'; 2 | import { isFn } from './utils'; 3 | let currentlyRenderingFiber = null; 4 | let workInProgressHook = null; 5 | 6 | // todo 获取当前hook 7 | function updateWorkInProgressHook() { 8 | let hook; 9 | 10 | const current = currentlyRenderingFiber.alternate; 11 | 12 | if (current) { 13 | // 更新,复用老hook,在老的基础上更新 14 | currentlyRenderingFiber.memoizedState = current.memoizedState; 15 | 16 | if (workInProgressHook) { 17 | workInProgressHook = hook = workInProgressHook.next; 18 | } else { 19 | hook = workInProgressHook = currentlyRenderingFiber.memoizedState; 20 | } 21 | } else { 22 | // 初次渲染,从0创建hook 23 | hook = { 24 | memorizedState: null, 25 | next: null 26 | }; 27 | // 把hook挂到fiber上 28 | if (workInProgressHook) { 29 | workInProgressHook = workInProgressHook.next = hook; 30 | } else { 31 | // hook0 32 | workInProgressHook = currentlyRenderingFiber.memoizedState = hook; 33 | } 34 | } 35 | 36 | return hook; 37 | } 38 | 39 | // 函数组件执行的时候 40 | export function renderHooks(workInProgress) { 41 | currentlyRenderingFiber = workInProgress; 42 | workInProgressHook = null; 43 | } 44 | 45 | export function useReducer(reducer, initialState) { 46 | const hook = updateWorkInProgressHook(); 47 | if (!currentlyRenderingFiber.alternate) { 48 | // 初次渲染 49 | hook.memorizedState = initialState; 50 | } 51 | const dispatch = dispatchReducerAction.bind( 52 | null, 53 | currentlyRenderingFiber, 54 | hook, 55 | reducer 56 | ); 57 | return [hook.memorizedState, dispatch]; 58 | } 59 | 60 | export function useState(initialState) { 61 | return useReducer(null, initialState); 62 | } 63 | 64 | function dispatchReducerAction( 65 | fiber, 66 | hook, 67 | reducer, 68 | action 69 | ) { 70 | // 兼容了 useReducer 和 useState 71 | hook.memorizedState = reducer 72 | ? reducer(hook.memorizedState, action) 73 | : isFn(action) 74 | ? action() 75 | : action; 76 | console.log(fiber, 'fiber'); 77 | // fiber.alternate = { ...fiber }; 78 | // 当前函数组件的fiber 79 | scheduleUpdateOnFiber(fiber); 80 | }; 81 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 25 | React App 26 | 27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 | 38 |
39 | 40 |
41 | 42 | 43 |
44 | 45 |
46 | 47 |
48 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/reactCode/ReactDOMComponent.js: -------------------------------------------------------------------------------- 1 | export function createElement(element) { 2 | return document.createElement(element); 3 | } 4 | 5 | export function setInitialProperties(domElement, tag, props) { 6 | for (const propKey in props) { 7 | const nextProp = props[propKey]; 8 | if (propKey === 'children') { 9 | if (typeof nextProp === 'string' || typeof nextProp === 'number') { 10 | domElement.textContent = nextProp; 11 | } 12 | } else if (propKey === 'style') { 13 | for (const stylePropKey in nextProp) { 14 | domElement.style[stylePropKey] = nextProp[stylePropKey]; 15 | } 16 | } else if (propKey.slice(0, 2) === 'on') { // 伪模拟一下react的事件机制 17 | const eventName = propKey.slice(2).toLocaleLowerCase(); 18 | domElement.removeEventListener(eventName, nextProp); 19 | domElement.addEventListener(eventName, nextProp); 20 | } else { 21 | domElement[propKey] = nextProp; 22 | } 23 | } 24 | } 25 | 26 | export function diffProperties(domELement, type, lastProps, nextProps) { 27 | let updatePayload = null; 28 | let propKey; 29 | for (propKey in lastProps) { 30 | if (lastProps.hasOwnProperty(propKey) && (!nextProps.hasOwnProperty(propKey))) { 31 | (updatePayload = updatePayload || []).push(propKey, null); 32 | } 33 | } 34 | for (propKey in nextProps) { 35 | const nextProp = nextProps[propKey]; 36 | if (propKey === 'children') { 37 | if (typeof nextProp === 'string' || typeof nextProp === 'number') { 38 | if (nextProp !== lastProps[propKey]) { 39 | (updatePayload = updatePayload || []).push(propKey, nextProp); 40 | } 41 | } 42 | } else { 43 | if (nextProp !== lastProps[propKey]) { 44 | (updatePayload = updatePayload || []).push(propKey, nextProp); 45 | } 46 | } 47 | } 48 | return updatePayload; 49 | } 50 | 51 | export function updateProperties(domELement, updatePayload) { 52 | for (let i = 0; i < updatePayload.length; i += 2) { 53 | const propKey = updatePayload[i]; 54 | const propValue = updatePayload[i + 1]; 55 | if (propKey === 'children') { 56 | domELement.textContent = propValue; 57 | } else { 58 | domELement.setAttribute(propKey, propValue); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/reactCode/ReactFiberBeginWork.js: -------------------------------------------------------------------------------- 1 | import { HostRoot, HostComponent, FunctionComponent } from './ReactWorkTags'; 2 | import { reconcileChildFibers, mountChildFibers } from './ReactChildFiber'; 3 | import { shouldSetTextContent } from './ReactDOMHostConfig'; 4 | import { renderHooks } from './ReactFiberHooks'; 5 | 6 | export function beginWork(current, workInProgress) { 7 | switch (workInProgress.tag) { 8 | case HostRoot: 9 | return updateHostRoot(current, workInProgress); 10 | case HostComponent: 11 | return updateHostComponent(current, workInProgress); 12 | case FunctionComponent: 13 | return updateFunctionComponent(current, workInProgress); 14 | default: 15 | break; 16 | } 17 | } 18 | 19 | /** 20 | * 21 | * @param {*} current 22 | * @param {*} workInProgress 23 | */ 24 | function updateHostRoot(current, workInProgress) { 25 | const updateQuene = workInProgress.updateQuene; 26 | const nextChildren = updateQuene.shared.pending.payload.element; 27 | // 处理子节点,根据老fiber和新的虚拟dom进行对比,创建新的fiber树 28 | reconcileChildren(current, workInProgress, nextChildren); 29 | // 返回第一个子fiber 30 | return workInProgress.child; 31 | } 32 | 33 | function updateHostComponent(current, workInProgress) { 34 | // 获取此原生组件的类型 span p 35 | const type = workInProgress.type; 36 | // 新属性 37 | const nextProps = workInProgress.pendingProps; 38 | let nextChildren = nextProps.children; 39 | // 在react中,如果一个原生组件,它只有一个儿子,并且这个儿子是一个字符串的话, 40 | // 不会对此儿子创建一个fiber节点,而是把它当成一个属性来处理 41 | const isDirectTextChild = shouldSetTextContent(type, nextProps); 42 | if (isDirectTextChild) { 43 | nextChildren = null; 44 | } 45 | // 处理子节点,根据老fiber和新的虚拟DOM进行对比,创建新的fiber树 46 | reconcileChildren(current, workInProgress, nextChildren); 47 | return workInProgress.child; 48 | } 49 | 50 | function updateFunctionComponent(current, workInProgress) { 51 | renderHooks(workInProgress); 52 | const { type, pendingProps } = workInProgress; 53 | const children = type(pendingProps); 54 | reconcileChildren(current, workInProgress, children);; 55 | return workInProgress.child; 56 | } 57 | 58 | export function reconcileChildren(current, workInProgress, nextChildren) { 59 | // 如果current有值,说明这是更新 60 | if (current) { 61 | workInProgress.child = reconcileChildFibers( 62 | workInProgress, // 新fiber 63 | current.child, // 老fiber的第一个子节点fiber 64 | nextChildren 65 | ); 66 | } else { 67 | workInProgress.child = mountChildFibers( 68 | workInProgress, // 新fiber 69 | null, 70 | nextChildren 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from './reactCode/React'; 2 | import { ReactDOM } from './reactCode/ReactDOM'; 3 | import { useReducer, useState } from './reactCode/ReactFiberHooks'; 4 | 5 | const single1 = document.getElementById('single1'); 6 | const single1Update = document.getElementById('single1Update'); 7 | 8 | const single2 = document.getElementById('single2'); 9 | const single2Update = document.getElementById('single2Update'); 10 | 11 | const single3 = document.getElementById('single3'); 12 | const single3Update = document.getElementById('single3Update'); 13 | 14 | const single4 = document.getElementById('single4'); 15 | 16 | single1.addEventListener('click', () => { 17 | let element = ( 18 |
title
19 | ) 20 | ReactDOM.render(element, document.getElementById('root')); 21 | }) 22 | 23 | single1Update.addEventListener('click', () => { 24 | let element = ( 25 |
title2
26 | ) 27 | ReactDOM.render(element, document.getElementById('root')); 28 | }) 29 | 30 | single2.addEventListener('click', () => { 31 | let element = ( 32 | 40 | ) 41 | ReactDOM.render(element, document.getElementById('root')); 42 | }) 43 | 44 | single2Update.addEventListener('click', () => { 45 | let element = ( 46 | 54 | ) 55 | ReactDOM.render(element, document.getElementById('root')); 56 | }) 57 | 58 | single3.addEventListener('click', () => { 59 | let element = ( 60 | 65 | ) 66 | ReactDOM.render(element, document.getElementById('root')); 67 | }) 68 | 69 | single3Update.addEventListener('click', () => { 70 | let element = ( 71 | 76 | ) 77 | ReactDOM.render(element, document.getElementById('root')); 78 | }) 79 | 80 | function Test() { 81 | const [count, setCount] = useReducer((x) => x + 1, 0); 82 | const [count2, setCount2] = useState(0); 83 | return
84 | 85 | 86 |
87 | } 88 | 89 | single4.addEventListener('click', () => { 90 | let element = ( 91 |
92 |
title3
93 | 94 |
95 | ) 96 | ReactDOM.render(element, document.getElementById('root')); 97 | }) 98 | 99 | // ReactDOM.render(element, document.getElementById('root')); 100 | -------------------------------------------------------------------------------- /src/reactCode/ReactFiberWorkLoop.js: -------------------------------------------------------------------------------- 1 | import { createWorkInProgress } from './ReactFiber'; 2 | import { beginWork } from './ReactFiberBeginWork'; 3 | import { completeWork } from './ReactFiberCompleteWork'; 4 | import { Deletion, Placement, Update, PlacementAndUpdate } from './ReactFiberFlags'; 5 | import { commitPlacement, commitDeletion, commitWork } from './ReactFiberCommitWork'; 6 | // 当前正在更新的根 7 | let workInProgressRoot = null; 8 | 9 | // 当前正在更新fiber节点 10 | let workInProgress = null; 11 | 12 | /** 13 | * 不管如何更新,不管谁来更新,都会调度到这个方法里 14 | * @param {*} fiber 15 | */ 16 | export function scheduleUpdateOnFiber(fiber) { 17 | const fiberRoot = markUpdateLaneFromFiberToRoot(fiber); 18 | performSyncWorkOnRoot(fiberRoot); 19 | } 20 | 21 | /** 22 | * 根据老的fiber树和更新对象创建新的fiber树,然后根据新的fiber树去更新真实DOM 23 | * @param {*} fiberRoot 24 | */ 25 | function performSyncWorkOnRoot(fiberRoot) { 26 | workInProgressRoot = fiberRoot; 27 | workInProgress = createWorkInProgress(workInProgressRoot.current); 28 | workLoopSync(); // 执行工作循环,构建副作用链 29 | commitRoot(); // 提交,修改DOM 30 | } 31 | 32 | function commitRoot() { 33 | // 指向新构建的fiber树 34 | const finishedWork = workInProgressRoot.current.alternate; 35 | workInProgressRoot.finishedWork = finishedWork; 36 | commitMutationEffects(workInProgressRoot); 37 | } 38 | 39 | function getFlags(flags) { 40 | switch (flags) { 41 | case Placement: 42 | return '插入'; 43 | case Update: 44 | return '更新'; 45 | case PlacementAndUpdate: 46 | return '移动'; 47 | case Deletion: 48 | return '删除'; 49 | default: 50 | break; 51 | } 52 | } 53 | 54 | function commitMutationEffects(root) { 55 | const finishedWork = root.finishedWork; 56 | let nextEffect = finishedWork.firstEffect; 57 | let effectsList = ''; 58 | while (nextEffect) { 59 | effectsList += `(${getFlags(nextEffect.flags)}#${nextEffect.type}#${nextEffect.key})`; 60 | const flags = nextEffect.flags; 61 | const current = nextEffect.alternate; 62 | if (flags === Placement) { 63 | commitPlacement(nextEffect); 64 | } else if (flags === PlacementAndUpdate) { 65 | commitPlacement(nextEffect); 66 | nextEffect.flags = Update; 67 | commitWork(current, nextEffect); 68 | } else if (flags === Update) { 69 | commitWork(current, nextEffect); 70 | } else if (flags === Deletion) { 71 | commitDeletion(nextEffect); 72 | } 73 | nextEffect = nextEffect.nextEffect; 74 | } 75 | effectsList += ' '; 76 | console.log(effectsList, 'effectsList'); 77 | console.log(finishedWork, 'finishedWork'); 78 | root.current = finishedWork; 79 | } 80 | 81 | /** 82 | * 开始自上而下构建新的fiber树 83 | */ 84 | function workLoopSync() { 85 | while (workInProgress) { 86 | performUnitWork(workInProgress); 87 | } 88 | } 89 | 90 | /** 91 | * 执行单个工作单元 92 | * workInProgress 要处理的fiber 93 | */ 94 | function performUnitWork(unitOfWork) { 95 | // 获取当前正在构建的fiber的替身 96 | const current = unitOfWork.alternate; 97 | // 开始构建当前fiber的子fiber链表 98 | // 它会返回下一个要处理的fiber,一般都是unitOfWork的大儿子 99 | // div#title这个fiber它的返回值是一个null 100 | const next = beginWork(current, unitOfWork); 101 | // 在beginWork后,需要把新属性同步到老属性上 102 | // div id = 1 变成 id = 2 memorizedProps={id:2} pendingProps={id:1} 103 | unitOfWork.memorizedProps = unitOfWork.pendingProps; 104 | // 当前的fiber还有子节点 105 | if (next) { 106 | workInProgress = next; 107 | } else { 108 | // 如果当前fiber没有子fiber,那么当前的fiber就算完成 109 | completeUnitOfWork(unitOfWork); 110 | } 111 | } 112 | 113 | /** 114 | * 完成一个fiber节点 115 | * @param {*} unitOfWork 116 | */ 117 | function completeUnitOfWork(unitOfWork) { 118 | let completedWork = unitOfWork; 119 | do { 120 | const current = completedWork.alternate; 121 | const returnFiber = completedWork.return; 122 | // 完成此fiber对应的真实DOM节点创建和属性赋值的功能 123 | completeWork(current, completedWork); 124 | // 收集当前fiber的副作用到父fiber上 125 | collectEffectList(returnFiber, completedWork); 126 | // 当自己这个fiber完成后,如何寻找下个要构建的fiber 127 | const siblingFiber = completedWork.sibling; 128 | if (siblingFiber) { 129 | // 如果有弟弟,就开始构建弟弟,处理弟弟 beginwork 130 | workInProgress = siblingFiber; 131 | return; 132 | } 133 | // 如果没有弟弟,说明这是最后一个儿子,父亲也可以完成了 134 | // 这个循环到最后的时候,returnFiber就是null,也就是根fiber的父亲 135 | completedWork = returnFiber; 136 | // 不停的修改当前正在处理的fiber,最后workInProgress=null 就可以退出workLoop了 137 | workInProgress = completedWork; 138 | } while (workInProgress) 139 | } 140 | 141 | function collectEffectList(returnFiber, completedWork) { 142 | if (returnFiber) { 143 | if (!returnFiber.firstEffect) { 144 | returnFiber.firstEffect = completedWork.firstEffect; 145 | } 146 | // 如果自己有链表尾 147 | if (completedWork.lastEffect) { 148 | // 并且父亲也有链表尾 149 | if (returnFiber.lastEffect) { 150 | // 把自己身上的effectList挂接到父亲的链表尾部 151 | returnFiber.lastEffect.nextEffect = completedWork.firstEffect; 152 | } 153 | returnFiber.lastEffect = completedWork.lastEffect; 154 | } 155 | const flags = completedWork.flags; 156 | // 如果此完成的fiber有副作用,那么就需要添加到effectList里 157 | if (flags) { 158 | // 如果父fiber有lastEffect的话,说明父fiber已经有effect链表 159 | if (returnFiber.lastEffect) { 160 | returnFiber.lastEffect.nextEffect = completedWork; 161 | } else { 162 | returnFiber.firstEffect = completedWork; 163 | } 164 | returnFiber.lastEffect = completedWork; 165 | } 166 | } 167 | } 168 | 169 | function markUpdateLaneFromFiberToRoot(sourceFiber) { 170 | let node = sourceFiber; 171 | let parent = node.return; 172 | while (parent) { 173 | // 一直往上找到fiber树的根节点 174 | node = parent; 175 | parent = parent.return; 176 | } 177 | // node其实就是fiber树的根节点,hostFiberRoot.stateNode div#root 178 | return node.stateNode; 179 | } 180 | -------------------------------------------------------------------------------- /src/reactCode/ReactChildFiber.js: -------------------------------------------------------------------------------- 1 | import { REACT_ELEMENT_TYPE } from "./ReactSymbols"; 2 | import { createFiberFromElement, createWorkInProgress } from "./ReactFiber"; 3 | import { Deletion, Placement } from "./ReactFiberFlags"; 4 | 5 | function childReconciler(shouldTrackSideEffects) { 6 | function deleteRemainingChildren(returnFiber, childToDelete) { 7 | while (childToDelete) { 8 | deleteChild(returnFiber, childToDelete); 9 | childToDelete = childToDelete.sibling; 10 | } 11 | } 12 | 13 | function useFiber(oldFiber, pendingProps) { 14 | return createWorkInProgress(oldFiber, pendingProps); 15 | } 16 | 17 | function reconcileSingleElement(returnFiber, currentFirstChild, element) { 18 | 19 | const key = element.key; 20 | let child = currentFirstChild; 21 | while (child) { 22 | if (child.key === key) { 23 | if (child.type === element.type) { 24 | deleteRemainingChildren(returnFiber, child.sibling); 25 | const existing = useFiber(child, element.props); 26 | existing.return = returnFiber; 27 | return existing; 28 | } else { 29 | deleteChild(returnFiber, child); 30 | break; 31 | } 32 | } else { 33 | deleteChild(returnFiber, child); 34 | } 35 | child = child.sibling; 36 | } 37 | const crated = createFiberFromElement(element); 38 | crated.return = returnFiber; 39 | return crated; 40 | } 41 | 42 | function deleteChild(returnFiber, childToDelete) { 43 | if (!shouldTrackSideEffects) { 44 | return; 45 | } 46 | const lastEffect = returnFiber.lastEffect; 47 | if (lastEffect) { 48 | lastEffect.nextEffect = childToDelete; 49 | returnFiber.lastEffect = childToDelete; 50 | } else { 51 | returnFiber.firstEffect = returnFiber.lastEffect = childToDelete; 52 | } 53 | childToDelete.nextEffect = null; 54 | childToDelete.flags = Deletion; 55 | } 56 | 57 | function placeSingleChild(newFiber) { 58 | // 如果当前需要跟踪副作用,并且当前这个新的fiber它的替身不存在 59 | if (shouldTrackSideEffects && !newFiber.alternate) { 60 | // 给这个新fiber添加一个副作用,表示在未来提交阶段的DOM操作中回向真实DOM树中添加此节点 61 | newFiber.flags = Placement; 62 | } 63 | return newFiber; 64 | } 65 | 66 | function createChild(returnFiber, newChild) { 67 | const created = createFiberFromElement(newChild); 68 | created.return = returnFiber; 69 | return created; 70 | } 71 | 72 | function updateElement(returnFiber, oldFiber, newChild) { 73 | if (oldFiber) { 74 | if (oldFiber.type === newChild.type) { 75 | const existing = useFiber(oldFiber, newChild.props); 76 | existing.return = returnFiber; 77 | return existing; 78 | } 79 | } 80 | // 如果没有老fiber 81 | const created = createFiberFromElement(newChild); 82 | created.return = returnFiber; 83 | return created; 84 | } 85 | 86 | function updateSlot(returnFiber, oldFiber, newChild) { 87 | const key = oldFiber ? oldFiber.key : null; 88 | if (newChild.key === key) { 89 | return updateElement(returnFiber, oldFiber, newChild); 90 | } else { 91 | // 如果key不一样,直接结束返回null 92 | return null; 93 | } 94 | } 95 | 96 | function placeChild(newFiber, lastPlacedIndex, newIdx) { 97 | newFiber.index = newIdx; 98 | if (!shouldTrackSideEffects) { 99 | return lastPlacedIndex; 100 | } 101 | const current = newFiber.alternate; 102 | // 如果有current说是更新,复用老节点的更新,不会添加Placement 103 | if (current) { 104 | const oldIndex = current.index; 105 | // 如果老fiber它对应的真是DOM挂载的索引比lastPlacedIndex小 106 | if (oldIndex < lastPlacedIndex) { 107 | // 老fiber对应的真是DOM就需要移动了 108 | newFiber.flags |= Placement; 109 | return lastPlacedIndex; 110 | } else { 111 | // 否则 不需要移动 并且把老fiber它的原来的挂载索引返回成为新的lastPlacedIndex 112 | return oldIndex; 113 | } 114 | } else { 115 | newFiber.flags = Placement; 116 | return lastPlacedIndex; 117 | } 118 | } 119 | 120 | function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) { 121 | // 将要返回的第一个新fiber 122 | let resultingFirstChild = null; 123 | // 上一个新fiber 124 | let previousNewFiber = null; 125 | // 当前的老fiber 126 | let oldFiber = currentFirstChild; 127 | // 下一个老fiber 128 | let nextOldFiber = null; 129 | // 新的虚拟DOM的索引 130 | let newIdx = 0; 131 | // 指的上一个可以复用的,不需要移动的节点的老索引 132 | let lastPlacedIndex = 0; 133 | // 处理更新的情况 老fiber和新fiber的存在 134 | for (; oldFiber && newIdx < newChildren.length; newIdx++) { 135 | // 先缓存下一个老fiber 136 | nextOldFiber = oldFiber.sibling; 137 | // 试图复用老fiber 138 | const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx]); 139 | if (!newFiber) { 140 | break; 141 | } 142 | if (oldFiber && !newFiber.alternate) { 143 | deleteChild(returnFiber, oldFiber); 144 | } 145 | lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); 146 | if (!previousNewFiber) { 147 | resultingFirstChild = newFiber; 148 | } else { 149 | previousNewFiber.sibling = newFiber; 150 | } 151 | previousNewFiber = newFiber; 152 | oldFiber = nextOldFiber; 153 | } 154 | if (newIdx === newChildren.length) { 155 | deleteRemainingChildren(returnFiber, oldFiber); 156 | return resultingFirstChild; 157 | } 158 | if (!oldFiber) { 159 | for (; newIdx < newChildren.length; newIdx++) { 160 | const newFiber = createChild(returnFiber, newChildren[newIdx]); 161 | lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); 162 | if (!previousNewFiber) { 163 | resultingFirstChild = newFiber; 164 | } else { 165 | previousNewFiber.sibling = newFiber; 166 | } 167 | previousNewFiber = newFiber; 168 | } 169 | return resultingFirstChild; 170 | } 171 | // 将剩下的老fiber放去map中 172 | const existingChildren = mapRemainingChildren(returnFiber, oldFiber); 173 | for (; newIdx < newChildren.length; newIdx++) { 174 | // 去map中找找有没key相同并且类型相同可以复用的老fiber 老真实DOM 175 | const newFiber = updateFromMap(existingChildren, returnFiber, newIdx, newChildren[newIdx]); 176 | if (newFiber) { 177 | if (newFiber.alternate) { 178 | existingChildren.delete(newFiber.key || newIdx); 179 | } 180 | lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); 181 | if (!previousNewFiber) { 182 | resultingFirstChild = newFiber; 183 | } else { 184 | previousNewFiber.sibling = newFiber; 185 | } 186 | previousNewFiber = newFiber; 187 | } 188 | } 189 | existingChildren.forEach(child => deleteChild(returnFiber, child)); 190 | return resultingFirstChild; 191 | } 192 | 193 | function updateFromMap(existingChildren, returnFiber, newIdx, newChild) { 194 | const matchedFiber = existingChildren.get(newChild.key || newIdx); 195 | return updateElement(returnFiber, matchedFiber, newChild); 196 | } 197 | 198 | function mapRemainingChildren(returnFiber, currentFirstChild) { 199 | const existingChildren = new Map(); 200 | let existingChild = currentFirstChild; 201 | while (existingChild != null) { 202 | if (existingChild.key !== null) { 203 | existingChildren.set(existingChild.key, existingChild); 204 | } else { 205 | existingChildren.set(existingChild.index, existingChild); 206 | } 207 | existingChild = existingChild.sibling; 208 | } 209 | return existingChildren; 210 | } 211 | 212 | function reconcilerChildFibers(returnFiber, currentFirstChild, newChild) { 213 | // 判断newChild是不是一个对象,如果是的话说明新的虚拟DOM只有一个React元素节点 214 | const isObject = 215 | Object.prototype.toString.call(newChild) === "[object Object]"; 216 | if (isObject) { 217 | switch (newChild.$$typeof) { 218 | case REACT_ELEMENT_TYPE: 219 | return placeSingleChild( 220 | reconcileSingleElement(returnFiber, currentFirstChild, newChild) 221 | ); 222 | } 223 | } else if (Array.isArray(newChild)) { 224 | return reconcileChildrenArray(returnFiber, currentFirstChild, newChild); 225 | } 226 | } 227 | return reconcilerChildFibers; 228 | } 229 | 230 | export const reconcileChildFibers = childReconciler(true); 231 | export const mountChildFibers = childReconciler(false); 232 | -------------------------------------------------------------------------------- /docs/hook.md: -------------------------------------------------------------------------------- 1 | #### 前言 2 | 3 | 先看看react大概是怎么实现的朋友们可以想看看上文 [react的基本实现](https://juejin.cn/post/7278305453599096893) 4 | 5 | 代码地址在这里 [mini-react](https://github.com/forturegrant/mini-react) 6 | 7 | #### hook原理 8 | 9 | 上文我们讲了react的虚拟dom实现和diff算法,还讲了React触发更新的整个过程,这一篇就继续讲一下关于hooks的内容 10 | 11 | 我们先来看看我们日常使用hooks的是怎么使用的 12 | 13 | 我们还是沿用上篇文章的代码继续展开 14 | 15 | 同样的我们在html里面加上一个button并点击按钮渲染对应的内容 16 | 17 | ```index.html 18 | 19 |
20 |
21 | 22 | 23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 |
38 | 39 | ``` 40 | ```index.js 41 | function Test() { 42 | const [count, setCount] = useReducer((x) => x + 1, 0); 43 | const [count2, setCount2] = useState(0); 44 | return
45 | 46 | 47 |
48 | } 49 | 50 | single4.addEventListener('click', () => { 51 | let element = ( 52 |
53 |
title3
54 | 55 |
56 | ) 57 | ReactDOM.render(element, document.getElementById('root')); 58 | }) 59 | ``` 60 | 点击之后 61 | 62 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f4d13a473a3d4444a11b71b11872644d~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=897&h=1035&s=182407&e=png&b=ffffff) 63 | 64 | 可以看到这里我们渲染出来了对应的function Test里面的内容,**而且我们新的fiber tree上面有了这个function对应的fiber节点,fiber节点上面有一个memorizedState属性,这个属性里面存储了一个通过next链接的单向链表**,这就是我们写的useReducer和useState的状态,第一个 **memorizedState:0** 对应的useReducer的 0,**而next中memorizedState存的则是useState的0**,这里我们先不展开讲useReducer和useState的实现原理(后面会讲到),这里我们先讲hooks到底是什么样的结构,并且hooks的信息是怎么存储到fiber节点上的 65 | 66 | 大概实现是这样的 67 | 68 | ```ReactFiberHooks.js 69 | function updateWorkInProgressHook() { 70 | let hook; 71 | 72 | const current = currentlyRenderingFiber.alternate; 73 | 74 | if (current) { 75 | // 更新,复用老hook,在老的基础上更新 76 | currentlyRenderingFiber.memoizedState = current.memoizedState; 77 | 78 | if (workInProgressHook) { 79 | workInProgressHook = hook = workInProgressHook.next; 80 | } else { 81 | hook = workInProgressHook = currentlyRenderingFiber.memoizedState; 82 | } 83 | } else { 84 | // 初次渲染,从0创建hook 85 | hook = { 86 | memorizedState: null, 87 | next: null 88 | }; 89 | // 把hook挂到fiber上 90 | if (workInProgressHook) { 91 | workInProgressHook = workInProgressHook.next = hook; 92 | } else { 93 | // hook0 94 | workInProgressHook = currentlyRenderingFiber.memoizedState = hook; 95 | } 96 | } 97 | 98 | return hook; 99 | } 100 | ``` 101 | 102 | 遇到第一个hooks useReducer,**看看fiber里有没有 memorizeState,没有就建一个 { memorizeState: 0, next: null },然后将这个对象赋值给workInProgressHook和对应的fiber节点(即代码中的currentlyRenderingFiber)的memorizedState,此时workInProgressHook = currentlyRenderingFiber = { memorizeState: 0, next: null }**,到了第二hooks useState,因为fiber里已经有memorizeState了,就直接workInProgressHook的next变成 { memorizeState: 1, next: null },然后同时将next赋值给workInProgressHook,因为对象引用的关系,currentlyRenderingFiber上的memorizeState也会一起变化,所以workInProgressHook总是会指向最后一个hooks,**所以不管一个函数组件有多少个hook,我们都可以通过workInProgressHook来把最新的hook添加到尾部(因为是链表,如果是数组我们可以直接push到尾部),而不用从头开始找到最后** 103 | 104 | #### hook实现 105 | 106 | ##### useState & useReducer 107 | 108 | 为什么要把useState和useReducer放在一起讲呢,因为useState其实就是用useReducer实现的 109 | 110 | 我们继续接着上面的例子,我们写了一个函数组件Test 111 | 112 | ```index.html 113 | 114 |
115 |
116 | 117 | 118 |
119 | 120 |
121 | 122 | 123 |
124 | 125 |
126 | 127 | 128 |
129 | 130 |
131 | 132 |
133 | 134 | ``` 135 | ```index.js 136 | function Test() { 137 | const [count, setCount] = useReducer((x) => x + 1, 0); 138 | const [count2, setCount2] = useState(0); 139 | return
140 | 141 | 142 |
143 | } 144 | 145 | single4.addEventListener('click', () => { 146 | let element = ( 147 |
148 |
title3
149 | 150 |
151 | ) 152 | ReactDOM.render(element, document.getElementById('root')); 153 | }) 154 | ``` 155 | 点击之后按钮 4.hooks 之后可以看到我们的两个按钮渲染了出来,(想知道点击按钮按钮 4.hooks 之后是如何渲染出dom的,可以看文章开头写到的上文 [react的基本实现](https://juejin.cn/post/7278305453599096893) ),按钮的值也是useState和useReducer的初始值 156 | 157 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f4d13a473a3d4444a11b71b11872644d~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=897&h=1035&s=182407&e=png&b=ffffff) 158 | 159 | 那我们接下来就开始先实现useReducer 160 | 161 | 按照我们上面对hook基本原理的了解,当遇到第一个hooks useReducer时,**看看fiber里有没有 memorizeState,没有就建一个 { memorizeState: 0, next: null },然后将这个对象赋值给workInProgressHook和对应的fiber节点(即代码中的currentlyRenderingFiber)的memorizedState,此时workInProgressHook = currentlyRenderingFiber = { memorizeState: 0, next: null }**,下面我们实现这个逻辑 162 | 163 | ```ReactFiberHooks.js 164 | import { scheduleUpdateOnFiber } from './ReactFiberWorkLoop'; 165 | import { isFn } from './utils'; 166 | let currentlyRenderingFiber = null; 167 | let workInProgressHook = null; 168 | 169 | // todo 获取当前hook 170 | function updateWorkInProgressHook() { 171 | let hook; 172 | 173 | const current = currentlyRenderingFiber.alternate; 174 | 175 | if (current) { 176 | // 更新,复用老hook,在老的基础上更新 177 | currentlyRenderingFiber.memoizedState = current.memoizedState; 178 | 179 | if (workInProgressHook) { 180 | workInProgressHook = hook = workInProgressHook.next; 181 | } else { 182 | hook = workInProgressHook = currentlyRenderingFiber.memoizedState; 183 | } 184 | } else { 185 | // 初次渲染,从0创建hook 186 | hook = { 187 | memorizedState: null, 188 | next: null 189 | }; 190 | // 把hook挂到fiber上 191 | if (workInProgressHook) { 192 | workInProgressHook = workInProgressHook.next = hook; 193 | } else { 194 | // hook0 195 | workInProgressHook = currentlyRenderingFiber.memoizedState = hook; 196 | } 197 | } 198 | 199 | return hook; 200 | } 201 | 202 | // 函数组件执行的时候 203 | export function renderHooks(workInProgress) { 204 | currentlyRenderingFiber = workInProgress; 205 | workInProgressHook = null; 206 | } 207 | 208 | export function useReducer(reducer, initialState) { 209 | const hook = updateWorkInProgressHook(); 210 | if (!currentlyRenderingFiber.alternate) { 211 | // 初次渲染 212 | hook.memorizedState = initialState; 213 | } 214 | const dispatch = dispatchReducerAction.bind( 215 | null, 216 | currentlyRenderingFiber, 217 | hook, 218 | reducer 219 | ); 220 | return [hook.memorizedState, dispatch]; 221 | } 222 | 223 | function dispatchReducerAction( 224 | fiber, 225 | hook, 226 | reducer, 227 | action 228 | ) { 229 | // 兼容了 useReducer 和 useState 230 | hook.memorizedState = reducer 231 | ? reducer(hook.memorizedState, action) 232 | : isFn(action) 233 | ? action() 234 | : action; 235 | // 当前函数组件的fiber 236 | scheduleUpdateOnFiber(fiber); 237 | }; 238 | ``` 239 | 可以看到我们在执行useReducer时,先通过**updateWorkInProgressHook**这个方法成功在fiber添加了我们想要的**memorizeState链表**,然后就是实现**useReducer**的逻辑了,其实就是**dispatchReducerAction**这个方法,可以看到这个方法并没有实现什么太多的逻辑,他只是把hooks上的memorizedState更新了一下,**然后执行scheduleUpdateOnFiber这个方法带上当前函数组件的fiber**,就开始执行**react diff的过程去更新dom tree而已**。(这里想了解scheduleUpdateOnFiber是怎么实现的,可以看文章开头写到的上文 [react的基本实现](https://juejin.cn/post/7278305453599096893),**scheduleUpdateOnFiber这个方法不管如何更新,不管谁来更新,都会调度到这个方法里**) 240 | 241 | 简单实现完useReducer之后我们就可以开始点击这个按钮,可以看到按钮的值一直在改变 242 | 243 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2113a0c252f645c99f6b5b87cf0ebeac~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=892&h=728&s=40427&e=png&b=fefefe) 244 | 245 | 这样我们的useReducer就实现了 246 | 247 | 不过这里我们是使用button点击之后改变状态的,react是有一套属于自己的事件机制的,实现起来比较复杂,所以我们这里就稍微简单粗暴一下监听了一下事件的触发,如果属性是on开头,那就直接绑定事件 248 | 249 | ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ff0e97d82e604f1db90116e7067e19d4~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=603&h=379&s=45667&e=png&b=1f1f1f) 250 | 251 | 然后我们再实现一下useState,上面也说过useState其实就是useReducer实现的 252 | 253 | ```ReactFiberHooks.js 254 | export function useState(initialState) { 255 | return useReducer(null, initialState); 256 | } 257 | ``` 258 | #### 总结 259 | 260 | 大概就讲到这里,主要内容还是讲了react hook的原理和实现,还很简单的实现了一下useState和useReducer,有兴趣的同学可以直接看仓库 [mini-react](https://github.com/forturegrant/mini-react) 261 | 262 | 263 | -------------------------------------------------------------------------------- /docs/vdom_diff.md: -------------------------------------------------------------------------------- 1 | #### 前言 2 | 会贴一部分代码和截图方便理解,但是代码太多不会全部贴上,有需要的同学可以直接上github上 [源码demo](https://github.com/forturegrant/mini-react) 跑起来慢慢食用 3 | #### 虚拟DOM 4 | 我们都知道react的入口文件都是类似这样的 5 | 6 | ```js 7 | import React from 'react'; 8 | import { ReactDOM } from "../../reactCode/react"; 9 | 10 | 11 | export default class App extends React.Component { 12 | render() { 13 | return
14 |
222
15 |
16 | } 17 | } 18 | 19 | 20 | ReactDOM.render(, document.getElementById('root')); 21 | ``` 22 | 今天我们就来一步步实现ReactDOM.render,一步步来看看react的核心源码实现属于我们自己的react 23 | 24 | 我们先实现react的虚拟DOM,react使用的jsx是利用babel编译成React.createElement之后,再由js执行React.createElement生成虚拟DOM 25 | 26 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c7bdd56b5b184e8385e2e09482922178~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1242&h=622&s=123398&e=png&b=2e2e2b) 27 | 28 | 这里先**实现下React.createElement** 29 | 30 | ```reactCode/react.js 31 | import { REACT_ELEMENT_TYPE } from "./ReactSymbols"; 32 | 33 | const RESERVED_PROPS = { 34 | key: true, 35 | ref: true, 36 | __self: true, 37 | __source: true 38 | } 39 | 40 | /** 41 | * 创建虚拟DOM 42 | * @param {*} type 元素的类型 43 | * @param {*} config 配置对象 44 | * @param {*} children 第一个儿子,如果有多个儿子的话会依次放在后面 45 | */ 46 | 47 | function createElement(type, config, children) { 48 | let propName; 49 | const props = {}; 50 | let key = null; 51 | let ref = null; 52 | if (config) { 53 | if (config.key) { 54 | key = config.key; 55 | } 56 | if (config.ref) { 57 | ref = config.ref; 58 | } 59 | for (propName in config) { 60 | if (!RESERVED_PROPS.hasOwnProperty(propName)) { 61 | props[propName] = config[propName]; 62 | } 63 | } 64 | } 65 | const childrenLength = arguments.length - 2; 66 | if (childrenLength === 1) { 67 | props.children = children; 68 | } else if (childrenLength > 1) { 69 | const childArray = new Array(childrenLength); 70 | for (let i = 0; i < childrenLength; i++) { 71 | childArray[i] = arguments[i + 2]; 72 | } 73 | props.children = childArray; 74 | } 75 | 76 | return { 77 | $$typeof: REACT_ELEMENT_TYPE, // 类型是一个React元素 78 | type, 79 | ref, 80 | key, 81 | props 82 | } 83 | }; 84 | 85 | const React = { 86 | createElement 87 | }; 88 | 89 | export default React; 90 | ``` 91 | 然后在这里打印下 92 | 93 | ```index.js 94 | import React from './reactCode/React'; 95 | import ReactDOM from './reactCode/ReactDOM'; 96 | 97 | let element = ( 98 |
title
99 | ) 100 | 101 | console.log(element); 102 | ``` 103 | 104 | ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/01525f10ca504a659c2d460fdbf5ff6a~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=944&h=220&s=40413&e=png&b=ffffff) 105 | 106 | 可以看到这里打印出来我们需要的虚拟DOM的结构 107 | 108 | 首先我们要先搞懂react16的fiber架构是怎样的,fiber架构是 React在16以后引入的,之前是直接递归渲染vdom,现在则是多了一步vdom(vdom即是虚拟DOM,后面都简称vdom)转fiber的reconcile,dom diff也是用新的vdom和老的fiber树去对比,然后生成的fiber树,那fiber树的结构是怎样的,可以看下面这个图 109 | 110 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f3e3cbcb1e2f499f9757035bf92e1587~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=649&h=464&s=92812&e=png&b=f7f7f7) 111 | 112 | fiberTree都有一个根节点rootFiber,父节点的child是子节点,子节点的return是兄弟节点 113 | 114 | 那么react是如何构建一个新的fiber树呢,流程大概如下 115 | 116 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7e28b0ae39394940acbec4091b152834~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=694&h=513&s=107819&e=png&b=f9f9f6) 117 | 118 | 当一个节点没有子节点了,他就走completework,然后找右边的兄弟元素,兄弟元素没有子节点也走completework,直到所有兄弟元素走完completework就回到父节点,父节点走completework 119 | 120 | 看一个更复杂的例子 121 | 122 | ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/316a030608b0496594c8714d6a69aefb~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=663&h=343&s=150683&e=png&b=f7f6f6) 123 | 124 | 大概就是这么一个流程 125 | 126 | 那我们再来仔细看看fiber tree的具体结构,fiberRootNode其实就是我们的root容器,它的current指向fiber tree 的根节点rootFiber,rootFiber的stateNode则指向我们的root容器 127 | 128 | 在`React`中最多会同时存在两棵`Fiber树`。当前屏幕上显示内容对应的`Fiber树`称为`current Fiber树`,正在内存中构建的`Fiber树`称为`workInProgress Fiber树`。 129 | 130 | `current Fiber树`中的`Fiber节点`被称为`current fiber`,`workInProgress Fiber树`中的`Fiber节点`被称为`workInProgress fiber`,他们通过`alternate`属性连接。 131 | 132 | 即当`workInProgress Fiber树`构建完成交给`Renderer`渲染在页面上后,应用根节点的`current`指针指向`workInProgress Fiber树`,此时`workInProgress Fiber树`就变为`current Fiber树`。 133 | 134 | 每次状态更新都会产生新的`workInProgress Fiber树`,通过`current`与`workInProgress`的替换,完成`DOM`更新。 135 | 136 | 这里`workInProgress Fiber树`其实就是通过对比老的`current Fiber树`和`新的虚拟DOM diff`出来生成的 137 | 138 | ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8de53e3704db446a92773c6c3a5c7b68~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1100&h=646&s=452887&e=png&b=fcfbfb) 139 | 140 | 那我们就来一步步实现吧,直接开始实现ReactDOM.render 141 | 142 | ```index.js 143 | import React from './reactCode/React'; 144 | import ReactDOM from './reactCode/ReactDOM'; 145 | 146 | let element = ( 147 |
title
148 | ) 149 | 150 | ReactDOM.render(element, document.getElementById('root')); 151 | 152 | ``` 153 | 154 | ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2403fde6422a4f84a9371e1795b66794~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=745&h=398&s=88303&e=png&b=fbfbfb) 155 | 156 | 首先会创建fiberRootNode和hostRootFiber,然后将fiberRootNode的current指向hostRootFiber,在第一次渲染的时候,其实react就已经开始使用双缓存树了,先建一个current tree,这个tree上只有hostRootFiber这个根节点,然后再从hostRootFiber的updateQuene里面拿到真正要渲染的部分,就是`
title
` 157 | 158 | **也就是说,react在第一次渲染的时候就使用双缓存树,利用新的vDOM和老的current tree 去 dom diff生成 workInProgress tree** 159 | 160 | 当第一次渲染结束之后就会变成这样 161 | 162 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e5f6f7453ad74908aa2791c074b13338~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1100&h=646&s=450096&e=png&b=fcfbfb) 163 | 164 | 然后此时开始进行下图的流程 165 | 166 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b5d2599aac204ea391c249d0d35c9baa~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1952&h=1182&s=523351&e=png&b=f8f8f8) 167 | 168 | 这是更加仔细的流程 169 | 170 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/291bef035d5848fb91586f11f0310957~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=230&h=234&s=57488&e=png&b=fbf9f9) 171 | 172 | 来看下具体代码的实现吧 173 | 174 | 代码实在有点多,感兴趣的可以直接跳到 https://github.com/forturegrant/mini-react 175 | 176 | 里面有全部代码的实现 177 | 178 | ```reactCode/reactDOM.js 179 | import { createHostRootFiber } from './ReactFiber'; 180 | import { updateContainer } from './ReactFiberReconciler'; 181 | import { initializeUpdateQuene } from './ReactUpdateQuene'; 182 | 183 | // ReactDom.render 开始把虚拟dom渲染到容器中 184 | function render(element, container) { 185 | let fiberRoot = container._reactRootContainer; 186 | if (!fiberRoot) { 187 | fiberRoot = container._reactRootContainer = createFiberRoot(container); 188 | } 189 | updateContainer(element, fiberRoot); 190 | } 191 | 192 | export const ReactDOM = { 193 | render 194 | }; 195 | 196 | // createFiberRoot 创建fiberRootNode(真实dom,id = 'root')和hostRootFiber(stateNode指向fiberRootNode) 197 | 198 | function createFiberRoot(containerInfo) { 199 | const fiberRoot = { containerInfo }; // fiberRoot指的就是容器对象containerInfo div#root 200 | const hostRootFiber = createHostRootFiber(); // 创建fiber树的根节点 这两个对应上面说的 201 | // 当前fiberRoot的current指向这个根fiber 202 | // current当前的意思,它指的是当前跟我们页面中真实dom相同的fiber树 203 | fiberRoot.current = hostRootFiber; 204 | // 让此根fiber的真实节点指向fiberRoot div#root stateNode就是指真实dom的意思 205 | hostRootFiber.stateNode = fiberRoot; 206 | initializeUpdateQuene(hostRootFiber); 207 | return fiberRoot; 208 | } 209 | 210 | export * from './ReactFiberHooks'; 211 | ``` 212 | 213 | reactFiber代码里主要是创建了fiber这种数据结构,react16的fiber架构就是基于fiber节点实现的 214 | 215 | ```reactCode/reactFiber.js 216 | import { NoFlags } from './ReactFiberFlags'; 217 | import { FunctionComponent, HostComponent, HostRoot } from './ReactWorkTags'; 218 | 219 | export function createHostRootFiber() { 220 | return createFiber(HostRoot); 221 | } 222 | 223 | /** 224 | * 创建fiber节点 225 | * @date 2023-04-24 226 | * @param {any} tag fiber的标签 HostRoot指的是根结点(HostRoot的tag是3,对应的真实dom节点 div#root) HostComponent(tag是5,例如div,span) 227 | * @param {any} pendingProps 等待生效的属性对象 228 | * @param {any} key 229 | * @returns {any} 230 | */ 231 | function createFiber(tag, pendingProps, key) { 232 | return new FiberNode(tag, pendingProps, key); 233 | } 234 | 235 | function FiberNode(tag, pendingProps, key) { 236 | this.tag = tag; 237 | this.pendingProps = pendingProps; 238 | this.key = key; 239 | } 240 | 241 | export function createWorkInProgress(current, pendingProps) { 242 | let workInProgress = current.alternate; 243 | if (!workInProgress) { 244 | workInProgress = createFiber(current.tag, pendingProps, current.key) 245 | workInProgress.type = current.type; 246 | workInProgress.stateNode = current.stateNode; 247 | workInProgress.alternate = current; 248 | current.alternate = workInProgress; 249 | } else { 250 | workInProgress.pendingProps = pendingProps; 251 | } 252 | workInProgress.flags = NoFlags; 253 | workInProgress.child = null; 254 | workInProgress.sibling = null; 255 | workInProgress.updateQuene = current.updateQuene; 256 | workInProgress.firstEffect = workInProgress.lastEffect = workInProgress.nextEffect = null; 257 | return workInProgress; 258 | } 259 | 260 | /** 261 | * 根据虚拟DOM元素创建fiber节点 262 | * @param {*} element 263 | * @returns 264 | */ 265 | export function createFiberFromElement(element) { 266 | const { key, type, props } = element; 267 | let tag; 268 | if (typeof type === 'string') { // span div p 269 | tag = HostComponent; // 标签等于原生组建 270 | } 271 | if (typeof type === 'function') { 272 | tag = FunctionComponent; 273 | } 274 | const fiber = createFiber(tag, props, key); 275 | fiber.type = type; 276 | return fiber; 277 | } 278 | 279 | ``` 280 | #### DOM diff 281 | 282 | 接下来重点讲一下**dom diff**的过程 283 | 284 | dom diff可以说是react源码里面最重要的部分了 285 | 286 | 我们举个例子再从代码里一步步分析react是如何进行dom diff的吧 287 | 288 | react diff算法分为两种情况 289 | 290 | ##### 新的vDOM是单个节点 291 | 292 | 我们先说说单个节点的情况,上面我们已经实现了**ReactDOM.render**,但我们还没实现setState或者hook useState,没有办法通过这两个办法去触发更新,但是其实**ReactDOM.render和setState/useState后面的逻辑是差不多的**,都是生成新的vDOM去和老的fiber tree进行dom diff生成新的fiber tree,我们就还是用ReactDOM.render去触发更新 293 | 294 | 这里先直接在html里写入两个按钮,一个负责初次渲染,一个负责更新 295 | 296 | ```index.html 297 | 298 |
299 | 300 |
301 |
302 | 303 | 304 |
305 | 306 | ``` 307 | 然后监听事件 308 | 309 | ```index.js 310 | import React from './reactCode/React'; 311 | import { ReactDOM } from './reactCode/ReactDOM'; 312 | 313 | const single1 = document.getElementById('single1'); 314 | const single1Update = document.getElementById('single1Update'); 315 | 316 | single1.addEventListener('click', () => { 317 | let element = ( 318 |
title
319 | ) 320 | console.log(element, 'element'); 321 | ReactDOM.render(element, document.getElementById('root')); 322 | }) 323 | 324 | single1Update.addEventListener('click', () => { 325 | let element = ( 326 |
title2
327 | ) 328 | ReactDOM.render(element, document.getElementById('root')); 329 | }) 330 | 331 | // ReactDOM.render(element, document.getElementById('root')); 332 | 333 | ``` 334 | 这里我们点击onClick,我们看看控制台打印了什么 335 | 336 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fbf066478d8f482da3e2f1da12987fdd~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1025&h=693&s=57846&e=png&b=ffffff) 337 | 338 | 打印出来了一个**effectList**和根节点的结构 339 | 340 | 这里的**effectList**是什么,每次react重新渲染的时候(包括第一次mounted,从前面我们知道mounted也会走dom diff的流程),然后dom diff的结果会以链表的形式存在根节点上,就是这个**firstEffect**,那这个**effectList**是怎么形成的,effects是通过nextEffect自下而上拼接起来的一个链表,然后react再去根据这个链表是进行对应的dom操作(其实就是我们常说的commit操作),其实就是在上面的workLoopSync结束之后,进行commitMutationEffects 341 | 342 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d3a5297622dd4cc1bc1a174c23dfbc40~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=720&h=358&s=86508&e=png&b=f8f8f6) 343 | 344 | 上面的render之后可以看到我们打印出来的**插入#div#title effectsList**,这时候react就会根据我们的flags去进行对应的dom操作,可以看到react给这些标识都行了二进制的处理 345 | 346 | ```ReactFiberWorkLoop.js 347 | function getFlags(flags) { 348 | switch (flags) { 349 | case Placement: 350 | return '插入'; 351 | case Update: 352 | return '更新'; 353 | case PlacementAndUpdate: 354 | return '移动'; 355 | case Deletion: 356 | return '删除'; 357 | default: 358 | break; 359 | } 360 | } 361 | 362 | export const NoFlags = /* */ 0b0000000000000000000000000000;// 二进制0 没有动作 363 | export const Placement = /* */ 0b0000000000000000000000000010;// 二进制2 添加或者创建挂载 364 | export const Update = /* */ 0b0000000000000000000000000100;// 二进制4 更新 365 | export const PlacementAndUpdate = /* */ 0b0000000000000000000000000110;// 二进制6 移动 366 | export const Deletion = /* */ 0b0000000000000000000000001000;// 二进制8 删除 367 | 368 | ``` 369 | 那我们点击update按钮来看看,可以看到更新了dom,effectList也发生了改变,flags变成了4,是update 370 | 371 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2501bce8ff644e0587bc5acc69354de3~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=903&h=738&s=63615&e=png&b=ffffff) 372 | 373 | 那这里如果新的vDOM只有一个节点他到底是怎么进行dom diff的呢 374 | 375 | 比较简单,如果是新的虚拟dom是单个节点,那就是直接去老的fiber里遍历,如果key相同,type相同,就搭上update标签,删掉其他的,如果key不同,继续找下一个,如果都不相同,那就全部删掉,自己新增一个 376 | 377 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/96cfc4676436458c869055663c8ae1eb~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=589&h=260&s=24999&e=png&b=202020) 378 | 379 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dcbc188cc9324036ba4b0975af85f1f5~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=983&h=739&s=70223&e=png&b=ffffff) 380 | 381 | 这里我把key改成不同的,可以看到,如果key不同,就直接删掉,然后新增了一个 382 | 383 | ##### 新的vDOM是多个节点 384 | 385 | 多节点的情况比较复杂,我们还是通过代码来看效果 386 | 387 | ```index.js 388 | import React from './reactCode/React'; 389 | import { ReactDOM } from './reactCode/ReactDOM'; 390 | 391 | const single1 = document.getElementById('single1'); 392 | const single1Update = document.getElementById('single1Update'); 393 | 394 | const single2 = document.getElementById('single2'); 395 | const single2Update = document.getElementById('single2Update'); 396 | 397 | single1.addEventListener('click', () => { 398 | let element = ( 399 |
title
400 | ) 401 | ReactDOM.render(element, document.getElementById('root')); 402 | }) 403 | 404 | single1Update.addEventListener('click', () => { 405 | let element = ( 406 |
title2
407 | ) 408 | ReactDOM.render(element, document.getElementById('root')); 409 | }) 410 | 411 | single2.addEventListener('click', () => { 412 | let element = ( 413 | 421 | ) 422 | ReactDOM.render(element, document.getElementById('root')); 423 | }) 424 | 425 | single2Update.addEventListener('click', () => { 426 | let element = ( 427 | 435 | ) 436 | ReactDOM.render(element, document.getElementById('root')); 437 | }) 438 | 439 | // ReactDOM.render(element, document.getElementById('root')); 440 | 441 | ``` 442 | 443 | 我们加多两个按钮来模拟多节点diff的情况 444 | 445 | ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e1fad12231594e15b157ea3ecdc71e87~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=985&h=570&s=81587&e=png&b=ffffff) 446 | 447 | 可以看到这里打印出来的effectList,这里我们再来仔细讲讲多节点dom diff的流程是怎么样的 448 | 449 | **如果新的虚拟dom是多个节点,那就先进入第一轮遍历,遍历的时候一一对应,如果不能复用,就立马跳出第一轮循环,进入第二轮循环,将剩余的老fiber放入一个以老fiberkey或者索引为key,value为fiber节点的Map中,然后遍历新dom到map中去找有没有可以复用的节点, 450 | 找到了就看这个节点的索引值是否大于lastplaceIndex,如果大就把lastplaceIndex置为这个fiber的索引,然后从map中删除该节点,如果小于lastPlaceIndex,就打上移动的标签** 451 | 452 | lastPlaceIndex这个索引值,其实说的有点复杂,其实可以理解为老的fiber就只能往右移,并不会像左移动,像这个例子移动的只有B和D,C并不会往左移动,但是实现是通过lastPlaceIndex来实现的 453 | 454 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ae86a78d9780465d9c9db09c90305eee~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=758&h=387&s=239674&e=png&b=fbfbfb) 455 | 456 | 再看一个例子,比如现在老fiber是 C=>B=>A 的结构,但是新DOM是A=>C>=B,那节点应该是怎么移动的 457 | 458 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/765f996e7faf4be9940575d70edd9537~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=831&h=547&s=80810&e=png&b=fefefe) 459 | 460 | 答案是移动C再移动B,其实最佳的方案应该是直接移动A到最前面就只用移动一个节点,但是由于react的 diff 设计如此,react diff 的时候就是单侧的,vue 的 dom diff就不是单侧的,而是双侧的,效率会更高,这里就不发散了,有兴趣的同学可以去了解下vue的dom diff。 461 | 462 | 这里同学们肯定还有一个疑问,为什么这里显示的是 **插入** 而不是 **移动** ,这是因为react在最后处理真实dom的时候使用了[node.appendChild](https://developer.mozilla.org/zh-CN/docs/Web/API/Node/appendChild) 或者 [node.insertBefore](https://developer.mozilla.org/zh-CN/docs/Web/API/Node/insertBefore) ,这两个api **方法在插入节点的时候,如果给定的子节点是对文档中现有节点的引用,会将其从当前位置移动到新位置,不用重新创建一个节点,这就巧妙的达到了移动的效果,同时需要重新创建节点时候也是用这个api,也就是说这两个方法实现了移动的效果,react就不用去处理移动的问题** 463 | 464 | 所以react源码中只有需要更新且插入节点的时候,才会标记为移动, 465 | 移动(不需要更新的移动)和插入共用Placement这个标识 466 | 467 | ```js 468 | function getFlags(flags) { 469 | switch (flags) { 470 | case Placement: 471 | return '插入'; 472 | case Update: 473 | return '更新'; 474 | case PlacementAndUpdate: 475 | return '移动'; 476 | case Deletion: 477 | return '删除'; 478 | default: 479 | break; 480 | } 481 | } 482 | ``` 483 | 484 | #### 总结 485 | 大概就讲到这里,主要内容还是讲了react大概的实现和dom diff,主要就讲了ReactDOM.render的实现,setState 和 hooks 的知识也没讲到,还有调度机制lane也没有讲,后面也会继续更新hooks相关的内容 486 | --------------------------------------------------------------------------------