首页 / TypeScript 入门教程 / JSX 类型支持

TypeScript 入门教程

JSX 类型支持

本教程共 80 篇 · 第 75 篇 · 更新于 2026-08-10 · 约 14 分钟阅读

TypeScriptTypeScript 入门教程JSXReactJSX.IntrinsicElementstsx

本节目标:搞懂 TypeScript 怎么给 JSX 提供类型检查——四种 JSX 模式的区别、如何标注原生 HTML 元素的类型、怎样给自定义组件写 Props 类型。不深入框架内部,只聚焦 TS 层面的类型标注。

JSX 不是 JS 的一部分

JSX 语法(<div>hello</div>)不是 ECMAScript 标准,它是编译器的扩展语法。TypeScript 支持 JSX,但需要你告诉它”编译成什么”——这个选择由 tsconfig.json 里的 jsx 选项控制。

四种 JSX 模式

TS 7.0 保留了四种 JSX 模式(移除了旧模式,只留下现代选项):

模式jsx输出行为适用场景
preserve"preserve"保留 JSX 不变,输出 .jsx交给 Babel 等后续工具处理
react-jsx"react-jsx"自动导入 jsx 函数,不引入 ReactReact 17+ 的新 JSX 转换
react-jsxdev"react-jsxdev"同 react-jsx,附加调试信息React 17+ 开发模式
react"react"转换为 React.createElement 调用React 16 及更早版本(传统转换)

TS 7.0 移除了 react-native 模式。React Native 项目现在统一用 react-jsx

// tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx"
  }
}
Note

jsx 选项只控制编译输出格式,类型检查的行为和模式无关——无论选哪个,TS 的类型检查逻辑是一样的。

固有元素:JSX.IntrinsicElements

TypeScript 怎么知道你写的 <div> 有哪些合法属性?答案是一个特殊的全局接口:JSX.IntrinsicElements

// JSX 命名空间中的接口(框架的 .d.ts 文件里会声明)
declare namespace JSX {
  interface IntrinsicElements {
    div: React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLDivElement>,
      HTMLDivElement
    >;
    span: React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLSpanElement>,
      HTMLSpanElement
    >;
    a: React.DetailedHTMLProps<
      React.AnchorHTMLAttributes<HTMLAnchorElement>,
      HTMLAnchorElement
    >;
    // ... 所有 HTML 元素
  }
}

这个接口里的每一条,都定义了对应 HTML 标签的属性类型。你写 <a href="/"> 时,TS 通过 JSX.IntrinsicElements["a"] 找到属性定义,然后检查你传的 href 对不对。

属性类型检查示例

// ✅ href 和 onClick 都在 a 元素的类型定义中
<a href="/home" onClick={(e) => console.log(e)}>
  首页
</a>

// ❌ href 的值应该是 string,不是 number
<a href={42}>首页</a>
// TS 报错:Type 'number' is not assignable to type 'string'

// ❌ foo 不是 a 元素的合法属性
<a foo="bar">首页</a>
// TS 报错:Property 'foo' does not exist on type '...'

这就是 JSX 类型检查的核心价值:写标签时就能发现属性传错的 bug。

自定义组件:Props 类型

自定义组件的类型检查,本质上就是函数参数的类型检查。JSX 的 <MyComponent prop1={...} /> 会被编译器理解成函数调用,所有属性打包成一个对象传给函数。

React 组件(函数式)

interface ButtonProps {
  label: string;
  disabled?: boolean;
  onClick: () => void;
}

function Button({ label, disabled = false, onClick }: ButtonProps) {
  return (
    <button disabled={disabled} onClick={onClick}>
      {label}
    </button>
  );
}

// 使用
<Button label="提交" onClick={() => console.log("click")} />
<Button label="取消" disabled onClick={() => {}} />

// ❌ label 必填但没传
<Button onClick={() => {}} />
// TS 报错:Property 'label' is missing

// ❌ disabled 应该是 boolean
<Button label="确认" disabled="yes" onClick={() => {}} />
// TS 报错:Type 'string' is not assignable to type 'boolean'

React 组件(类组件)

interface CounterProps {
  initial: number;
  step?: number;
}

interface CounterState {
  count: number;
}

class Counter extends React.Component<CounterProps, CounterState> {
  state: CounterState = {
    count: this.props.initial,
  };

  render() {
    const { step = 1 } = this.props;
    return (
      <div>
        <span>{this.state.count}</span>
        <button onClick={() => this.setState({ count: this.state.count + step })}>
          +
        </button>
      </div>
    );
  }
}

<Counter initial={0} step={5} />;

React.Component<P, S> 的第一个泛型是 Props 类型,第二个是 State 类型。TS 会检查 <Counter> 标签上有没有传对 Props。

