Skip to content

Commit 5745b07

Browse files
committed
feat: performance
1 parent 45de076 commit 5745b07

23 files changed

+682
-82
lines changed

demos/performance/Context.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useContext, createContext } from 'react';
1+
import { useState, useContext, createContext, memo } from 'react';
22

33
const ctx = createContext(0);
44

@@ -18,14 +18,14 @@ export default function App() {
1818
);
1919
}
2020

21-
function Cpn() {
21+
const Cpn = memo(function () {
2222
console.log('Cpn render');
2323
return (
2424
<div>
2525
<Child />
2626
</div>
2727
);
28-
}
28+
});
2929

3030
function Child() {
3131
console.log('Child render');

demos/performance/Hook.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import { useState, useContext, createContext, memo } from 'react';
2-
3-
const ctx = createContext(0);
1+
import { useState, memo, useCallback } from 'react';
42

53
export default function App() {
64
const [num, update] = useState(0);
75
console.log('App render ', num);
86

9-
const addOne = () => update((num) => num + 1);
7+
const addOne = useCallback(() => update((num) => num + 1), []);
108

119
return (
1210
<div>

demos/performance/Principle_demo1.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ export default function App() {
88
<div>
99
<button onClick={() => update(num + 1)}>+ 1</button>
1010
<p>num is: {num}</p>
11-
<ExpensiveChild />
11+
<ExpensiveSubtree />
1212
</div>
1313
);
1414
}
1515

16-
function ExpensiveChild() {
17-
console.log('ExpensiveChild render');
16+
function ExpensiveSubtree() {
17+
console.log('ExpensiveSubtree render');
1818
return <p>i am child</p>;
1919
}

demos/performance/Principle_demo2.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ export default function App() {
1515
+ 1
1616
</button>
1717
<p>num is: {num}</p>
18-
<ExpensiveChild />
18+
<ExpensiveSubtree />
1919
</div>
2020
);
2121
}
2222

23-
function ExpensiveChild() {
24-
console.log('ExpensiveChild render');
23+
function ExpensiveSubtree() {
24+
console.log('ExpensiveSubtree render');
2525
return <p>i am child</p>;
2626
}

demos/performance/main.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { useState } from 'react';
22
import ReactDOM from 'react-dom/client';
33

44
// import App from './Simple';
5-
// import App from './Context';
6-
// import App from './BailoutBoundary';
5+
import App from './Context';
76
// import App from './Hook';
87
// import App from './Principle_demo1';
98
// import App from './Principle_demo2';
10-
import App from './Principle_demo3';
9+
// import App from './memo';
10+
// import App from './useMemo';
1111

1212
ReactDOM.createRoot(document.getElementById('root')).render(<App />);

demos/performance/BailoutBoundary.tsx renamed to demos/performance/memo.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import { useState, useContext, createContext } from 'react';
2-
3-
const ctx = createContext(0);
1+
import { useState, memo } from 'react';
42

53
export default function App() {
64
const [num, update] = useState(0);
@@ -13,15 +11,15 @@ export default function App() {
1311
);
1412
}
1513

16-
function Cpn({ num, name }) {
14+
const Cpn = memo(function ({ num, name }) {
1715
console.log('render ', name);
1816
return (
1917
<div>
2018
{name}: {num}
2119
<Child />
2220
</div>
2321
);
24-
}
22+
});
2523

2624
function Child() {
2725
console.log('Child render');
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import { useState, useContext, createContext, useMemo } from 'react';
22

3+
// 方式1:App提取 bailout四要素
4+
// 方式2:ExpensiveSubtree用memo包裹
35
export default function App() {
46
const [num, update] = useState(0);
57
console.log('App render ', num);
68

9+
const Cpn = useMemo(() => <ExpensiveSubtree />, []);
10+
711
return (
812
<div onClick={() => update(num + 100)}>
913
<p>num is: {num}</p>
10-
<ExpensiveChild />
14+
{Cpn}
1115
</div>
1216
);
1317
}
1418

15-
function ExpensiveChild() {
16-
console.log('ExpensiveChild render');
19+
function ExpensiveSubtree() {
20+
console.log('ExpensiveSubtree render');
1721
return <p>i am child</p>;
1822
}

packages/react-reconciler/src/beginWork.ts

Lines changed: 154 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import {
77
createFiberFromOffscreen,
88
OffscreenProps
99
} from './fiber';
10-
import { renderWithHooks } from './fiberHooks';
11-
import { Lane } from './fiberLanes';
10+
import { bailoutHook, renderWithHooks } from './fiberHooks';
11+
import { Lane, NoLanes, includeSomeLanes } from './fiberLanes';
1212
import { processUpdateQueue, UpdateQueue } from './updateQueue';
1313
import {
1414
ContextProvider,
@@ -17,6 +17,7 @@ import {
1717
HostComponent,
1818
HostRoot,
1919
HostText,
20+
MemoComponent,
2021
OffscreenComponent,
2122
SuspenseComponent
2223
} from './workTags';
@@ -27,11 +28,63 @@ import {
2728
Placement,
2829
ChildDeletion
2930
} from './fiberFlags';
30-
import { pushProvider } from './fiberContext';
31+
import {
32+
prepareToReadContext,
33+
propagateContextChange,
34+
pushProvider
35+
} from './fiberContext';
3136
import { pushSuspenseHandler } from './suspenseContext';
37+
import { cloneChildFibers } from './childFibers';
38+
import { shallowEqual } from 'shared/shallowEquals';
39+
40+
// 是否能命中bailout
41+
let didReceiveUpdate = false;
42+
43+
export function markWipReceivedUpdate() {
44+
didReceiveUpdate = true;
45+
}
3246

3347
// 递归中的递阶段
3448
export const beginWork = (wip: FiberNode, renderLane: Lane) => {
49+
// bailout策略
50+
didReceiveUpdate = false;
51+
const current = wip.alternate;
52+
53+
if (current !== null) {
54+
const oldProps = current.memoizedProps;
55+
const newProps = wip.pendingProps;
56+
// 四要素~ props type
57+
// {num: 0, name: 'cpn2'}
58+
// {num: 0, name: 'cpn2'}
59+
if (oldProps !== newProps || current.type !== wip.type) {
60+
didReceiveUpdate = true;
61+
} else {
62+
// state context
63+
const hasScheduledStateOrContext = checkScheduledUpdateOrContext(
64+
current,
65+
renderLane
66+
);
67+
if (!hasScheduledStateOrContext) {
68+
// 四要素~ state context
69+
// 命中bailout
70+
didReceiveUpdate = false;
71+
72+
switch (wip.tag) {
73+
case ContextProvider:
74+
const newValue = wip.memoizedProps.value;
75+
const context = wip.type._context;
76+
pushProvider(context, newValue);
77+
break;
78+
// TODO Suspense
79+
}
80+
81+
return bailouOnAlreadyFinishedWork(wip, renderLane);
82+
}
83+
}
84+
}
85+
86+
wip.lanes = NoLanes;
87+
3588
// 比较,返回子fiberNode
3689
switch (wip.tag) {
3790
case HostRoot:
@@ -41,15 +94,17 @@ export const beginWork = (wip: FiberNode, renderLane: Lane) => {
4194
case HostText:
4295
return null;
4396
case FunctionComponent:
44-
return updateFunctionComponent(wip, renderLane);
97+
return updateFunctionComponent(wip, wip.type, renderLane);
4598
case Fragment:
4699
return updateFragment(wip);
47100
case ContextProvider:
48-
return updateContextProvider(wip);
101+
return updateContextProvider(wip, renderLane);
49102
case SuspenseComponent:
50103
return updateSuspenseComponent(wip);
51104
case OffscreenComponent:
52105
return updateOffscreenComponent(wip);
106+
case MemoComponent:
107+
return updateMemoComponent(wip, renderLane);
53108
default:
54109
if (__DEV__) {
55110
console.warn('beginWork未实现的类型');
@@ -59,12 +114,80 @@ export const beginWork = (wip: FiberNode, renderLane: Lane) => {
59114
return null;
60115
};
61116

62-
function updateContextProvider(wip: FiberNode) {
117+
function updateMemoComponent(wip: FiberNode, renderLane: Lane) {
118+
// bailout四要素
119+
// props浅比较
120+
const current = wip.alternate;
121+
const nextProps = wip.pendingProps;
122+
const Component = wip.type.type;
123+
124+
if (current !== null) {
125+
const prevProps = current.memoizedProps;
126+
127+
// 浅比较props
128+
if (shallowEqual(prevProps, nextProps) && current.ref === wip.ref) {
129+
didReceiveUpdate = false;
130+
wip.pendingProps = prevProps;
131+
132+
// state context
133+
if (!checkScheduledUpdateOrContext(current, renderLane)) {
134+
// 满足四要素
135+
wip.lanes = current.lanes;
136+
return bailouOnAlreadyFinishedWork(wip, renderLane);
137+
}
138+
}
139+
}
140+
return updateFunctionComponent(wip, Component, renderLane);
141+
}
142+
143+
function bailouOnAlreadyFinishedWork(wip: FiberNode, renderLane: Lane) {
144+
if (!includeSomeLanes(wip.childLanes, renderLane)) {
145+
if (__DEV__) {
146+
console.warn('bailout整棵子树', wip);
147+
}
148+
return null;
149+
}
150+
151+
if (__DEV__) {
152+
console.warn('bailout一个fiber', wip);
153+
}
154+
cloneChildFibers(wip);
155+
return wip.child;
156+
}
157+
158+
function checkScheduledUpdateOrContext(
159+
current: FiberNode,
160+
renderLane: Lane
161+
): boolean {
162+
const updateLanes = current.lanes;
163+
164+
if (includeSomeLanes(updateLanes, renderLane)) {
165+
return true;
166+
}
167+
return false;
168+
}
169+
170+
function updateContextProvider(wip: FiberNode, renderLane: Lane) {
63171
const providerType = wip.type;
64172
const context = providerType._context;
65173
const newProps = wip.pendingProps;
174+
const oldProps = wip.memoizedProps;
175+
const newValue = newProps.value;
176+
177+
pushProvider(context, newValue);
178+
179+
if (oldProps !== null) {
180+
const oldValue = oldProps.value;
66181

67-
pushProvider(context, newProps.value);
182+
if (
183+
Object.is(oldValue, newValue) &&
184+
oldProps.children === newProps.children
185+
) {
186+
return bailouOnAlreadyFinishedWork(wip, renderLane);
187+
} else {
188+
propagateContextChange(wip, context, renderLane);
189+
}
190+
}
68191

69192
const nextChildren = newProps.children;
70193
reconcileChildren(wip, nextChildren);
@@ -77,8 +200,21 @@ function updateFragment(wip: FiberNode) {
77200
return wip.child;
78201
}
79202

80-
function updateFunctionComponent(wip: FiberNode, renderLane: Lane) {
81-
const nextChildren = renderWithHooks(wip, renderLane);
203+
function updateFunctionComponent(
204+
wip: FiberNode,
205+
Component: FiberNode['type'],
206+
renderLane: Lane
207+
) {
208+
prepareToReadContext(wip, renderLane);
209+
// render
210+
const nextChildren = renderWithHooks(wip, Component, renderLane);
211+
212+
const current = wip.alternate;
213+
if (current !== null && !didReceiveUpdate) {
214+
bailoutHook(wip, renderLane);
215+
return bailouOnAlreadyFinishedWork(wip, renderLane);
216+
}
217+
82218
reconcileChildren(wip, nextChildren);
83219
return wip.child;
84220
}
@@ -88,16 +224,24 @@ function updateHostRoot(wip: FiberNode, renderLane: Lane) {
88224
const updateQueue = wip.updateQueue as UpdateQueue<Element>;
89225
const pending = updateQueue.shared.pending;
90226
updateQueue.shared.pending = null;
227+
228+
const prevChildren = wip.memoizedState;
229+
91230
const { memoizedState } = processUpdateQueue(baseState, pending, renderLane);
92231
wip.memoizedState = memoizedState;
93232

94233
const current = wip.alternate;
95234
// 考虑RootDidNotComplete的情况,需要复用memoizedState
96235
if (current !== null) {
97-
current.memoizedState = memoizedState;
236+
if (!current.memoizedState) {
237+
current.memoizedState = memoizedState;
238+
}
98239
}
99240

100241
const nextChildren = wip.memoizedState;
242+
if (prevChildren === nextChildren) {
243+
return bailouOnAlreadyFinishedWork(wip, renderLane);
244+
}
101245
reconcileChildren(wip, nextChildren);
102246
return wip.child;
103247
}

packages/react-reconciler/src/childFibers.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,3 +333,23 @@ function updateFragment(
333333

334334
export const reconcileChildFibers = ChildReconciler(true);
335335
export const mountChildFibers = ChildReconciler(false);
336+
337+
export function cloneChildFibers(wip: FiberNode) {
338+
// child sibling
339+
if (wip.child === null) {
340+
return;
341+
}
342+
let currentChild = wip.child;
343+
let newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
344+
wip.child = newChild;
345+
newChild.return = wip;
346+
347+
while (currentChild.sibling !== null) {
348+
currentChild = currentChild.sibling;
349+
newChild = newChild.sibling = createWorkInProgress(
350+
newChild,
351+
newChild.pendingProps
352+
);
353+
newChild.return = wip;
354+
}
355+
}

0 commit comments

Comments
 (0)