├── 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 |
33 | - A
34 | - B
35 | - C
36 | - D
37 | - E
38 | - F
39 |
40 | )
41 | ReactDOM.render(element, document.getElementById('root'));
42 | })
43 |
44 | single2Update.addEventListener('click', () => {
45 | let element = (
46 |
47 | - A
48 | - C
49 | - E
50 | - B2
51 | - G
52 | - D
53 |
54 | )
55 | ReactDOM.render(element, document.getElementById('root'));
56 | })
57 |
58 | single3.addEventListener('click', () => {
59 | let element = (
60 |
61 | - C
62 | - B
63 | - A
64 |
65 | )
66 | ReactDOM.render(element, document.getElementById('root'));
67 | })
68 |
69 | single3Update.addEventListener('click', () => {
70 | let element = (
71 |
72 | - A
73 | - C
74 | - B
75 |
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 |
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 |
56 | )
57 | ReactDOM.render(element, document.getElementById('root'));
58 | })
59 | ```
60 | 点击之后
61 |
62 | 
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 |
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 | 
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 | 
244 |
245 | 这样我们的useReducer就实现了
246 |
247 | 不过这里我们是使用button点击之后改变状态的,react是有一套属于自己的事件机制的,实现起来比较复杂,所以我们这里就稍微简单粗暴一下监听了一下事件的触发,如果属性是on开头,那就直接绑定事件
248 |
249 | 
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
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 | 
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 | 
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 | 
111 |
112 | fiberTree都有一个根节点rootFiber,父节点的child是子节点,子节点的return是兄弟节点
113 |
114 | 那么react是如何构建一个新的fiber树呢,流程大概如下
115 |
116 | 
117 |
118 | 当一个节点没有子节点了,他就走completework,然后找右边的兄弟元素,兄弟元素没有子节点也走completework,直到所有兄弟元素走完completework就回到父节点,父节点走completework
119 |
120 | 看一个更复杂的例子
121 |
122 | 
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 | 
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 | 
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 | 
163 |
164 | 然后此时开始进行下图的流程
165 |
166 | 
167 |
168 | 这是更加仔细的流程
169 |
170 | 
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 | 
337 |
338 | 打印出来了一个**effectList**和根节点的结构
339 |
340 | 这里的**effectList**是什么,每次react重新渲染的时候(包括第一次mounted,从前面我们知道mounted也会走dom diff的流程),然后dom diff的结果会以链表的形式存在根节点上,就是这个**firstEffect**,那这个**effectList**是怎么形成的,effects是通过nextEffect自下而上拼接起来的一个链表,然后react再去根据这个链表是进行对应的dom操作(其实就是我们常说的commit操作),其实就是在上面的workLoopSync结束之后,进行commitMutationEffects
341 |
342 | 
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 | 
372 |
373 | 那这里如果新的vDOM只有一个节点他到底是怎么进行dom diff的呢
374 |
375 | 比较简单,如果是新的虚拟dom是单个节点,那就是直接去老的fiber里遍历,如果key相同,type相同,就搭上update标签,删掉其他的,如果key不同,继续找下一个,如果都不相同,那就全部删掉,自己新增一个
376 |
377 | 
378 |
379 | 
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 |
414 | - A
415 | - B
416 | - C
417 | - D
418 | - E
419 | - F
420 |
421 | )
422 | ReactDOM.render(element, document.getElementById('root'));
423 | })
424 |
425 | single2Update.addEventListener('click', () => {
426 | let element = (
427 |
428 | - A
429 | - C
430 | - E
431 | - B
432 | - G
433 | - D
434 |
435 | )
436 | ReactDOM.render(element, document.getElementById('root'));
437 | })
438 |
439 | // ReactDOM.render(element, document.getElementById('root'));
440 |
441 | ```
442 |
443 | 我们加多两个按钮来模拟多节点diff的情况
444 |
445 | 
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 | 
455 |
456 | 再看一个例子,比如现在老fiber是 C=>B=>A 的结构,但是新DOM是A=>C>=B,那节点应该是怎么移动的
457 |
458 | 
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 |
--------------------------------------------------------------------------------