createContext

组件外部调用 createContext 以创建上下文,与 useContext 一起使用

const SomeContext = createContext(defaultValue)
  • defaultValue:默认值,当读取上下文的组件上方的树中没有匹配的上下文提供者时的后备
    • 如果没有任何有意义的默认值,请指定 null
    • 它是静态的,不会随着时间的推移而改变
  • SomeContext:上下文对象,本身不保存任何信息,它代表其他组件读取或提供的上下文
    • SomeContext.Provider:向组件提供上下文值
    • SomeContext.Consumer:读取上下文值(已过时,使用 useContext 代替)

SomeContext.Provider

function App() {
  const [theme, setTheme] = useState('light');
 
  return (
    <ThemeContext.Provider value={theme}>
      <Page />
    </ThemeContext.Provider>
  );
}

其中 value 是想要传递给子组件读取此上下文的值,无论有多深,可以是任何类型

SomeContext.Consumer

// 读取上下文值(已过时)
function Button() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <button className={theme} />
      )}
    </ThemeContext.Consumer>
  );
}
 
// 推荐使用 useContext Hook 读取上下文
function Button() {
  const theme = useContext(ThemeContext);
  return <button className={theme} />;
}

其中 children 是一个函数,React 将调用该函数并把最近的上层 Provider 的上下文值作为参数,并渲染从此函数返回的结果。每当父组件的上下文发生变化时,React 也会重新运行此函数并更新 UI

forwardRef

