Skip to content

Commit 41e1b4a

Browse files
committed
feat: ref
1 parent 8e6630e commit 41e1b4a

File tree

15 files changed

+181
-35
lines changed

15 files changed

+181
-35
lines changed

demos/ref/index.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8" />
6+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8+
<title>noop-renderer测试</title>
9+
</head>
10+
11+
<body>
12+
<div id="root"></div>
13+
<script type="module" src="main.tsx"></script>
14+
</body>
15+
16+
</html>

demos/ref/main.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useState, useEffect, useRef } from 'react';
2+
import { createRoot } from 'react-dom/client';
3+
4+
function App() {
5+
const [isDel, del] = useState(false);
6+
const divRef = useRef(null);
7+
8+
console.warn('render divRef', divRef.current);
9+
10+
useEffect(() => {
11+
console.warn('useEffect divRef', divRef.current);
12+
}, []);
13+
14+
return (
15+
<div ref={divRef} onClick={() => del(true)}>
16+
{isDel ? null : <Child />}
17+
</div>
18+
);
19+
}
20+
21+
function Child() {
22+
return <p ref={(dom) => console.warn('dom is:', dom)}>Child</p>;
23+
}
24+
25+
createRoot(document.getElementById('root') as HTMLElement).render(<App />);

demos/ref/vite-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="vite/client" />

demos/vite.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ export default defineConfig({
3434
find: 'hostConfig',
3535
replacement: path.resolve(
3636
__dirname,
37-
'../packages/react-noop-renderer/src/hostConfig.ts'
38-
// '../packages/react-dom/src/hostConfig.ts'
37+
// '../packages/react-noop-renderer/src/hostConfig.ts'
38+
'../packages/react-dom/src/hostConfig.ts'
3939
)
4040
}
4141
]

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"license": "MIT",
77
"scripts": {
88
"build:dev": "rm -rf dist && rollup --config scripts/rollup/dev.config.js",
9-
"demo": "vite serve demos/noop-renderer --config demos/vite.config.js --force",
9+
"demo": "vite serve demos/ref --config demos/vite.config.js --force",
1010
"lint": "eslint --ext .ts,.jsx,.tsx --fix --quiet ./packages",
1111
"test": "jest --config scripts/jest/jest.config.js"
1212
},

packages/react-reconciler/src/beginWork.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
HostRoot,
1212
HostText
1313
} from './workTags';
14+
import { Ref } from './fiberFlags';
1415

1516
export const beginWork = (workInProgress: FiberNode, renderLanes: Lanes) => {
1617
if (__LOG__) {
@@ -55,6 +56,7 @@ function updateHostComponent(workInProgress: FiberNode, renderLanes: Lanes) {
5556
// 根据element创建fiberNode
5657
const nextProps = workInProgress.pendingProps;
5758
const nextChildren = nextProps.children;
59+
markRef(workInProgress.alternate, workInProgress);
5860
reconcileChildren(workInProgress, nextChildren, renderLanes);
5961
return workInProgress.child;
6062
}
@@ -97,3 +99,14 @@ function reconcileChildren(
9799
);
98100
}
99101
}
102+
103+
function markRef(current: FiberNode | null, workInProgress: FiberNode) {
104+
const ref = workInProgress.ref;
105+
106+
if (
107+
(current === null && ref !== null) ||
108+
(current !== null && current.ref !== ref)
109+
) {
110+
workInProgress.flags |= Ref;
111+
}
112+
}

packages/react-reconciler/src/commitWork.ts

