跳到主要内容

模拟实现

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

一、实现


1.1 /packages/shared/ReactSymbols.js

export const REACT_MEMO_TYPE = supportSymbol
? Symbol.for('react.memo')
: 0xeac4;

1.2 /packages/react-reconciler/workTags.js

export const MemoComponent = 15;

1.3 /packages/react/memo.js

import { REACT_MEMO_TYPE } from '../shared/ReactSymbols';

export function memo(type, compare) {
const fiberType = {
$$typeof: REACT_MEMO_TYPE,
type,
compare: compare === undefined ? null : compare
};

return fiberType;
}

1.4 /packages/react-reconciler/fiber.js

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') {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
fiberTag = ContextProvider;
break;
case REACT_MEMO_TYPE:
fiberTag = MemoComponent;
break;
default:
break;
}
} 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;
}

1.5 /packages/react-reconciler/beginWork.js

function updateMemoComponent(workInProgress, renderLane) {
/**
* @description: 满足 bailout 四要素
* 1. props 浅比较
*/
const current = workInProgress.alternate;
const nextProps = workInProgress.pendingProps;
const Component = workInProgress.type.type;

if (current != null) {
const prevProps = current.memoizedProps;

if (
shallowEqual(prevProps, nextProps) &&
current.ref === workInProgress.ref
) {
didReceiveUpdate = false;
workInProgress.pendingProps = prevProps;

if (!checkScheduledUpdateOrContext(current, renderLane)) {
workInProgress.lanes = current.lanes;
return bailoutOnAlreadyFinishedWork(workInProgress, renderLane);
}
}
}
return updateFunctionComponent(workInProgress, Component, renderLane);
}


export const beginWork = (workInProgress, renderLane) => {
// bailout 策略
didReceiveUpdate = false;
const current = workInProgress.alternate;

if (current != null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (oldProps !== newProps || current.type !== workInProgress.type) {
didReceiveUpdate = true;
} else {
const hasScheduledStateOrContext = checkScheduledUpdateOrContext(
current,
renderLane
);
if (!hasScheduledStateOrContext) {
didReceiveUpdate = false;

switch (workInProgress.tag) {
case ContextProvider:
const newValue = workInProgress.memoizedProps.value;
const context = workInProgress.type._context;
pushProvider(context, newValue);
break;
default:
break;
}

return bailoutOnAlreadyFinishedWork(workInProgress, renderLane);
}
}
}

workInProgress.lanes = NoLanes;

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,
workInProgress.type,
renderLane
);
case ContextProvider:
return updateContextProvider(workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(workInProgress);
case OffscreenComponent:
return updateOffscreenComponent(workInProgress);
case MemoComponent:
return updateMemoComponent(workInProgress, renderLane);
default:
console.log('beginWork 未实现的类型');
break;
}
return null;
};

1.6 /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;
case MemoComponent:
bubbleProperties(workInProgress);
return;
default:
console.log('completeWork 未实现的类型');
break;
}
};

1.7 /packages/shared/shallowEquals.js

export function shallowEqual(objA, objB) {
if (Object.is(objA, objB)) {
return true;
}

if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}

const keysA = Object.keys(objA);
const keysB = Object.keys(objB);

if (keysA.length !== keysB.length) {
return false;
}

for (let i = 0; i < keysA.length; i++) {
const currentKey = keysA[i];
if (
!Object.hasOwnProperty.call(objB, currentKey) ||
!Object.is(objA[currentKey], objB[currentKey])
) {
return false;
}
}

return true;
}

二、测试


<!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 React.memo</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: { memo, useState, createElement }
} = React;

function Cpn(props) {
console.log('Cpn Render', props);

return createElement('div', {}, 'Cpn');
}

const MemoCpn = memo(Cpn);

function App() {
const [num, setNum] = useState(0);

return createElement('div', { onClick: () => setNum(num + 1) }, [
createElement(MemoCpn, { num: num }),
createElement(MemoCpn, { num: 0 })
]);
}

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