将组件包装forwardRef 中以转发 Ref,使组件通过 Ref 向父组件公开 DOM 节点或自定义命令式句柄(通过 useImperativeHandle

const SomeComponent = forwardRef(render)
  • render:组件的渲染函数(一般为 Function Component)。 React 使用该组件从其父组件接收到的 props 和 ref 调用此函数,此函数返回的 JSX 将是组件的输出
  • SomeComponent:返回一个可以在 JSX 中渲染的 React 组件。与定义为普通函数的 React 组件不同,forwardRef 返回的组件能够接收 ref 属性
const MyInput = forwardRef(function MyInput(props, ref) {
  return (
    <label>
      {props.label}
      <input ref={ref} />
    </label>
  );
});
 
function Form() {
  const ref = useRef(null);
 
  function handleClick() {
    ref.current.focus();
  }
 
  return (
    <form>
      <MyInput label="Enter your name:" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}
  • props:父组件传递的 props
  • ref:父组件传递的 ref 属性,可以是对象或函数(对象:修改 current 属性;函数:调用并以参数传入)。如果父组件未传递 ref,则将为 null

lazy

组件外部调用 lazy 来声明延迟加载的 React 组件

const SomeComponent = lazy(load)
  • load:返回 Promise 的函数
    • 在第一次尝试渲染返回的组件之前,React 不会调用 load
    • React 第一次调用 load 后,会等待它解析,然后将解析值的 .default 渲染为 React 组件
    • 返回的 Promise 和 Promise 的解析值都会被缓存,因此 React 不会多次调用 load
    • 如果 Promise 被拒绝,React 会 throw 拒绝原因,让最近的错误边界来处理
  • SomeComponent:一个可以在树中渲染的 React 组件。当惰性组件的代码仍在加载时,尝试渲染它将会挂起(使用 [[002.前端/008.React/001.官方文档/001.React/002.Components#suspense|]] 在加载时显示加载指示器)

示例:

import { lazy } from 'react';
 
const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));

memo

将组件包装memo 中以获取该组件的记忆版本

  • 只要组件的 props 没有改变,当它的父组件重新渲染时,组件的这个记忆版本通常不会被重新渲染
  • 但 React 可能仍然会重新渲染它:memoization 是一种性能优化,而不是保证
  • 使用上下文时,上下文值变化也会重新渲染
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)
  • SomeComponent:要记忆的组件
    • memo 不会修改此组件,而是返回一个新的已记忆的组件
    • 任何有效的 React 组件,包括函数和 forwardRef 组件,都被接受
  • arePropsEqual:一个接受两个参数(oldProps 和 newProps)并返回 Boolean 的函数,用于判断记忆是否失效
    • 如果新旧 props 相等,它应该返回 true。即:如果组件渲染将有相同的输出,并且使用新 props 的行为方式与旧 props 相同。否则它应该返回 false
    • 不传该函数时,React 会将每组 prop 使用 Object.is 进行比较
  • MemoizedComponent:一个新的 React 组件,它的行为与提供给 memo 的组件相同,只是加了记忆化

尽量减少 props 变化:

  • 最小化 props 更改的更好方法是确保组件在其 props 中接受最少的必要信息。例如,它可以接受单个值而不是整个对象
  • prop 是对象:可以在组件外部声明它以使其永远不会更改,或使用 useMemo 防止父组件每次渲染都创建新对象
  • prop 是函数:可以在组件外部声明它以使其永远不会更改,或使用 useCallback 防止父组件每次渲染都创建新函数

注意:

  • 不要过度“优化”:子组件重新渲染的代价和比较新旧 props 的代价,孰轻孰重?

startTransition

将状态更新标记为非紧急,类似于 useTransition

startTransition(scope)
  • scope:通过调用一个或多个 set 函数来更新某些状态的函数,React 立即调用不带参数的 scope ,并将 scope 函数调用期间同步计划的所有状态更新标记为转换(非阻塞 UI、不会 [[002.前端/008.React/001.官方文档/001.React/002.Components#suspense|]] fallback)

示例:

import { startTransition } from 'react';
 
function TabContainer() {
  const [tab, setTab] = useState('about');
 
  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab);
    });
  }
  // ...
}

注意:

  • startTransition 不提供跟踪 Transition 是否处于挂起状态的方法。要在转换正在进行时显示待处理指示器,使用 useTransition
  • 仅当有权访问该状态的 set 函数时,才可以将更新包装到 Transition 中。如果想启动 Transition 来响应某些 prop 或自定义 Hook 返回值,使用 useDeferredValue
  • 传递给 startTransition 函数必须是同步的。 React 立即执行此函数,将其执行时发生的所有状态更新标记为转换。如果是异步的(例如,在定时器),它们将不会被标记为转换
  • 标记为 Transition 的状态更新将被其他状态更新中断
  • 转换更新不能用于控制文本输入
  • 如果有多个正在进行的转换,React 目前会将它们批处理在一起。这是一个限制,可能会在未来的版本中删除

act

是一个测试助手,用于在做出断言之前应用待处理的 React 更新,即:确保在做出断言之前已处理更新

await act(async actFn)
  • async actFn:包装正在测试的组件的渲染或交互的异步函数。actFn 内触发的任何更新都会添加到内部 act 队列中,然后一起刷新以处理任何更改并将其应用到 DOM。由于它是异步的,React 还将运行任何跨越异步边界的代码,并刷新任何计划的更新

举例:

it ('renders with button disabled', async () => {
  await act(async () => {
    root.render(<TestComponent />)
  });
  expect(container.querySelector('button')).toBeDisabled();
});

注意:

  • 使用 act 需要在测试环境中设置 global.IS_REACT_ACT_ENVIRONMENT=true。这是为了确保该 act 仅在正确的环境中使用
  • 直接使用 act() 有点过于冗长,为了避免一些样板文件,可以使用像 React Testing Library 这样的库

use(实验特性)

组件中调用 use 读取 Promise 或 context 等资源的值

const value = use(resource)
  • resource:要从中读取值的数据源,可以是 Promise 或 context
  • value:从资源读取的值,例如 Promise 或 context 的解析值

注意:

  • 与 React Hooks 不同,use 可以在循环和条件语句内调用。与 React Hooks 一样,调用 use 的函数必须是 Component 或 Hook
  • 当使用 Promise 调用时,use 会与 Suspense错误边界集成
    • 挂起时:显示 Suspense 的后备
    • 拒绝时:显示最近的错误边界的回退
  • 当资源是 context 时,use 的工作方式与 useContext 类似,但其可以在循环和条件语句内调用
  • 服务器组件中获取数据时,优先选择 async/await 而不是 use。因为 async/await 从调用 await 的地方开始渲染,而 use 在解析数据后重新渲染组件
  • 与在客户端组件中创建 Promise 相比,更喜欢在服务器组件中创建 Promise 并将它们传递给客户端组件。因为在客户端组件中创建的 Promise 会在每次渲染时重新创建,从服务器组件传递到客户端组件的 Promise 在重新渲染过程中是稳定的

cache(实验特性)

组件外部调用 cache 以创建具有缓存的函数版本,来缓存数据获取或计算的结果

const cachedFn = cache(fn)
  • fn :要缓存结果的函数。 fn 可以接受任何参数并返回任何值
  • cachedFn:有相同类型签名的 fn 的缓存版本
    • 当使用给定参数调用 cachedFn 时,它首先检查缓存中是否存在缓存结果。如果存在缓存结果,则返回结果。如果没有,则使用参数调用 fn ,将结果存储在缓存中,然后返回结果。唯一一次调用 fn 是当缓存未命中时

举例:

import {cache} from 'react';
import calculateMetrics from 'lib/metrics';
 
const getMetrics = cache(calculateMetrics);
 
function Chart({data}) {
  const report = getMetrics(data);
  // ...
}

当第一次使用 data 调用 getMetrics 时,getMetrics 将调用 calculateMetrics(data) 并将结果存储在缓存中。如果使用相同的 data 再次调用 getMetrics,它将返回缓存的结果,而不是再次调用 calculateMetrics(data)

注意:

  • 仅用于服务器组件
  • 不同的 server 请求不共享记忆化缓存
  • 每次调用 cache 都会创建一个新函数。这意味着多次使用同一函数调用 cache 将返回不共享同一缓存的不同记忆函数
  • cachedFn 也会缓存错误。如果 fn 对某些参数抛出错误,它将被缓存,并且当使用这些相同的参数调用 cachedFn 时,会重新抛出相同的错误