Lines changed: 79 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { FiberNode, FiberRootNode, PendingPassiveEffects } from './fiber';
22
import {
33
ChildDeletion,
44
Flags,
5+
LayoutMask,
56
MutationMask,
67
NoFlags,
78
PassiveEffect,
89
PassiveMask,
910
Placement,
10-
Update
11+
Update,
12+
Ref
1113
} from './fiberFlags';
1214
import { Effect, FCUpdateQueue } from './fiberHooks';
1315
import { HookHasEffect } from './hookEffectTags';
@@ -29,42 +31,42 @@ import {
2931
let nextEffect: FiberNode | null = null;
3032

3133
// 以DFS形式执行
32-
export const commitMutationEffects = (
33-
finishedWork: FiberNode,
34-
root: FiberRootNode
34+
const commitEffects = (
35+
phrase: 'mutation' | 'layout',
36+
mask: Flags,
37+
callback: (fiber: FiberNode, root: FiberRootNode) => void
3538
) => {
36-
nextEffect = finishedWork;
39+
return (finishedWork: FiberNode, root: FiberRootNode) => {
40+
nextEffect = finishedWork;
3741

38-
while (nextEffect !== null) {
39-
// 向下遍历
40-
const child: FiberNode | null = nextEffect.child;
42+
while (nextEffect !== null) {
43+
// 向下遍历
44+
const child: FiberNode | null = nextEffect.child;
4145

42-
if (
43-
(nextEffect.subtreeFlags & (MutationMask | PassiveMask)) !== NoFlags &&
44-
child !== null
45-
) {
46-
nextEffect = child;
47-
} else {
48-
// 向上遍历
49-
up: while (nextEffect !== null) {
50-
commitMutationEffectsOnFiber(nextEffect, root);
51-
const sibling: FiberNode | null = nextEffect.sibling;
52-
53-
if (sibling !== null) {
54-
nextEffect = sibling;
55-
break up;
46+
if ((nextEffect.subtreeFlags & mask) !== NoFlags && child !== null) {
47+
nextEffect = child;
48+
} else {
49+
// 向上遍历
50+
up: while (nextEffect !== null) {
51+
callback(nextEffect, root);
52+
const sibling: FiberNode | null = nextEffect.sibling;
53+
54+
if (sibling !== null) {
55+
nextEffect = sibling;
56+
break up;
57+
}
58+
nextEffect = nextEffect.return;
5659
}
57-
nextEffect = nextEffect.return;
5860
}
5961
}
60-
}
62+
};
6163
};
6264

6365
const commitMutationEffectsOnFiber = (
6466
finishedWork: FiberNode,
6567
root: FiberRootNode
6668
) => {
67-
const flags = finishedWork.flags;
69+
const { flags, tag } = finishedWork;
6870

6971
if ((flags & Placement) !== NoFlags) {
7072
// 插入/移动
@@ -90,8 +92,59 @@ const commitMutationEffectsOnFiber = (
9092
commitPassiveEffect(finishedWork, root, 'update');
9193
finishedWork.flags &= ~PassiveEffect;
9294
}
95+
if ((flags & Ref) !== NoFlags && tag === HostComponent) {
96+
safelyDetachRef(finishedWork);
97+
}
9398
};
9499

100+
function safelyDetachRef(current: FiberNode) {
101+
const ref = current.ref;
102+
if (ref !== null) {
103+
if (typeof ref === 'function') {
104+
ref(null);
105+
} else {
106+
ref.current = null;
107+
}
108+
}
109+
}
110+
111+
const commitLayoutEffectsOnFiber = (
112+
finishedWork: FiberNode,
113+
root: FiberRootNode
114+
) => {
115+
const { flags, tag } = finishedWork;
116+
117+
if ((flags & Ref) !== NoFlags && tag === HostComponent) {
118+
// 绑定新的ref
119+
safelyAttachRef(finishedWork);
120+
finishedWork.flags &= ~Ref;
121+
}
122+
};
123+
124+
function safelyAttachRef(fiber: FiberNode) {
125+
const ref = fiber.ref;
126+
if (ref !== null) {
127+
const instance = fiber.stateNode;
128+
if (typeof ref === 'function') {
129+
ref(instance);
130+
} else {
131+
ref.current = instance;
132+
}
133+
}
134+
}
135+
136+
export const commitMutationEffects = commitEffects(
137+
'mutation',
138+
MutationMask | PassiveMask,
139+
commitMutationEffectsOnFiber
140+
);
141+
142+
export const commitLayoutEffects = commitEffects(
143+
'layout',
144+
LayoutMask,
145+
commitLayoutEffectsOnFiber
146+
);
147+
95148
/**
96149
* 难点在于目标fiber的hostSibling可能并不是他的同级sibling
97150
* 比如: <A/><B/> 其中:function B() {return <div/>} 所以A的hostSibling实际是B的child
@@ -249,6 +302,7 @@ function commitDeletion(childToDelete: FiberNode, root: FiberRootNode) {
249302
case HostComponent:
250303
recordHostChildrenToDelete(hostChildrenToDelete, unmountFiber);
251304
// 解绑ref
305+
safelyDetachRef(unmountFiber);
252306
return;
253307
case HostText:
254308
recordHostChildrenToDelete(hostChildrenToDelete, unmountFiber);

packages/react-reconciler/src/completeWork.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { updateFiberProps } from 'react-dom/src/SyntheticEvent';
22
import { FiberNode } from './fiber';
3-
import { NoFlags, Update } from './fiberFlags';
3+
import { NoFlags, Ref, Update } from './fiberFlags';
44
import {
55
appendInitialChild,
66
createInstance,
@@ -15,6 +15,10 @@ import {
1515
HostText
1616
} from './workTags';
1717

18+
function markRef(fiber: FiberNode) {
19+
fiber.flags |= Ref;
20+
}
21+
1822
const appendAllChildren = (parent: Instance, workInProgress: FiberNode) => {
1923
// 遍历workInProgress所有子孙 DOM元素,依次挂载
2024
let node = workInProgress.child;
@@ -74,13 +78,20 @@ export const completeWork = (workInProgress: FiberNode) => {
7478
// 不应该在此处调用updateFiberProps,应该跟着判断属性变化的逻辑,在这里打flag
7579
// 再在commitWork中更新fiberProps,我准备把这个过程留到「属性变化」相关需求一起做
7680
updateFiberProps(workInProgress.stateNode, newProps);
81+
// 标记Ref
82+
if (current.ref !== workInProgress.ref) {
83+
markRef(workInProgress);
84+
}
7785
} else {
7886
// 初始化DOM
7987
const instance = createInstance(workInProgress.type, newProps);
8088
// 挂载DOM
8189
appendAllChildren(instance, workInProgress);
8290
workInProgress.stateNode = instance;
83-
91+
// 标记Ref
92+
if (workInProgress.ref !== null) {
93+
markRef(workInProgress);
94+
}
8495
// TODO 初始化元素属性
8596
}
8697
// 冒泡flag

packages/react-reconciler/src/fiber.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export function createFiberFromElement(
112112
element: ReactElement,
113113
lanes: Lanes
114114
): FiberNode {
115-
const { type, key, props } = element;
115+
const { type, key, props, ref } = element;
116116
let fiberTag: WorkTag = FunctionComponent;
117117

118118
if (typeof type === 'string') {
@@ -123,6 +123,7 @@ export function createFiberFromElement(
123123
const fiber = new FiberNode(fiberTag, props, key);
124124
fiber.type = type;
125125
fiber.lanes = lanes;
126+
fiber.ref = ref;
126127

127128
return fiber;
128129
}
@@ -166,6 +167,7 @@ export const createWorkInProgress = (
166167
// 数据
167168
wip.memoizedProps = current.memoizedProps;
168169
wip.memoizedState = current.memoizedState;
170+
wip.ref = current.ref;
169171

170172
wip.lanes = current.lanes;
171173

packages/react-reconciler/src/fiberFlags.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ export const ChildDeletion = 0b00000000000000000000010000;
77

88
// useEffect
99
export const PassiveEffect = 0b00000000000000000000100000;
10+
export const Ref = 0b00000000000000000001000000;
1011

11-
export const MutationMask = Placement | Update | ChildDeletion;
12+
export const MutationMask = Placement | Update | ChildDeletion | Ref;
13+
export const LayoutMask = Ref;
1214

1315
// 删除子节点可能触发useEffect destroy
1416
export const PassiveMask = PassiveEffect | ChildDeletion;

packages/react-reconciler/src/fiberHooks.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,14 @@ export const renderWithHooks = (workInProgress: FiberNode, lane: Lane) => {
6868

6969
const HooksDispatcherOnMount: Dispatcher = {
7070
useState: mountState,
71-
useEffect: mountEffect
71+
useEffect: mountEffect,
72+
useRef: mountRef
7273
};
7374

7475
const HooksDispatcherOnUpdate: Dispatcher = {
7576
useState: updateState,
76-
useEffect: updateEffect
77+
useEffect: updateEffect,
78+
useRef: updateRef
7779
};
7880

7981
function mountState<State>(
@@ -226,6 +228,18 @@ function areHookInputsEqual(nextDeps: TEffectDeps, prevDeps: TEffectDeps) {
226228
return true;
227229
}
228230

231+
function mountRef<T>(initialValue: T): { current: T } {
232+
const hook = mountWorkInProgressHook();
233+
const ref = { current: initialValue };
234+
hook.memoizedState = ref;
235+
return ref;
236+
}
237+
238+
function updateRef<T>(initialValue: T): { current: T } {
239+
const hook = updateWorkInProgressHook();
240+
return hook.memoizedState;
241+
}
242+
229243
export interface Effect {
230244
tag: Flags;
231245
create: TEffectCallback | void;

packages/react-reconciler/src/workLoop.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
commitHookEffectListDestroy,
44
commitHookEffectListMount,
55
commitHookEffectListUnmount,
6+
commitLayoutEffects,
67
commitMutationEffects
78
} from './commitWork';
89
import { completeWork } from './completeWork';
@@ -338,6 +339,7 @@ function commitRoot(root: FiberRootNode) {
338339
root.current = finishedWork;
339340

340341
// 阶段3/3:Layout
342+
commitLayoutEffects(finishedWork, root);
341343

342344
executionContext = prevExecutionContext;
343345
} else {

packages/react/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ export const useEffect: Dispatcher['useEffect'] = (create, deps) => {
1515
return dispatcher.useEffect(create, deps);
1616
};
1717

18+
export const useRef: Dispatcher['useRef'] = (initialValue) => {
19+
const dispatcher = resolveDispatcher() as Dispatcher;
20+
return dispatcher.useRef(initialValue);
21+
};
22+
1823
export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
1924
currentDispatcher
2025
};

packages/react/src/currentDispatcher.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Action } from 'shared/ReactTypes';
33
export type Dispatcher = {
44
useState: <T>(initialState: (() => T) | T) => [T, Dispatch<T>];
55
useEffect: (callback: (() => void) | void, deps: any[] | void) => void;
6+
useRef: <T>(initialValue: T) => { current: T };
67
};
78

89
export type Dispatch<State> = (action: Action<State>) => void;

0 commit comments

Comments
 (0)