跳到主要内容

模拟实现

2023年06月11日
柏拉文
越努力,越幸运

一、实现


1.1 /packages/react/index.js

导出 Suspense 类型

export { Fragment, Suspense } from './jsx.js';

1.2 /kaSong/packages/react/jsx.js

声明 Suspense 类型

export const Suspense = REACT_SUSPENSE_TYPE;

1.3 /packages/react-reconciler/fiber.js

处理 Suspense 类型

export function createFiberFromElement(element, lanes) {
const { ref, key, type, props } = element;

let fiberTag = FunctionComponent;

if (typeof type === 'string') {
fiberTag = HostComponent;
} else if (
typeof type === 'object' &&
type.$$typeof === REACT_PROVIDER_TYPE
) {
fiberTag = ContextProvider;
} else if (type === REACT_SUSPENSE_TYPE) {
fiberTag = SuspenseComponent;
} else if (typeof type !== 'function') {
console.log('未定义的 type 类型', element);
}

const fiber = new FiberNode(fiberTag, props, key);

fiber.ref = ref;
fiber.type = type;
fiber.lanes = lanes;

return fiber;
}

……

export function createFiberFromOffscreen(pendingProps) {
const fiber = new FiberNode(OffscreenComponent, pendingProps, null);
return fiber;
}

1.4 /packages/react-reconciler/beginWork.js

export const beginWork = (workInProgress, renderLane) => {
switch (workInProgress.tag) {
case HostRoot:
return updateHostRoot(workInProgress, renderLane);
case HostText:
return null;
case Fragment:
return updateFragment(workInProgress);
case HostComponent:
return updateHostComponent(workInProgress);
case FunctionComponent:
return updateFunctionComponent(workInProgress, renderLane);
case ContextProvider:
return updateContextProvider(workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(workInProgress);
case OffscreenComponent:
return updateOffscreenComponent(workInProgress);
default:
console.log('beginWork 未实现的类型');
break;
}
return null;
};

1.5 /packages/react-reconciler/completeWork.js

export const completeWork = workInProgress => {
const newProps = workInProgress.pendingProps;
const current = workInProgress.alternate;

switch (workInProgress.tag) {
case HostComponent:
if (current != null && workInProgress.stateNode) {
updateFiberProps(workInProgress.stateNode, newProps);

if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
const instance = createInstance(workInProgress.type, newProps);
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;

if (workInProgress.ref !== null) {
markRef(workInProgress);
}
}

bubbleProperties(workInProgress);
return null;
case HostText:
if (current != null && workInProgress.stateNode) {
const oldText = current.memoizedProps.content;
const newText = newProps.content;

if (oldText !== newText) {
markUpdate(workInProgress);
}
} else {
const instance = createTextInstance(newProps.content);
workInProgress.stateNode = instance;
}
bubbleProperties(workInProgress);
return null;
case HostRoot:
case Fragment:
case FunctionComponent:
bubbleProperties(workInProgress);
return null;
case ContextProvider:
const context = workInProgress.type._context;
popProvider(context);
bubbleProperties(workInProgress);
return;
case SuspenseComponent:
popSuspenseHandler();
const offscreenFiber = workInProgress.child;
const isHidden = offscreenFiber.pendingProps.mode === 'hidden';
const currentOffscreenFiber = offscreenFiber.alternate;
if (currentOffscreenFiber != null) {
const wasHidden = currentOffscreenFiber.pendingProps.mode === 'hidden';
if (isHidden !== wasHidden) {
offscreenFiber.flags |= Visibility;
bubbleProperties(offscreenFiber);
}
} else if (isHidden) {
offscreenFiber.flags |= Visibility;
bubbleProperties(offscreenFiber);
}
bubbleProperties(workInProgress);
return;
case OffscreenComponent:
bubbleProperties(workInProgress);
return;
default:
console.log('completeWork 未实现的类型');
break;
}
};

1.6 /packages/react-reconciler/commitWork.js

export function commitMutationEffectsOnFiber(finishedWork, root) {
const { tag, flags } = finishedWork;

if ((flags & Placement) !== NoFlags) {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}

if ((flags & ChildDeletion) !== NoFlags) {
const deletions = finishedWork.deletions;
if (deletions != null) {
deletions.forEach(childToDelete => {
commitDeletion(childToDelete, root);
});
}
finishedWork.flags &= ~ChildDeletion;
}

if ((flags & Update) !== NoFlags) {
commitUpdate(finishedWork);
finishedWork.flags &= ~Update;
}

if ((flags & PassiveEffect) !== NoFlags) {
commitPassiveEffect(finishedWork, root, 'update');
finishedWork.flags &= ~PassiveEffect;
}

if ((flags & Ref) !== NoFlags && tag === HostComponent) {
safelyDetachRef(finishedWork);
}

if ((flags & Visibility) !== NoFlags && tag === OffscreenComponent) {
const isHidden = finishedWork.pendingProps.mode === 'hidden';
hideOrUnhideAllChildren(finishedWork, isHidden);
finishedWork.flags &= ~Visibility;
}

}

二、测试


3.1 Suspense use

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Mini KaSong use</title>
<script>
var process = {
env: {
NODE_ENV: 'development'
}
};
</script>
<script src="../dist/react.iife.js"></script>
</head>
<body>
<div id="root"></div>

<script>
const {
ReactDOM: { createRoot },
React: { use, Suspense, createElement }
} = React;

const delay = t =>
new Promise(r => {
setTimeout(r, t);
});

const cachePool = [];

function fetchData(id, timeout) {
const cache = cachePool[id];
if (cache) {
return cache;
}

return (cachePool[id] = delay(timeout).then(() => {
return { data: Math.random().toFixed(2) * 100 };
}));
}

function Child() {
const value = use(fetchData(0, 3000));

return createElement('div', {}, value.data);
}

function App() {
return createElement(
Suspense,
{ fallback: createElement('div', {}, 'Loading') },
createElement(Child)
);
// return createElement(Child);
}

const root = createRoot(document.getElementById('root'));
root.render(createElement(App));
</script>
</body>
</html>