useTransition

更新时间:2023-02-22 15:57:53标签:reactweb前端

示例一

代码

1import { FC, memo, ReactNode, useRef, useState, useTransition } from 'react';
2import { Button } from 'antd';
3
4interface SlowItemProps {
5 children?: ReactNode;
6}
7const SlowItem: FC<SlowItemProps> = (props) => {
8
9 const { children } = props;
10 const now = useRef(Date.now());
11
12 while (Date.now() - now.current < 10) {
13 // 强行阻塞代码10毫秒
14 }
15
16 return (
17 <span>{ children }</span>
18 );
19};
20
21const SlowList = memo(() => {
22 return (
23 <>
24 {
25 // 循环200次就是阻塞2秒钟
26 new Array(200).fill(1).map((v, k) => {
27 return (
28 <SlowItem key={k}>{ k }</SlowItem>
29 );
30 })
31 }
32 </>
33 );
34});
35SlowList.displayName = 'SlowList';
36
37const UseTransitionList: FC = () => {
38
39 const [show, setShow] = useState(false);
40 const [isPending, startTransition] = useTransition();
41
42 return (
43 <div>
44 <div>
45 <Button onClick={() => setShow(true)}>展示列表(同步渲染)</Button>
46 <span>同步的按钮点击后,rerender过程同步执行,所以浏览器会卡死5秒钟</span>
47 </div>
48 <div>
49 <Button
50 onClick={() => {
51 startTransition(() => setShow(true));
52 }}
53 >展示列表(startTransition)</Button>
54 <span>{ `${isPending}` }</span>
55 <Button onClick={() => setShow(false)}>隐藏列表</Button>
56 <span>使用startTransition,rerender过程异步执行,可以随时中断rerender过程</span>
57 </div>
58 {
59 show ?
60 <SlowList /> :
61 null
62 }
63 </div>
64 );
65};
66
67export { UseTransitionList };

效果

同步的按钮点击后,rerender过程同步执行,所以浏览器会卡死5秒钟
false使用startTransition,rerender过程异步执行,可以随时中断rerender过程

示例二(与Suspense配合食用)

代码

1import { FC, lazy, Suspense, useState, useTransition } from 'react';
2import { sleep } from '@shared/utils';
3import { Button, Space } from 'antd';
4
5// 将lazy组件再延迟2000毫秒以便更好的模拟网络延迟
6const LazyExample = lazy(() => import('./lazy-example').then(res => sleep(2000).then(() => res)));
7
8const SuspenseExample: FC = () => {
9
10 const [show, setShow] = useState(false);
11 const [isPending, startTransition] = useTransition();
12
13 return (
14 <>
15 <div>
16 <Space>
17 <Button onClick={() => setShow(true)}>显示</Button>
18 <Button onClick={() => setShow(false)}>隐藏</Button>
19 <Button
20 onClick={() => {
21 startTransition(() => setShow(true));
22 }}
23 >显示(startTransition)</Button>
24 <span>{`${isPending}`}</span>
25 </Space>
26 </div>
27 <hr />
28 <Suspense fallback={<span>loading...</span>}>
29 {
30 show ?
31 <LazyExample /> :
32 <span>初始状态</span>
33 }
34 </Suspense>
35 </>
36 );
37};
38
39export { SuspenseExample };

效果

false

初始状态

可以看到,使用startTransition时,Suspense的fallback没有生效,在异步组件加载完成之前,页面UI始终没有变化

应用场景

懒加载路由进度条


切换路由时注意浏览器上方会有进度条显示

代码(模拟异步加载的路由场景)

1import { ComponentType, FC, lazy, LazyExoticComponent, Suspense, useCallback, useState, useTransition } from 'react';
2import { Button } from 'antd';
3import { sleep, NProgress } from '@shared/utils';
4
5const AsyncLoad = <T extends ComponentType>(factory: () => Promise<{default: T}>) => {
6 const C = lazy(() => {
7 const load = factory().then(res => sleep(2000).then(() => res));
8 NProgress.start();
9 load.finally(() => {
10 NProgress.done();
11 });
12 return load;
13 }) as LazyExoticComponent<any>;
14 return (
15 <C />
16 );
17};
18
19const Router1 = AsyncLoad(() => import('./router-1'));
20const Router2 = AsyncLoad(() => import('./router-2'));
21const Router3 = AsyncLoad(() => import('./router-3'));
22
23const Routes: FC<{ pathname: string }> = (props) => {
24 if (props.pathname === '/page-1') {
25 return Router1;
26 }
27 if (props.pathname === '/page-2') {
28 return Router2;
29 }
30 if (props.pathname === '/page-3') {
31 return Router3;
32 }
33 return <span>切换路由时注意浏览器上方会有进度条显示</span>;
34};
35
36const LazyProgress: FC = () => {
37 const [pathname, setPathname] = useState('/');
38
39 const [, startTransition] = useTransition();
40 const navigate = useCallback((url: string) => {
41 startTransition(() => {
42 setPathname(url);
43 });
44 }, []);
45
46 return (
47 <div>
48 <div>
49 <Button onClick={() => navigate('/page-1')} type={pathname === '/page-1' ? 'primary' : 'text'}>页面1</Button>
50 <Button onClick={() => navigate('/page-2')} type={pathname === '/page-2' ? 'primary' : 'text'}>页面2</Button>
51 <Button onClick={() => navigate('/page-3')} type={pathname === '/page-3' ? 'primary' : 'text'}>页面3</Button>
52 </div>
53 <hr/>
54 <Suspense fallback={<span>loading...</span>}>
55 <Routes pathname={pathname} />
56 </Suspense>
57 </div>
58 );
59
60};
61
62export { LazyProgress };