Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix recordHostChildrenToDelete for fragment deletion #43

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
- 💪 功能完备,当前可跑通官方测试用例数量:34
- 🚶 按`Git Tag`划分迭代步骤,记录从 0 实现的每个功能

如果想加入项目对应的`源码交流群`,和 7000+小伙伴们一起交流`React`,可以加我微信,备注「开发」:
如果想跟着我学习「如何从0到1实现React18」,可以[点击这里](https://qux.xet.tech/s/2wiFh1)

<img width="200" src="https://user-images.githubusercontent.com/15828041/181666959-57941b01-61b3-47db-9d73-ecc9ae175112.png" alt="卡颂的微信" />

## TODO List

Expand Down
16 changes: 16 additions & 0 deletions demos/fragment/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>v11测试并发更新</title>
</head>

<body>
<div id="root"></div>
<script type="module" src="main.tsx"></script>
</body>

</html>
23 changes: 23 additions & 0 deletions demos/fragment/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';

function App() {
const [num, update] = useState(0);
function onClick() {
update(num + 1);
}

const arr =
num % 2 === 0
? [<li key="a">a</li>, <li key="b">b</li>, <li key="d">d</li>]
: [<li key="d">d</li>, <li key="c">c</li>, <li key="b">b</li>];

return (
<ul onClick={onClick}>
<></>
{arr}
</ul>
);
}

createRoot(document.getElementById('root') as HTMLElement).render(<App />);
1 change: 1 addition & 0 deletions demos/fragment/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
16 changes: 16 additions & 0 deletions demos/noop-renderer/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>noop-renderer测试</title>
</head>

<body>
<div id="root"></div>
<script type="module" src="main.tsx"></script>
</body>

</html>
21 changes: 21 additions & 0 deletions demos/noop-renderer/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useState, useEffect } from 'react';
import * as ReactNoop from 'react-noop-renderer';

const root = ReactNoop.createRoot();

function Parent() {
return (
<>
<Child />
<div>hello world</div>
</>
);
}

function Child() {
return 'Child';
}

root.render(<Parent />);

window.root = root;
1 change: 1 addition & 0 deletions demos/noop-renderer/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
16 changes: 16 additions & 0 deletions demos/ref/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>noop-renderer测试</title>
</head>

<body>
<div id="root"></div>
<script type="module" src="main.tsx"></script>
</body>

</html>
25 changes: 25 additions & 0 deletions demos/ref/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useState, useEffect, useRef } from 'react';
import { createRoot } from 'react-dom/client';

function App() {
const [isDel, del] = useState(false);
const divRef = useRef(null);

console.warn('render divRef', divRef.current);

useEffect(() => {
console.warn('useEffect divRef', divRef.current);
}, []);

return (
<div ref={divRef} onClick={() => del(true)}>
{isDel ? null : <Child />}
</div>
);
}

function Child() {
return <p ref={(dom) => console.warn('dom is:', dom)}>Child</p>;
}

createRoot(document.getElementById('root') as HTMLElement).render(<App />);
1 change: 1 addition & 0 deletions demos/ref/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
2 changes: 1 addition & 1 deletion demos/v11/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createRoot } from 'react-dom/client';

