TSX 与类型化前端组件的兴起
你正在构建一个 React 组件。你在应该传递数字的地方传递了字符串。TypeScript 在浏览器之前就捕获了这个错误。这种简单的交互——类型防止运行时错误——解释了为什么 TSX 已成为现代前端开发的默认格式。
本文涵盖了类型化前端组件在实践中的实际含义:props 类型、事件处理、children 模式,以及 TypeScript 类型推断如何消除样板代码。我们还将讨论 React 19 的服务端和客户端组件分离如何影响你的类型策略。
核心要点
- TSX 类型化组件在编译时捕获类型不匹配,在代码到达浏览器之前减少运行时错误。
- TypeScript 类型推断自动处理大多数事件类型——只有在将处理程序提取到单独函数时才需要显式注解。
- 可辨识联合类型使具有互斥行为的组件类型安全,TypeScript 根据判别属性收窄类型。
- React 19 的服务端/客户端边界引入了序列化约束:从服务端组件传递到客户端组件的 props 必须可序列化(不能是函数、类、Symbol 或其他不可序列化的值)。
类型化组件的实际含义
类型化组件不仅仅是带有类型化 props 的组件。它是 TypeScript 理解整个契约的组件:输入什么、输出什么,以及中间发生什么。
使用现代 JSX 转换(react-jsx),你不需要在每个文件中导入 React。你编写 TSX,工具链处理其余部分。Vite 开箱即用地正确配置了这一点:
interface ButtonProps {
label: string
disabled?: boolean
}
function Button({ label, disabled = false }: ButtonProps) {
return <button disabled={disabled}>{label}</button>
}
注意这里没有 React.FC。这种模式是可选的——而且通常是不必要的。直接在函数参数上对 props 进行类型注解更简洁,并避免了 React.FC 添加的隐式 children prop,无论你是否需要它。
Props 类型与 React TSX 类型安全
Props 类型是大多数开发者的起点,但 React TSX 类型安全更深入。考虑使用可辨识联合类型来处理具有互斥行为的组件:
type LinkButtonProps =
| { variant: 'button'; onClick: () => void }
| { variant: 'link'; href: string }
function LinkButton(props: LinkButtonProps) {
if (props.variant === 'link') {
return <a href={props.href}>Navigate</a>
}
return <button onClick={props.onClick}>Click</button>
}
TypeScript 根据 variant 收窄类型。当 variant 是 'button' 时,你不会意外访问 href。这种模式对于复杂的组件 API 具有良好的扩展性。
无样板代码的事件类型
事件处理程序经常让 TypeScript 新手感到困惑。好消息是:类型推断自动处理大多数情况。
import { useState } from 'react'
function SearchInput() {
const [query, setQuery] = useState('')
// TypeScript 从上下文推断事件类型
return (
<input
value={query}
onChange={(e) => setQuery(e.currentTarget.value)}
/>
)
}
当你将处理程序提取到单独的函数时,需要显式类型:
import type { ChangeEvent } from 'react'
function handleChange(event: ChangeEvent<HTMLInputElement>) {
// event.currentTarget.value 被正确类型化为 string
}
Discover how at OpenReplay.com.
Children 类型:ReactNode vs ReactElement
对于 children,React.ReactNode 涵盖了常见情况——任何可渲染的内容:
import type { ReactNode } from 'react'
interface CardProps {
children: ReactNode
}
当你需要严格的 JSX 元素时使用 React.ReactElement,排除字符串和数字。对于接受 children 的组件,PropsWithChildren 是手动添加 children prop 的更简洁替代方案:
import type { PropsWithChildren } from 'react'
interface CardProps {
title: string
}
function Card({ title, children }: PropsWithChildren<CardProps>) {
return (
<div>
<h2>{title}</h2>
{children}
</div>
)
}
React 19 中的服务端和客户端组件类型
React 19 TypeScript 项目,特别是使用 Next.js App Router 的项目,将组件分为服务端和客户端边界。这会影响类型。
服务端组件可以是异步的并直接获取数据:
// 服务端组件 - 不需要指令(在 App Router 中是默认的)
async function UserProfile({ userId }: { userId: string }) {
const user = await fetchUser(userId)
return <div>{user.name}</div>
}
客户端组件需要 'use client' 指令并可以使用 hooks:
'use client'
import { useState } from 'react'
interface CounterProps {
initialCount: number
}
function Counter({ initialCount }: CounterProps) {
const [count, setCount] = useState(initialCount)
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
类型边界很重要:从服务端组件传递到客户端组件的 props 必须可序列化。函数、类、Symbol 和不可序列化的值(如类实例或 Date)无法跨越该边界。
表单操作与现代类型化工作流
React 19 引入了服务端操作(通常与表单一起使用)作为处理提交的类型化模式,通常在服务端文件中定义并从客户端组件调用:
async function submitForm(formData: FormData) {
'use server'
const email = formData.get('email')
if (typeof email !== 'string') {
throw new Error('Email is required')
}
// 处理提交
}
这通过保持从客户端到服务端的表单处理类型感知,与类型化前端组件集成。
结论
TSX 类型化组件通过在编译时捕获不匹配来减少错误。TypeScript 类型推断最小化样板代码——你很少需要为 hooks 或内联处理程序提供显式类型注解。React 19 中的服务端/客户端分离增加了新的考虑因素:序列化边界。
除非有特定原因,否则跳过 React.FC。直接对 props 进行类型注解。让类型推断发挥作用。你的编辑器会通过准确的自动完成和即时错误反馈感谢你。
常见问题
直接在函数参数上对 props 进行类型注解。React.FC 会添加隐式的 children prop,无论你是否需要它,而直接对 props 进行类型注解更简洁。React 团队不推荐将 React.FC 作为默认模式。只有在特别需要其行为时才使用它,例如在处理期望它的旧代码库时。
对于内联处理程序,TypeScript 会从上下文自动推断事件类型。当你将处理程序提取到单独的函数时,使用显式类型,如 React.ChangeEvent 用于输入变化或 React.MouseEvent 用于点击。泛型参数指定元素类型,如 HTMLInputElement 或 HTMLButtonElement。
ReactNode 接受任何可渲染的内容,包括字符串、数字、null、undefined 和 JSX 元素。ReactElement 只接受 JSX 元素,排除原始值。大多数情况下使用 ReactNode。当你的组件特别需要 JSX children 并应该拒绝纯文本或数字时,使用 ReactElement。
不可以。从服务端组件传递到客户端组件的 props 必须可序列化。函数、类、Symbol 和其他不可序列化的值无法跨越服务端-客户端边界。在客户端组件内定义事件处理程序和回调,或使用服务端操作处理需要服务端处理的表单提交。
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.