Compare commits

...

1 Commits

Author SHA1 Message Date
Dan Abramov
7b90545420 wip 2025-03-19 04:42:31 +09:00
3 changed files with 251 additions and 1 deletions

View File

@@ -261,7 +261,7 @@ function scheduleFibersWithFamiliesRecursively(
staleFamilies: Set<Family>,
): void {
if (__DEV__) {
const {alternate, child, sibling, tag, type} = fiber;
const {alternate, child, sibling, tag, type, elementType} = fiber;
let candidateType = null;
switch (tag) {
@@ -283,6 +283,11 @@ function scheduleFibersWithFamiliesRecursively(
let needsRender = false;
let needsRemount = false;
if (staleFamilies.has(resolveFamily(elementType))) {
console.log('gonna mark stale fiber for remount');
needsRemount = true;
}
if (candidateType !== null) {
const family = resolveFamily(candidateType);
if (family !== undefined) {

View File

@@ -146,6 +146,12 @@ function canPreserveStateBetween(prevType: any, nextType: any) {
if (isReactClass(prevType) || isReactClass(nextType)) {
return false;
}
if (
typeof prevType !== typeof nextType ||
getProperty(prevType, '$$typeof') !== getProperty(nextType, '$$typeof')
) {
return false;
}
if (haveEqualSignatures(prevType, nextType)) {
return true;
}
@@ -215,6 +221,7 @@ export function performReactRefresh(): RefreshUpdate | null {
if (canPreserveStateBetween(prevType, nextType)) {
updatedFamilies.add(family);
} else {
console.log('mark family as stale', family);
staleFamilies.add(family);
}
});

View File

@@ -699,6 +699,244 @@ describe('ReactFresh', () => {
}
});
fit('can remount when change function to memo', async () => {
if (__DEV__) {
await act(async () => {
await render(() => {
function Test() {
return <p>hi test</p>;
}
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check the initial render
const el = container.firstChild;
expect(el.textContent).toBe('hi test');
// Patch to change function to memo
await act(async () => {
await patch(() => {
function Test2() {
return <p>hi memo</p>;
}
const Test = React.memo(Test2);
$RefreshReg$(Test2, 'Test2');
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check remount
expect(container.firstChild !== el).toBe(true);
const nextEl = container.firstChild;
expect(nextEl.textContent).toBe('hi memo');
console.log('patch to original');
// Patch back to original function
await act(async () => {
await patch(() => {
function Test() {
return <p>hi test</p>;
}
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check final remount
expect(container.firstChild !== nextEl).toBe(true);
const newEl = container.firstChild;
expect(newEl.textContent).toBe('hi test');
}
});
it('can remount when change memo to forwardRef', async () => {
if (__DEV__) {
await act(async () => {
await render(() => {
function Test2() {
return <p>hi memo</p>;
}
const Test = React.memo(Test2);
$RefreshReg$(Test2, 'Test2');
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check the initial render
const el = container.firstChild;
expect(el.textContent).toBe('hi memo');
// Patch to change memo to forwardRef
await act(async () => {
await patch(() => {
function Test2() {
return <p>hi forwardRef</p>;
}
const Test = React.forwardRef(Test2);
$RefreshReg$(Test2, 'Test2');
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check remount
expect(container.firstChild).not.toBe(el);
const nextEl = container.firstChild;
expect(nextEl.textContent).toBe('hi forwardRef');
// Patch back to memo
await act(async () => {
await patch(() => {
function Test2() {
return <p>hi memo</p>;
}
const Test = React.memo(Test2);
$RefreshReg$(Test2, 'Test2');
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check final remount
expect(container.firstChild).not.toBe(nextEl);
const newEl = container.firstChild;
expect(newEl.textContent).toBe('hi memo');
}
});
it('can remount when change function to forwardRef', async () => {
if (__DEV__) {
await act(async () => {
await render(() => {
function Test() {
return <p>hi test</p>;
}
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check the initial render
const el = container.firstChild;
expect(el.textContent).toBe('hi test');
// Patch to change function to forwardRef
await act(async () => {
await patch(() => {
function Test2() {
return <p>hi forwardRef</p>;
}
const Test = React.forwardRef(Test2);
$RefreshReg$(Test2, 'Test2');
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check remount
expect(container.firstChild).not.toBe(el);
const nextEl = container.firstChild;
expect(nextEl.textContent).toBe('hi forwardRef');
// Patch back to a new function
await act(async () => {
await patch(() => {
function Test() {
return <p>hi test1</p>;
}
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check final remount
expect(container.firstChild).not.toBe(nextEl);
const newEl = container.firstChild;
expect(newEl.textContent).toBe('hi test1');
}
});
it('resets state when switching between different component types', async () => {
if (__DEV__) {
await act(async () => {
await render(() => {
function Test() {
const [count, setCount] = React.useState(0);
return (
<div onClick={() => setCount(c => c + 1)}>count: {count}</div>
);
}
$RefreshReg$(Test, 'Test');
return Test;
});
});
expect(container.firstChild.textContent).toBe('count: 0');
await act(async () => {
container.firstChild.click();
});
expect(container.firstChild.textContent).toBe('count: 1');
await act(async () => {
await patch(() => {
function Test2() {
const [count, setCount] = React.useState(0);
return (
<div onClick={() => setCount(c => c + 1)}>count: {count}</div>
);
}
const Test = React.memo(Test2);
$RefreshReg$(Test2, 'Test2');
$RefreshReg$(Test, 'Test');
return Test;
});
});
expect(container.firstChild.textContent).toBe('count: 0');
await act(async () => {
container.firstChild.click();
});
expect(container.firstChild.textContent).toBe('count: 1');
await act(async () => {
await patch(() => {
const Test = React.forwardRef((props, ref) => {
const [count, setCount] = React.useState(0);
const handleClick = () => setCount(c => c + 1);
// Ensure ref is extensible
const divRef = React.useRef(null);
React.useEffect(() => {
if (ref) {
if (typeof ref === 'function') {
ref(divRef.current);
} else if (Object.isExtensible(ref)) {
ref.current = divRef.current;
}
}
}, [ref]);
return (
<div ref={divRef} onClick={handleClick}>
count: {count}
</div>
);
});
$RefreshReg$(Test, 'Test');
return Test;
});
});
expect(container.firstChild.textContent).toBe('count: 0');
await act(async () => {
container.firstChild.click();
});
expect(container.firstChild.textContent).toBe('count: 1');
}
});
it('can update simple memo function in isolation', async () => {
if (__DEV__) {
await render(() => {