🍂落页
登 录

React 18 笔记

  • Fiber 架构
  • 新 Suspense 下的 SSR 框架
  • API - react
  • API - react-dom
  • 跳出机制
  • 个人对 React 的理解
  • 发布博客
🍂落页
TALAXY
API - react
🍂落页
TALAXY

API - react

API - react
  • cache
  • startTransition
  • useTransition
  • useDeferredValue
  • Suspense

cache

const cachedFn = cache(fn);

cache 会以函数实参为键,缓存首次函数调用的结果,后续的函数调用也将直接使用缓存值。

  • cache 只能在服务端组件中使用,缓存的生命周期为单次服务器请求。
  • fn 可以是异步函数。若 fn 抛出错误,该错误也会被缓存。
  • cache 会通过 Object.is 来区分函数传参。
  • cachedFn 可以跨组件使用并共享缓存。缓存访问是通过 context 实现的,所以在组件外调用 cachedFn 不会读取或更新缓存。

如果 fn 是个比较缓慢的异步函数(比如数据请求),则可以先在组件的头部位置执行 cachedFn :

const getData = cache(fetchData);

async function MyComponent() {
    getData();
    // 利用获取数据的时间做一些别的工作
    await getData();
    // ...
}

startTransition

startTransition(scope);
  • scope 应当是个同步的函数,内部可以调用多个 setState 状态更新。
  • 使用 startTransition 的状态更新会被标记为 transition 。transition 更新会被别的 state 更新打断。
  • 不要将 transition 更新用于控制文本输入等行为中。
import { startTransition } from 'react';

function TabContainer() {
    const [tab, setTab] = useState('about');

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

scope 中应当同步调用状态更新。一些错误使用:

startTransition(() => {
    setTimeout(() => {
        setPage('/about');
    }, 1000);
});
startTransition(async () => {
    await someAsyncFunction();
    setPage('/about');
});

useTransition

const [isPending, startTransition] = useTransition();
  • isPending - 是否有待处理的 transition 更新。

useDeferredValue

const deferredValue = useDeferredValue(value);

对于一些较慢的组件,可以用 useDeferredValue 优化,可以避免高频率的更新。

  • value - 想延迟的值。
  • value 更新时,React 仍会先用旧值渲染,然后在后台用新值渲染。这个后台渲染是可中断的,如果渲染期间 value 再次被更新,React 则会丢弃先前的渲染并重新渲染。
  • useDeferredValue 本身不会设立一个延迟的值,一旦 value 更新就会立即在后台渲染。在提交到屏幕之前 useDeferredValue 不会触发 effect 。
  • 性能开销较大的组件,一般也需要对其用 memo 包裹,才能得到真正的优化。

import { useState, useDeferredValue } from 'react';

function SearchPage() {
    const [query, setQuery] = useState('');
    const deferredQuery = useDeferredValue(query);
    // ...
}

当 query 更新时:

  1. 首先,React 会使用新的 query 和旧的 deferredQuery 重新渲染。
  2. 在后台,React 尝试用新的 deferredQuery 渲染。如果中途遇到了 suspense ,或者 query 有了新的更新,则会中断放弃此次渲染(但期间产生的网络请求并不会因此中断)。

Suspense

<Suspense fallback={<Loading />}>
    <SomeComponent />
</Suspense>

下文中的「挂起」指的是英文 suspense 。

  • 若 Suspense 被再次挂起,将会展示 fallback ,除非是 startTransition 或 useDeferredValue 引起的更新。
  • 为了确保测量 DOM 布局的 effect 正常工作,Suspense 在挂起时会清理 children 里的 layout effect ,再次展示时也会重新触发 layout effect 。
  • Suspense 允许嵌套,当组件挂起时会查找其最近的 Suspense 挂起。

在 children 加载数据时,Suspense 会被挂起。这里的 数据加载 仅限以下方式:

  • 在支持 Suspense 的框架(如 Relay 或 Next.js)下请求数据(需参阅相应文档);
  • 使用 lazy 加载组件代码;
  • 使用 use 读取 Promise 值。
function App() {
    const [query, setQuery] = useState('');
    return (
        <>
            <label>
                Search albums:
                <input value={query} onChange={e => setQuery(e.target.value)} />
            </label>
            <Suspense fallback={<h2>Loading...</h2>}>
                <SearchResults query={query} />
            </Suspense>
        </>
    );
}

如果想在更新时展示旧数据(而不是 fallback),可以用 useDeferredValue 或 transition :

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

在服务端中,可以在组件中主动抛出错误,并用一个 Suspense 包裹:

<Suspense fallback={<Loading />}>
    <Chat />
</Suspense>

function Chat() {
    if (typeof window === 'undefined') {
        throw Error('Chat 只能在客户端中渲染。');
    }
    // ……
}

TALAXY 落于 2024年1月17日 。