function App() {
const [num, updateNum] = useState(0);
const len = 1000;
const len = 8;

console.log('num', num);
return (
Expand Down
9 changes: 9 additions & 0 deletions demos/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,19 @@ export default defineConfig({
find: 'react-dom',
replacement: path.resolve(__dirname, '../packages/react-dom')
},
{
find: 'react-reconciler',
replacement: path.resolve(__dirname, '../packages/react-reconciler')
},
{
find: 'react-noop-renderer',
replacement: path.resolve(__dirname, '../packages/react-noop-renderer')
},
{
find: 'hostConfig',
replacement: path.resolve(
__dirname,
// '../packages/react-noop-renderer/src/hostConfig.ts'
'../packages/react-dom/src/hostConfig.ts'
)
}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
"license": "MIT",
"scripts": {
"build:dev": "rm -rf dist && rollup --config scripts/rollup/dev.config.js",
"demo": "vite serve demos/v11 --config demos/vite.config.js --force",
"demo": "vite serve demos/ref --config demos/vite.config.js --force",
"lint": "eslint --ext .ts,.jsx,.tsx --fix --quiet ./packages",
"test": "jest"
"test": "jest --config scripts/jest/jest.config.js"
},
"devDependencies": {
"@babel/core": "^7.18.6",
Expand Down
46 changes: 13 additions & 33 deletions packages/react-dom/src/SyntheticEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,27 @@ const { unstable_runWithPriority: runWithPriority } = Scheduler;

// 支持的事件类型
const validEventTypeList = ['click'];
export const elementEventPropsKey = '__props';
export const elementPropsKey = '__props';

type EventCallback = (e: SyntheticEvent) => void;
interface Paths {
capture: EventCallback[];
bubble: EventCallback[];
}
interface SyntheticEvent extends Event {
type: string;
__stopPropagation: boolean;
}

export interface PackagedElement extends Element {
[elementEventPropsKey]: {
[eventType: string]: EventCallback;
export interface DOMElement extends Element {
[elementPropsKey]: {
[key: string]: any;
};
}

function createSyntheticEvent(e: Event): SyntheticEvent {
const syntheticEvent = e as SyntheticEvent;
syntheticEvent.__stopPropagation = false;
const originStopPropagation = e.stopPropagation;
const originStopPropagation = e.stopPropagation.bind(e);

syntheticEvent.stopPropagation = () => {
syntheticEvent.__stopPropagation = true;
Expand All @@ -51,27 +50,8 @@ function getEventCallbackNameFromtEventType(
}

// 将支持的事件回调保存在DOM中
export const updateFiberProps = (
node: Element,
props: any
): PackagedElement => {
(node as PackagedElement)[elementEventPropsKey] =
(node as PackagedElement)[elementEventPropsKey] || {};

validEventTypeList.forEach((eventType) => {
const callbackNameList = getEventCallbackNameFromtEventType(eventType);

if (!callbackNameList) {
return;
}
callbackNameList.forEach((callbackName) => {
if (Object.hasOwnProperty.call(props, callbackName)) {
(node as PackagedElement)[elementEventPropsKey][callbackName] =
props[callbackName];
}
});
});
return node as PackagedElement;
export const updateFiberProps = (node: DOMElement, props: any) => {
(node as DOMElement)[elementPropsKey] = props;
};

const triggerEventFlow = (paths: EventCallback[], se: SyntheticEvent) => {
Expand All @@ -96,7 +76,7 @@ const dispatchEvent = (container: Container, eventType: string, e: Event) => {
}

const { capture, bubble } = collectPaths(
targetElement as PackagedElement,
targetElement as DOMElement,
container,
eventType
);
Expand All @@ -115,7 +95,7 @@ const dispatchEvent = (container: Container, eventType: string, e: Event) => {

// 收集从目标元素到HostRoot之间所有目标回调函数
const collectPaths = (
targetElement: PackagedElement,
targetElement: DOMElement,
container: Container,
eventType: string
): Paths => {
Expand All @@ -125,12 +105,12 @@ const collectPaths = (
};
// 收集事件回调是冒泡的顺序
while (targetElement && targetElement !== container) {
const eventProps = targetElement[elementEventPropsKey];
if (eventProps) {
const elementProps = targetElement[elementPropsKey];
if (elementProps) {
const callbackNameList = getEventCallbackNameFromtEventType(eventType);
if (callbackNameList) {
callbackNameList.forEach((callbackName, i) => {
const eventCallback = eventProps[callbackName];
const eventCallback = elementProps[callbackName];
if (eventCallback) {
if (i === 0) {
// 反向插入捕获阶段的事件回调
Expand All @@ -143,7 +123,7 @@ const collectPaths = (
});
}
}
targetElement = targetElement.parentNode as PackagedElement;
targetElement = targetElement.parentNode as DOMElement;
}
return paths;
};
Expand Down
11 changes: 6 additions & 5 deletions packages/react-dom/src/hostConfig.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { PackagedElement, updateFiberProps } from './SyntheticEvent';
import { DOMElement, updateFiberProps } from './SyntheticEvent';
import { FiberNode } from 'react-reconciler/src/fiber';
import { HostText } from 'react-reconciler/src/workTags';

export type Container = PackagedElement;
export type Instance = PackagedElement;
export type Container = Element;
export type Instance = DOMElement;
export type TextInstance = Text;

export const createInstance = (type: string, props: any): Instance => {
const element = document.createElement(type);
return updateFiberProps(element, props);
const element = document.createElement(type) as unknown;
updateFiberProps(element as DOMElement, props);
return element as DOMElement;
};

export const createTextInstance = (content: string) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/react-dom/src/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
createContainer
} from 'react-reconciler/src/fiberReconciler';
import { ReactElement } from 'shared/ReactTypes';
import { initEvent, elementEventPropsKey } from './SyntheticEvent';
import { initEvent, elementPropsKey } from './SyntheticEvent';

const containerToRoot = new Map();

Expand All @@ -14,7 +14,7 @@ function clearContainerDOM(container: Container) {
}
for (let i = 0; i < container.childNodes.length; i++) {
const childNode = container.childNodes[i];
if (!Object.hasOwnProperty.call(childNode, elementEventPropsKey)) {
if (!Object.hasOwnProperty.call(childNode, elementPropsKey)) {
container.removeChild(childNode);
// 当移除节点时,再遍历时length会减少,所以相应i需要减少一个
i--;
Expand Down
Loading