你不知道的React系列(二十二)useTransition

147 阅读2分钟

本文正在参加「金石计划」

const [isPending, startTransition] = useTransition();

更新 state 界面不会出现卡顿

标记整个更新,通常框架和路由使用

  • isPending

    是否有正在加载的 transition

  • startTransition

    • 标记 state 更新函数为一个 transition 函数

    • transition 函数参数 scope

      • scope 函数没有参数,调用时同步标记 state 更新逻辑为 transition

      • 内部逻辑只能是 state 更新逻辑

        如果使用 props 或者其他 自定义 Hook,使用 useDeferredValue

      • 必须是同步更新 state,不能使用 timeout

    • state 更新标记会被其他 state 更新打断

    • 不能用作控制文本输入

    • 批量 transitions

state 更新成为 transition

import { useState, useTransition } from 'react';
import TabButton from './TabButton.js';
import AboutTab from './AboutTab.js';
import PostsTab from './PostsTab.js';
import ContactTab from './ContactTab.js';

export default function TabContainer() {
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState('about');

  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab);      
    });
  }

  return (
    <>
      <TabButton
        isActive={tab === 'about'}
        onClick={() => selectTab('about')}
      >
        About
      </TabButton>
      <TabButton
        isActive={tab === 'posts'}
        onClick={() => selectTab('posts')}
      >
        Posts (slow)
      </TabButton>
      <TabButton
        isActive={tab === 'contact'}
        onClick={() => selectTab('contact')}
      >
        Contact
      </TabButton>
      <hr />
      {tab === 'about' && <AboutTab />}
      {tab === 'posts' && <PostsTab />}
      {tab === 'contact' && <ContactTab />}
    </>
  );
}

父组件更新成为 transition

父组件更新 state 逻辑传递给子组件,子组件内部 state 更新逻辑变为 transition

export default function TabButton({ children, isActive, onClick }) {
  const [isPending, startTransition] = useTransition();
  if (isActive) {
    return <b>{children}</b>
  }
  return (
    <button onClick={() => {
      startTransition(() => {
        onClick();
      });
    }}>
      {children}
    </button>
  );
}

transition 期间展示效果

import { useTransition } from 'react';

export default function TabButton({ children, isActive, onClick }) {
  const [isPending, startTransition] = useTransition();
  if (isActive) {
    return <b>{children}</b>
  }
  if (isPending) {
    return <b className="pending">{children}</b>;
  }
  return (
    <button onClick={() => {
      startTransition(() => {
        onClick();
      });
    }}>
      {children}
    </button>
  );
}

屏蔽不想要的 loading 状态

Suspense fallback

Suspense 不能嵌套

function App() {
  const [isPending, startTransition] = useTransition();
  const [count, setCount] = useState(0);

  function handleClick() {
    startTransition(() => {
      setCount(c => c + 1);
    })
  }

  return (
    <div>
      {isPending && <Spinner />}
      <button onClick={handleClick}>{count}</button>
    </div>
  );
}

路由切换效果

路由切换逻辑成为一个 transition

import { Suspense, useState, useTransition } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';

export default function App() {
  return (
    <Suspense fallback={<BigSpinner />}>
      <Router />
    </Suspense>
  );
}

function Router() {
  const [page, setPage] = useState('/');
  const [isPending, startTransition] = useTransition();

  function navigate(url) {
    startTransition(() => {
      setPage(url);
    });
  }

  let content;
  if (page === '/') {
    content = (
      <IndexPage navigate={navigate} />
    );
  } else if (page === '/the-beatles') {
    content = (
      <ArtistPage
        artist={{
          id: 'the-beatles',
          name: 'The Beatles',
        }}
      />
    );
  }
  return (
    <Layout isPending={isPending}>
      {content}
    </Layout>
  );
}

function BigSpinner() {
  return <h2>🌀 Loading...</h2>;
}

问答

  • transition 更新 input 的数据不生效

    使用 useDeferredValue

  • state 更新没有被当成 transition

    scope 函数是异步的

  • 组件外边调用 useTransition

    使用 startTransition

  • startTransition scope 函数立即执行

    函数立即执行,标记 transitions 逻辑是在计划

startTransition

import { startTransition } from 'react';
startTransition(scope)
  • state 作为一个 transition 更新

    import { startTransition } from 'react';
    
    function TabContainer() {
      const [tab, setTab] = useState('about');
    
      function selectTab(nextTab) {
        startTransition(() => {
          setTab(nextTab);
        });
      }
      // ...
    }
    
  • startTransition 不能监测是否是 pending 状态

  • 可以在组件外部运行