Skip to content

Commit

Permalink
feat: ref
Browse files Browse the repository at this point in the history
  • Loading branch information
BetaSu committed Apr 20, 2023
1 parent 8e6630e commit 41e1b4a
Show file tree
Hide file tree
Showing 15 changed files with 181 additions and 35 deletions.
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" />
4 changes: 2 additions & 2 deletions demos/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export default defineConfig({
find: 'hostConfig',
replacement: path.resolve(
__dirname,
'../packages/react-noop-renderer/src/hostConfig.ts'
// '../packages/react-dom/src/hostConfig.ts'
// '../packages/react-noop-renderer/src/hostConfig.ts'
'../packages/react-dom/src/hostConfig.ts'
)
}
]
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"license": "MIT",
"scripts": {
"build:dev": "rm -rf dist && rollup --config scripts/rollup/dev.config.js",
"demo": "vite serve demos/noop-renderer --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 --config scripts/jest/jest.config.js"
},
Expand Down
13 changes: 13 additions & 0 deletions packages/react-reconciler/src/beginWork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
HostRoot,
HostText
} from './workTags';
import { Ref } from './fiberFlags';

export const beginWork = (workInProgress: FiberNode, renderLanes: Lanes) => {
if (__LOG__) {
Expand Down Expand Up @@ -55,6 +56,7 @@ function updateHostComponent(workInProgress: FiberNode, renderLanes: Lanes) {
// 根据element创建fiberNode
const nextProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
markRef(workInProgress.alternate, workInProgress);
reconcileChildren(workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
Expand Down Expand Up @@ -97,3 +99,14 @@ function reconcileChildren(
);
}
}

function markRef(current: FiberNode | null, workInProgress: FiberNode) {
const ref = workInProgress.ref;

if (
(current === null && ref !== null) ||
(current !== null && current.ref !== ref)
) {
workInProgress.flags |= Ref;
}
}
104 changes: 79 additions & 25 deletions packages/react-reconciler/src/commitWork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { FiberNode, FiberRootNode, PendingPassiveEffects } from './fiber';
import {
ChildDeletion,
Flags,
LayoutMask,
MutationMask,
NoFlags,
PassiveEffect,
PassiveMask,
Placement,
Update
Update,
Ref
} from './fiberFlags';
import { Effect, FCUpdateQueue } from './fiberHooks';
import { HookHasEffect } from './hookEffectTags';
Expand All @@ -29,42 +31,42 @@ import {
let nextEffect: FiberNode | null = null;

// 以DFS形式执行
export const commitMutationEffects = (
finishedWork: FiberNode,
root: FiberRootNode
const commitEffects = (
phrase: 'mutation' | 'layout',
mask: Flags,
callback: (fiber: FiberNode, root: FiberRootNode) => void
) => {
nextEffect = finishedWork;
return (finishedWork: FiberNode, root: FiberRootNode) => {
nextEffect = finishedWork;

while (nextEffect !== null) {
// 向下遍历
const child: FiberNode | null = nextEffect.child;
while (nextEffect !== null) {
// 向下遍历
const child: FiberNode | null = nextEffect.child;

if (
(nextEffect.subtreeFlags & (MutationMask | PassiveMask)) !== NoFlags &&
child !== null
) {
nextEffect = child;
} else {
// 向上遍历
up: while (nextEffect !== null) {
commitMutationEffectsOnFiber(nextEffect, root);
const sibling: FiberNode | null = nextEffect.sibling;

if (sibling !== null) {
nextEffect = sibling;
break up;
if ((nextEffect.subtreeFlags & mask) !== NoFlags && child !== null) {
nextEffect = child;
} else {
// 向上遍历
up: while (nextEffect !== null) {
callback(nextEffect, root);
const sibling: FiberNode | null = nextEffect.sibling;

if (sibling !== null) {
nextEffect = sibling;
break up;
}
nextEffect = nextEffect.return;
}
nextEffect = nextEffect.return;
}
}
}
};
};

const commitMutationEffectsOnFiber = (
finishedWork: FiberNode,
root: FiberRootNode
) => {
const flags = finishedWork.flags;
const { flags, tag } = finishedWork;

if ((flags & Placement) !== NoFlags) {
// 插入/移动
Expand All @@ -90,8 +92,59 @@ const commitMutationEffectsOnFiber = (
commitPassiveEffect(finishedWork, root, 'update');
finishedWork.flags &= ~PassiveEffect;
}
if ((flags & Ref) !== NoFlags && tag === HostComponent) {
safelyDetachRef(finishedWork);
}
};

function safelyDetachRef(current: FiberNode) {
const ref = current.ref;
if (ref !== null) {
if (typeof ref === 'function') {
ref(null);
} else {
ref.current = null;
}
}
}

const commitLayoutEffectsOnFiber = (
finishedWork: FiberNode,
root: FiberRootNode
) => {
const { flags, tag } = finishedWork;

if ((flags & Ref) !== NoFlags && tag === HostComponent) {
// 绑定新的ref
safelyAttachRef(finishedWork);
finishedWork.flags &= ~Ref;
}
};

function safelyAttachRef(fiber: FiberNode) {
const ref = fiber.ref;
if (ref !== null) {
const instance = fiber.stateNode;
if (typeof ref === 'function') {
ref(instance);
} else {
ref.current = instance;
}
}
}

export const commitMutationEffects = commitEffects(
'mutation',
MutationMask | PassiveMask,
commitMutationEffectsOnFiber
);

export const commitLayoutEffects = commitEffects(
'layout',
LayoutMask,
commitLayoutEffectsOnFiber
);

/**
* 难点在于目标fiber的hostSibling可能并不是他的同级sibling
* 比如: <A/><B/> 其中:function B() {return <div/>} 所以A的hostSibling实际是B的child
Expand Down Expand Up @@ -249,6 +302,7 @@ function commitDeletion(childToDelete: FiberNode, root: FiberRootNode) {
case HostComponent:
recordHostChildrenToDelete(hostChildrenToDelete, unmountFiber);
// 解绑ref
safelyDetachRef(unmountFiber);
return;
case HostText:
recordHostChildrenToDelete(hostChildrenToDelete, unmountFiber);
Expand Down
15 changes: 13 additions & 2 deletions packages/react-reconciler/src/completeWork.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { updateFiberProps } from 'react-dom/src/SyntheticEvent';
import { FiberNode } from './fiber';
import { NoFlags, Update } from './fiberFlags';
import { NoFlags, Ref, Update } from './fiberFlags';
import {
appendInitialChild,
createInstance,
Expand All @@ -15,6 +15,10 @@ import {
HostText
} from './workTags';

function markRef(fiber: FiberNode) {
fiber.flags |= Ref;
}

const appendAllChildren = (parent: Instance, workInProgress: FiberNode) => {
// 遍历workInProgress所有子孙 DOM元素,依次挂载
let node = workInProgress.child;
Expand Down Expand Up @@ -74,13 +78,20 @@ export const completeWork = (workInProgress: FiberNode) => {
// 不应该在此处调用updateFiberProps,应该跟着判断属性变化的逻辑,在这里打flag
// 再在commitWork中更新fiberProps,我准备把这个过程留到「属性变化」相关需求一起做
updateFiberProps(workInProgress.stateNode, newProps);
// 标记Ref
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
// 初始化DOM
const instance = createInstance(workInProgress.type, newProps);
// 挂载DOM
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;

// 标记Ref
if (workInProgress.ref !== null) {
markRef(workInProgress);
}
// TODO 初始化元素属性
}
// 冒泡flag
Expand Down
4 changes: 3 additions & 1 deletion packages/react-reconciler/src/fiber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export function createFiberFromElement(
element: ReactElement,
lanes: Lanes
): FiberNode {
const { type, key, props } = element;
const { type, key, props, ref } = element;
let fiberTag: WorkTag = FunctionComponent;

if (typeof type === 'string') {
Expand All @@ -123,6 +123,7 @@ export function createFiberFromElement(
const fiber = new FiberNode(fiberTag, props, key);
fiber.type = type;
fiber.lanes = lanes;
fiber.ref = ref;

return fiber;
}
Expand Down Expand Up @@ -166,6 +167,7 @@ export const createWorkInProgress = (
// 数据
wip.memoizedProps = current.memoizedProps;
wip.memoizedState = current.memoizedState;
wip.ref = current.ref;

wip.lanes = current.lanes;

Expand Down
4 changes: 3 additions & 1 deletion packages/react-reconciler/src/fiberFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ export const ChildDeletion = 0b00000000000000000000010000;

// useEffect
export const PassiveEffect = 0b00000000000000000000100000;
export const Ref = 0b00000000000000000001000000;

export const MutationMask = Placement | Update | ChildDeletion;
export const MutationMask = Placement | Update | ChildDeletion | Ref;
export const LayoutMask = Ref;

// 删除子节点可能触发useEffect destroy
export const PassiveMask = PassiveEffect | ChildDeletion;
18 changes: 16 additions & 2 deletions packages/react-reconciler/src/fiberHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,14 @@ export const renderWithHooks = (workInProgress: FiberNode, lane: Lane) => {

const HooksDispatcherOnMount: Dispatcher = {
useState: mountState,
useEffect: mountEffect
useEffect: mountEffect,
useRef: mountRef
};

const HooksDispatcherOnUpdate: Dispatcher = {
useState: updateState,
useEffect: updateEffect
useEffect: updateEffect,
useRef: updateRef
};

function mountState<State>(
Expand Down Expand Up @@ -226,6 +228,18 @@ function areHookInputsEqual(nextDeps: TEffectDeps, prevDeps: TEffectDeps) {
return true;
}

function mountRef<T>(initialValue: T): { current: T } {
const hook = mountWorkInProgressHook();
const ref = { current: initialValue };
hook.memoizedState = ref;
return ref;
}

function updateRef<T>(initialValue: T): { current: T } {
const hook = updateWorkInProgressHook();
return hook.memoizedState;
}

export interface Effect {
tag: Flags;
create: TEffectCallback | void;
Expand Down
2 changes: 2 additions & 0 deletions packages/react-reconciler/src/workLoop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
commitHookEffectListDestroy,
commitHookEffectListMount,
commitHookEffectListUnmount,
commitLayoutEffects,
commitMutationEffects
} from './commitWork';
import { completeWork } from './completeWork';
Expand Down Expand Up @@ -338,6 +339,7 @@ function commitRoot(root: FiberRootNode) {
root.current = finishedWork;

// 阶段3/3:Layout
commitLayoutEffects(finishedWork, root);

executionContext = prevExecutionContext;
} else {
Expand Down
5 changes: 5 additions & 0 deletions packages/react/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export const useEffect: Dispatcher['useEffect'] = (create, deps) => {
return dispatcher.useEffect(create, deps);
};

export const useRef: Dispatcher['useRef'] = (initialValue) => {
const dispatcher = resolveDispatcher() as Dispatcher;
return dispatcher.useRef(initialValue);
};

export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
currentDispatcher
};
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/currentDispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Action } from 'shared/ReactTypes';
export type Dispatcher = {
useState: <T>(initialState: (() => T) | T) => [T, Dispatch<T>];
useEffect: (callback: (() => void) | void, deps: any[] | void) => void;
useRef: <T>(initialValue: T) => { current: T };
};

export type Dispatch<State> = (action: Action<State>) => void;
Expand Down
Loading

0 comments on commit 41e1b4a

Please sign in to comment.