React Reference | 阅读文档后理解 02

86 阅读4分钟

Reference

react@18.2.0

Hook

useDeferredValue

简介


Parameters

value: 你想要推迟的值,它可以是任意类型

Returns

在首次渲染中,它返回与参数一致的值。当更新时,即由于 value(可以引起重新渲染的值) 引起重新渲染时,React 首先会尝试使用这个旧值来参与这次重新渲染,接着在后台执行 value 参与的重新渲染过程,一旦这个过程结束,就会替代掉旧值参与的结果,这时返回值就是新值了。

<Suspense> 打配合


请查看 <Suspense> 部分

使用 useDeferredValue 优化一部分 UI


使用 deferredValue 还可以优化一些场景。假如一个组件渲染时很慢 ,当 value 更新时引起重新渲染,这个有使用 value ,所以它也会被重新渲染,这样其他部分就会等待它渲染结束,从而产生卡顿。比如 value 就是输入框的值,那么每输入一次,value 就会更新,然而想要等待慢组件渲染,所以输入一次就会卡一次。

但是当慢组件使用 deferredValue 时,就相当于告诉 React 没必要让 value 引发的重新渲染与慢组件的重新渲染一起执行,就是说其他部分重新渲染时涉及到 value 的地方是新值,而慢组件是旧值,慢组件的使用新值的重新渲染在后台慢慢执行,当它完成时就拿上来。那其他部分就不会等待慢组件使用新值了,输入也就不卡了。

Components

<Suspense>

props

children 你真正想要渲染的 UI,如 children 在渲染的过程中出现了延迟,则 Suspense 的范围内将会展示 fallback 的内容

fallback 当 children 发送延迟时就会渲染 fallback 的内容,fallback 的内容通常是轻量的。放 children 加载完成时就会切换回 children。

✨ ``组件是用于 React 中的异步渲染,包括:
  • Ralay Next.js 这种可以激活 Suspense 的框架的数据请求
  • 使用 lazy 懒加载的组件

一般的延迟,比如 fetch API 的延迟,解决方案就是使用 useState 控制 jsx

嵌套使用 Suspense


Suspense 会等待 children 内容全部加载完毕

那如果 component A 和 component B 都需要加载,并且已知 A 大概率快于 B,需求是 A 加载好了就先渲染,怎么办?

让 A 在 B 的上层即可,这样 Biography 加载好了就先渲染并且展示 AlbumsGlimmer

export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<BigSpinner />}>
        <Biography artistId={artist.id} />
        <Suspense fallback={<AlbumsGlimmer />}>
          <Panel>
            <Albums artistId={artist.id} />
          </Panel>
        </Suspense>
      </Suspense>
    </>
  );
}

等待加载时展示旧内容 | 与 useDeferredValue 打配合


等待加载时给予用户响应,一种方案是展示 Loading,而另一种方案是继续展示旧内容,但是样式有些许变化,比如透明度降低

import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <div style={{ opacity: isStale ? 0.5 : 1 }}>
          <SearchResults query={deferredQuery} />
        </div>
      </Suspense>
    </>
  );
}

query 更新时,引起重新渲染,先是使用旧值的重新渲染,isStale 为 true,而使用新值的渲染在后台执行,执行完后替换掉旧值渲染的结果。旧值的渲染结果替换了 Suspense 的 fallback。

防止隐藏已经显示的内容 | 与 startTransition useTransition 打配合


简述下面的代码,<Suspense fallback=Loading><A/><B/></Suspense> ,因为B组件中的state更新所以B组件需要重新渲染,这会触发 Suspense,从而 loading() 替换 <A/><B/> ,这就造成了把已经显示的内容隐藏了,就A组件被替换掉了。

  • 代码

    // App.js
    import { Suspense, useState } 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('/');
    ​
      function navigate(url) {
        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>
          {content}
        </Layout>
      );
    }
    ​
    function BigSpinner() {
      return <h2>🌀 Loading...</h2>;
    }
    ​
    // Layout.js
    export default function Layout({ children }) {
      return (
        <div className="layout">
          <section className="header">
            Music Browser
          </section>
          <main>
            {children}
          </main>
        </div>
      );
    }
    ​
    // IndexPage.js
    export default function IndexPage({ navigate }) {
      return (
        <button onClick={() => navigate('/the-beatles')}>
          Open The Beatles artist page
        </button>
      );
    }
    ​
    // ArtistPage.js
    export default function IndexPage({ navigate }) {
      return (
        <button onClick={() => navigate('/the-beatles')}>
          Open The Beatles artist page
        </button>
      );
    }
    

因为 startTransition API 能使 state 正常更新且不阻塞 UI,所以只需要用 startTransition 包裹住 state 的更新即可,这样 Suspense 就不会检测到B组件在加载了,因为B组件的 state 更新进入了过渡状态

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

那我们如何得知 state 更新是否在过渡状态呢?从而做出相应的响应样式。

useTransition Hook 返回一个数组,数组第一个值 isPending 标志可以告诉我们 state 更新是否在过渡状态