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
:父组件传递的 propsref
:父组件传递的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
拒绝原因,让最近的错误边界来处理
- 在第一次尝试渲染返回的组件之前,React 不会调用
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
进行比较
- 如果新旧 props 相等,它应该返回
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 或 contextvalue
:从资源读取的值,例如 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
时,会重新抛出相同的错误