React 18
Concurrent Rendering(并发渲染)
1.渲染是可中断的
Suspense
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
SuspenseList
revealOrder: Suspense加载顺序
1.together: 所有Suspense一起显示,也就是最后一个加载完了才一起显示全部
2.forwards: 按照顺序显示Suspense
3.backwards: 反序显示Suspense
tail: 是否显示fallback,只在revealOrder为forwards或者backwards时候有效
1.hidden: 不显示
2.collapsed: 轮到自己再显示
import {useState, Suspense, SuspenseList} from "react"
import User from "../components/User"
import Num from "../components/Num"
import {fetchData} from "../utils"
import ErrorBoundaryPage from "./ErrorBoundaryPage"
const initialResource = fetchData();
export default function SuspenseListPage(props) {
const [resource, setResource] = useState(initialResource);
return (
<div>
<h3>SuspenseListPage</h3>
<SuspenseList tail="collapsed">
<ErrorBoundaryPage fallback={<h1>网络出错了</h1>}>
<Suspense fallback={<h1>loading - user</h1>}>
<User resource={resource} />
</Suspense>
</ErrorBoundaryPage>
<Suspense fallback={<h1>loading-num</h1>}>
<Num resource={resource} />
</Suspense>
</SuspenseList>
<button onClick={() => setResource(fetchData())}>refresh</button>
</div>
)
}
Automatic Batching(自动批量处理)
// 以前: 这里的两次setState并没有批量处理,React会render两次
setTimeout(() => {
setCount(c => c + 1)
setFlag(f => !f)
}, 1000)
// React18: 自动批量处理,这里只会render一次
setTimeout(() => {
setCount(c => c + 1)
setFlag(f => !f)
}, 1000)
Transition(过渡)
紧急更新(Urgent updates): 跟用户交互的更新属于紧急更新
过渡更新(Transition updates): 跟界面呈现相关的更新属于过渡更新
startTransition
import {useEffect, useState, Suspense} from "react";
import Button from "../components/Button";
import User from "../components/User";
import Num from "../components/Num";
import {fetchData} from "../utils";
const initialResource = fetchData();
export default function TransitionPage(props) {
const [resource, setResource] = useState(initialResource);
// useEffect(() => {
// console.log("resource", resource); //sy-log
// }, [resource]);
return (
<div>
<h3>TransitionPage</h3>
<Suspense fallback={<h1>loading - user</h1>}>
<User resource={resource} />
</Suspense>
<Suspense fallback={<h1>loading-num</h1>}>
<Num resource={resource} />
</Suspense>
<Button
refresh={() => {
setResource(fetchData());
}}
/>
</div>
);
}
import {
//startTransition,
useTransition,
} from "react";
export default function Button({refresh}) {
const [isPending, startTransition] = useTransition();
return (
<div className="border">
<h3>Button</h3>
<button
onClick={() => {
startTransition(() => {
refresh();
});
}}
disabled={isPending}>
点击刷新数据
</button>
{isPending ? <div>loading...</div> : null}
</div>
);
}
useTransition
const [isPending, startTransition] = useTransition();
function handleClick() {
startTransition(() => {
setTab('comments');
});
}
<Suspense fallback={<Spinner />}>
<div style={{ opacity: isPending ? 0.8 : 1 }}>
{tab === 'photos' ? <Photos /> : <Comments />}
</div>
</Suspense>
useDeferredValue
import {useDeferredValue, useState} from "react";
import MySlowList from "../components/MySlowList";
export default function UseDeferredValuePage(props) {
const [text, setText] = useState("hello");
const deferredText = useDeferredValue(text);
const handleChange = (e) => {
setText(e.target.value);
};
return (
<div>
<h3>UseDeferredValuePage</h3>
{/* 保持将当前文本传递给 input */}
<input value={text} onChange={handleChange} />
{/* 但在必要时可以将列表“延后” */}
<p>{deferredText}</p>
<MySlowList text={deferredText} />
</div>
);
}
useId
useSyncExternalStore
useInsertionEffect
原理
const HooksDispatcherOnMount = {
useCallback: mountCallback,
useEffect: mountEffect,
useLayoutEffect: mountLayoutEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
}
const HooksDispatcherOnUpdate = {
useCallback: updateCallback,
useEffect: updateEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState
}