Vue 3 组件(defineComponent + defineProps)

Vue 3 有自己的一套类型标注方式,但原理一样:

<script setup lang="ts">
interface Props {
  message: string;
  count?: number;
}

const props = defineProps<Props>();
// props.message: string
// props.count: number | undefined
</script>

<template>
  <div>{{ message }} - {{ count }}</div>
</template>

Vue 的 defineProps<Props>() 在编译期把泛型参数翻译成运行时的 props 声明,同时保留类型信息给 IDE。

SolidJS 组件

Solid 的函数组件 Props 类型标注和 React 几乎一样:

interface TodoItemProps {
  text: string;
  completed: boolean;
  onToggle: () => void;
}

function TodoItem(props: TodoItemProps) {
  return (
    <div class={props.completed ? "done" : ""}>
      <input type="checkbox" checked={props.completed} onChange={props.onToggle} />
      <span>{props.text}</span>
    </div>
  );
}

框架层面写法略有不同,但 TS 对它们的类型检查逻辑一致:把 JSX 的属性看作一个对象字面量,用组件函数的参数类型去校验

children 的类型

children 是所有 JSX 框架都无法绕开的类型问题。在 React 中:

interface CardProps {
  title: string;
  children?: React.ReactNode; // React 18+
}

function Card({ title, children }: CardProps) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div>{children}</div>
    </div>
  );
}

<Card title="通知">
  <p>你有一条新消息</p>
  <button>查看</button>
</Card>

React.ReactNode 是 React 里最宽泛的 children 类型——它接受 JSX 元素、字符串、数字、null、undefined、数组等任何可以作为子节点的内容。

如果你只想接受特定类型:

interface ListProps {
  items: string[];
  renderItem?: (item: string) => React.ReactNode;
}

function List({ items, renderItem }: ListProps) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item}>{renderItem ? renderItem(item) : item}</li>
      ))}
    </ul>
  );
}

事件处理器的类型

React 为每种原生事件提供了对应的类型:

interface InputProps {
  value: string;
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
}

function Input({ value, onChange, onKeyDown }: InputProps) {
  return <input value={value} onChange={onChange} onKeyDown={onKeyDown} />;
}

常用的事件类型:

事件类型适用场景
React.ChangeEvent<T>表单输入变化
React.MouseEvent<T>鼠标点击/移动
React.KeyboardEvent<T>键盘事件
React.FormEvent<T>表单提交
React.FocusEvent<T>聚焦/失焦

泛型参数 T 是 HTML 元素类型,比如 HTMLInputElementHTMLButtonElementHTMLDivElement。写对了类型,IDE 里 .currentTarget.value 等属性就有代码补全。

在 Vue 中使用 TSX

Vue 3 也支持 TSX 写法(需要 @vitejs/plugin-vue-jsx@vue/babel-plugin-jsx):

import { defineComponent } from "vue";

export default defineComponent({
  props: {
    message: { type: String, required: true },
  },
  setup(props) {
    return () => <div>{props.message}</div>;
  },
});

Vue 的 TSX 类型底层依赖 JSX.IntrinsicElements,和 React 共享同一套 TS 基础设施。区别在于 Vue 的 IntrinsicElements 里定义的是 Vue 风格的属性(比如 v-model 等指令的类型)。

框架适配差异:一张表

特性ReactVue 3Solid
Props 标注interface Props { ... }defineProps<Props>()props: Props 参数
children 类型React.ReactNodeslots 机制JSX.Element
事件类型React.ChangeEvent<T>emit 声明onChange: (e: Event) => void
类组件泛型React.Component<P, S>无(Vue3 推荐组合式)
JSX 模式react-jsxpreserve / react-jsxpreserve / react-jsx
类型定义来源@types/react编译期内置编译期内置
Note

不管你用哪个框架,TS 对 JSX 的类型检查机制是一样的:检查标签名是否存在于 JSX.IntrinsicElements 或组件函数中,检查属性是否匹配参数类型。不同框架只是在具体的类型定义上有差异。

小结

  • jsx 选项控制编译输出格式,不影响类型检查行为
  • JSX.IntrinsicElements 定义了所有原生 HTML 标签的属性类型
  • 自定义组件的 Props 类型就是组件的参数类型——TS 用函数参数校验去检查 JSX 属性
  • children 的类型在不同框架中不同:React 用 React.ReactNode,Vue 用 slots
  • 事件类型在 React 中有完整的泛型体系:React.ChangeEvent<T>React.MouseEvent<T>
  • Vue 3 和 Solid 的 JSX 底层同样依赖 JSX.IntrinsicElements,只是具体类型定义不同