在 Fiber 出現之前:
Fiber:
偵錯:componentDidCatch 和 getDerivedStateFromError
getDerivedStateFromError
componentDidCatch
不同裝置的體驗
不同網速的體驗
useTransition
Suspense
💡「延遲」 非同步操作,等非同步操作同步後,才開始 Render。
import {unstable_createResource as createResource} from 'react-cache';
const resource = createResource(fetchDataApi);
const Foo = () => {
const result = resource.read();
return (
<div>{result}</div>
);
// ...
<Suspense>
<Foo />
</Suspense>};
const wrapPromise = promise => {
let status = "pending";
let result = "";
let suspender = promise.then(
r => {
status = "success";
result = r;
},
e => {
status = "error";
result = e;
}
);
return {
read() {
if (status === "pending") {
throw suspender;
} else if (status === "error") {
throw result;
}
return result;
}
};
};
Suspense
import React from "react";
export class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.log(error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return (
<div>
Something went wrong: {this.state.error.message}
</div>
);
}
return this.props.children;
}
}
<ErrorBoundary>
<Suspense fallback={null}>
<ComponentThatThrowError />
</Suspense>
</ErrorBoundary>
<SuspenseList tails="collapsed" revealOrder="together">
<Suspense fallback={<h1>loading num...</h1>}>
<ComponentOne />
</Suspense>
<Suspense fallback={<h1>loading person...</h1>}>
<ComponentTwo />
</Suspense>
</SuspenseList>
Normal
forwards
together
tail: collapsed
Default : Receded → Skeleton → Complete
useTransition : Pending → Skeleton → Complete
Default
useTransition
集中 管理 和 延遲 頁面 更新
(其實在後台繼續 render 但延遲 commit 到真正畫面上的時間)
function ProfilePage() {
const [
startTransition,
isPending
] = useTransition({
// Wait 10 seconds before fallback
timeoutMs: 10000
});
const [resource, setResource] = useState(
initialResource
);
function handleRefreshClick() {
startTransition(() => {
setResource(fetchProfileData());
});
}
return (
<Suspense
fallback={<h1>Loading profile...</h1>}
>
<ProfileDetails resource={resource} />
<button
onClick={handleRefreshClick}
disabled={isPending}
>
{isPending ? "Refreshing..." : "Refresh"}
</button>
<Suspense
fallback={<h1>Loading posts...</h1>}
>
<ProfileTimeline resource={resource} />
</Suspense>
</Suspense>
);
}
流程:
1. 將 Suspense 變更為 Pending 狀態
2. 為 Pending 狀態的更新都會被延遲
(此時都還在上一個畫面)
3. 拉完資料 or 超時( 進入 fallback )
4. Render 新畫面
function updateTransition(
config: SuspenseConfig | void | null,
): [(() => void) => void, boolean] {
const [isPending, setPending] = updateState(false); // = useState
const startTransition = updateCallback( // = useCallback
callback => {
setPending(true); // pending: true
// 調降執行的優先級
Scheduler.unstable_next(() => {
// 設定 suspenseConfig
const previousConfig = ReactCurrentBatchConfig.suspense;
ReactCurrentBatchConfig.suspense = config === undefined ? null : config;
try {
// 還原 pending: false
setPending(false);
// 執行 fetch OR sth else...
callback();
} finally {
// 還原 suspenseConfig
ReactCurrentBatchConfig.suspense = previousConfig;
}
});
},
[config, isPending],
);
return [startTransition, isPending];
}
相較於 useTransition 是延遲更新
useDeferredValue 則是取上一個值
function ProfilePage({ resource }) {
const deferredResource = useDeferredValue(
resource,
{
timeoutMs: 1000
}
);
return (
<Suspense
fallback={<h1>Loading profile...</h1>}
>
<ProfileDetails resource={resource} />
<Suspense
fallback={<h1>Loading posts...</h1>}
>
<ProfileTimeline
resource={deferredResource}
isStale={deferredResource !== resource}
/>
</Suspense>
</Suspense>
);
}
function useDeferredValue<T>(
value: T,
config: TimeoutConfig | void | null,
): T {
const [prevValue, setValue] = useState(value);
const [startTransition] = useTransition(config)
useEffect(
() => {
startTransition(() => {
setValue(value);
})
},
[value, config],
);
return prevValue;
}
1.用 useEffect 監聽 value 的變化
2. 在 startTransition 中更新 value