TypeScript/JSX 简介及入门教程

目标:从零开始掌握 TypeScript 的类型系统,并学会在 React 中编写类型安全的 JSX 代码。


目录

  1. [TypeScript 简介](#TypeScript 简介)
  2. 基础类型系统
  3. 接口与类型别名
  4. 函数与泛型
  5. [JSX 是什么](#JSX 是什么)
  6. [TSX:TypeScript 中的 JSX](#TSX:TypeScript 中的 JSX)
  7. [React + TypeScript 实战](#React + TypeScript 实战)
  8. 常见类型定义技巧
  9. 最佳实践与避坑指南

1. TypeScript 简介

1.1 什么是 TypeScript

TypeScript(简称 TS)是 JavaScript 的超集,由微软开发。它在 JS 的基础上添加了静态类型系统

typescript 复制代码
// JavaScript(运行时才能发现错误)
function add(a, b) {
  return a + b;
}
add(1, "2"); // 不报错,但结果是 "12"(字符串拼接)

// TypeScript(编译时就能发现错误)
function add(a: number, b: number): number {
  return a + b;
}
add(1, "2"); // ❌ 编译错误:类型 'string' 不能赋值给类型 'number'

1.2 为什么需要 TypeScript

  • 更早发现错误:编译阶段捕获类型错误,而非运行时
  • 更好的 IDE 支持:智能提示、自动补全、重构支持
  • 可读性与可维护性:类型即文档,方便团队协作
  • 更安全的重构:修改代码时,类型系统会提示所有受影响的位置

1.3 安装与运行

bash 复制代码
# 全局安装 TypeScript
npm install -g typescript

# 编译单个文件
tsc hello.ts

# 初始化 tsconfig.json(项目配置)
tsc --init

# 使用 ts-node 直接运行(开发时方便)
npm install -D ts-node
ts-node hello.ts

1.4 tsconfig.json 基础配置

json 复制代码
{
  "compilerOptions": {
    "target": "ES2020",          // 编译目标 JS 版本
    "module": "ESNext",          // 模块系统
    "jsx": "react-jsx",          // JSX 转换模式
    "strict": true,              // 启用所有严格类型检查
    "esModuleInterop": true,     // 兼容 CommonJS/ES Module
    "skipLibCheck": true,        // 跳过 .d.ts 类型检查
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

2. 基础类型系统

2.1 原始类型

typescript 复制代码
// 布尔值
let isDone: boolean = false;

// 数字(支持十进制、十六进制、二进制、八进制)
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;

// 字符串
let color: string = "blue";
let fullName: string = `Bob Bobbington`;
let sentence: string = `Hello, my name is ${fullName}.`;

// 空值
function warnUser(): void {
  console.log("This is a warning message");
}

// null 和 undefined
let u: undefined = undefined;
let n: null = null;

2.2 any 与 unknown

typescript 复制代码
// any:绕过类型检查,尽量少用
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false;

// unknown:类型安全的 any,使用前必须进行类型检查
let userInput: unknown = getSomeInput();

if (typeof userInput === "string") {
  console.log(userInput.toUpperCase()); // ✅ 安全
}

// userInput.toUpperCase(); // ❌ 直接调用会报错

2.3 数组与元组

typescript 复制代码
// 数组:两种声明方式
let list1: number[] = [1, 2, 3];
let list2: Array<string> = ["a", "b", "c"];

// 元组:固定长度、固定类型的数组
let person: [string, number] = ["Alice", 25];
// person = [25, "Alice"]; // ❌ 顺序错误

// 具名元组(更清晰)
type Point = [x: number, y: number];
let p: Point = [10, 20];

2.4 对象类型

typescript 复制代码
// 直接声明对象形状
let user: { name: string; age: number } = {
  name: "Tom",
  age: 20,
};

// 可选属性
let config: { host: string; port?: number } = {
  host: "localhost",
};

// 只读属性
let readonlyUser: { readonly id: number; name: string } = {
  id: 1,
  name: "Tom",
};
// readonlyUser.id = 2; // ❌ 无法修改

2.5 联合类型与交叉类型

typescript 复制代码
// 联合类型:值可以是多种类型之一
let value: string | number = "hello";
value = 42;
// value = true; // ❌ 错误

// 类型收窄(Type Narrowing)
function printId(id: string | number) {
  if (typeof id === "string") {
    console.log(id.toUpperCase()); // TS 知道这里是 string
  } else {
    console.log(id.toFixed(2));    // TS 知道这里是 number
  }
}

// 交叉类型:合并多个类型
type Employee = { name: string; id: number };
type Manager = { department: string };

type ManagerEmployee = Employee & Manager;

let manager: ManagerEmployee = {
  name: "Alice",
  id: 1,
  department: "Engineering",
};

3. 接口与类型别名

3.1 Interface(接口)

typescript 复制代码
interface User {
  id: number;
  name: string;
  email?: string;        // 可选属性
  readonly createdAt: Date;
}

function createUser(user: User): User {
  return user;
}

const newUser = createUser({
  id: 1,
  name: "John",
  createdAt: new Date(),
});

3.2 接口的扩展

typescript 复制代码
interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

const myDog: Dog = {
  name: "Buddy",
  breed: "Golden Retriever",
};

3.3 Type Alias(类型别名)

typescript 复制代码
type UserID = string | number;
type Point = { x: number; y: number };

// 类型别名也可以表示联合类型
type Status = "pending" | "success" | "error";

let currentStatus: Status = "success";
// currentStatus = "unknown"; // ❌ 不在允许的范围内

3.4 Interface vs Type

特性 interface type
扩展方式 extends &(交叉类型)
同名合并 ✅ 自动声明合并 ❌ 不可重复定义
联合类型 ❌ 不支持 ✅ 支持
映射类型 ❌ 不支持 ✅ 支持
typescript 复制代码
// interface 自动声明合并
interface Window {
  myApp: string;
}
interface Window {
  version: number;
}
// Window 现在有 myApp 和 version 两个属性

建议 :定义对象形状时优先用 interface,需要联合类型或复杂类型运算时用 type


4. 函数与泛型

4.1 函数类型

typescript 复制代码
// 命名函数
function add(x: number, y: number): number {
  return x + y;
}

// 箭头函数
const multiply = (x: number, y: number): number => x * y;

// 可选参数和默认参数
function greet(name: string, greeting: string = "Hello"): string {
  return `${greeting}, ${name}!`;
}

// 剩余参数
function sum(...numbers: number[]): number {
  return numbers.reduce((a, b) => a + b, 0);
}

// 函数类型表达式
let binaryOp: (a: number, b: number) => number;
binaryOp = add;
binaryOp = multiply;

4.2 泛型(Generics)

泛型让你编写类型参数化的代码,实现代码复用和类型安全。

typescript 复制代码
// 无泛型:只能处理一种类型,或丢失类型信息
function identity(arg: any): any {
  return arg;
}

// 有泛型:保留传入的类型
function identity<T>(arg: T): T {
  return arg;
}

let output1 = identity<string>("myString");
let output2 = identity(123); // TS 自动推断类型为 number

4.3 泛型约束

typescript 复制代码
// 约束 T 必须具有 length 属性
interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(arg: T): T {
  console.log(arg.length);
  return arg;
}

logLength("hello");     // ✅ string 有 length
logLength([1, 2, 3]);   // ✅ 数组有 length
// logLength(123);      // ❌ number 没有 length

4.4 泛型接口与类

typescript 复制代码
// 泛型接口
interface GenericResponse<T> {
  data: T;
  status: number;
  message: string;
}

const userResponse: GenericResponse<User> = {
  data: { id: 1, name: "Tom", createdAt: new Date() },
  status: 200,
  message: "OK",
};

// 泛型类
class Container<T> {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  getValue(): T {
    return this.value;
  }
}

const numberContainer = new Container<number>(42);
const stringContainer = new Container("hello"); // 类型推断

5. JSX 是什么

5.1 JSX 语法

JSX(JavaScript XML)是一种 JavaScript 的语法扩展,允许在 JS 中编写类似 HTML 的结构。

jsx 复制代码
// JSX
const element = <h1 className="greeting">Hello, world!</h1>;

// 编译后(大致等效于)
const element = React.createElement(
  "h1",
  { className: "greeting" },
  "Hello, world!"
);

5.2 JSX 规则

  1. 必须有一个根元素

    jsx 复制代码
    // ❌ 错误:Adjacent JSX elements must be wrapped
    return (
      <h1>Title</h1>
      <p>Content</p>
    );
    
    // ✅ 正确
    return (
      <div>
        <h1>Title</h1>
        <p>Content</p>
      </div>
    );
    
    // ✅ 或者用 Fragment
    return (
      <>
        <h1>Title</h1>
        <p>Content</p>
      </>
    );
  2. 使用 camelCase 属性名

    jsx 复制代码
    // HTML 中的 class 变成 className
    <div className="container" tabIndex={0} />
  3. 大括号内嵌 JS 表达式

    jsx 复制代码
    const name = "Alice";
    const element = <h1>Hello, {name}</h1>;
    
    // 可以写任意表达式
    const element2 = <h1>1 + 1 = {1 + 1}</h1>;
  4. 标签必须闭合

    jsx 复制代码
    // ✅
    <img src="photo.jpg" alt="Photo" />
    <input type="text" />

6. TSX:TypeScript 中的 JSX

6.1 文件扩展名

  • .ts --- 普通 TypeScript 文件
  • .tsx --- 包含 JSX 的 TypeScript 文件

6.2 tsconfig 中的 JSX 配置

json 复制代码
{
  "compilerOptions": {
    "jsx": "react-jsx",      // React 17+ 新 JSX 转换
    // "jsx": "react",         // 传统转换:需要 import React
    "jsxImportSource": "react" // 自定义 JSX 运行时
  }
}

6.3 为 JSX 元素指定类型

在 TSX 中,HTML 标签有内置的类型定义。

tsx 复制代码
// 原生 DOM 元素的类型
const button: JSX.Element = <button>Click me</button>;

// HTML 属性类型
const inputProps: React.InputHTMLAttributes<HTMLInputElement> = {
  type: "text",
  placeholder: "Enter name",
  onChange: (e) => console.log(e.target.value),
};

6.4 React 类型定义安装

bash 复制代码
npm install -D @types/react @types/react-dom

7. React + TypeScript 实战

7.1 函数组件

tsx 复制代码
import React from "react";

// 定义 Props 类型
interface GreetingProps {
  name: string;
  age?: number; // 可选属性
}

// 方式一:显式声明 props 类型
function Greeting({ name, age }: GreetingProps) {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      {age && <p>You are {age} years old.</p>}
    </div>
  );
}

// 方式二:使用 React.FC(Functional Component)
const Greeting2: React.FC<GreetingProps> = ({ name, age }) => {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      {age && <p>You are {age} years old.</p>}
    </div>
  );
};

// 使用
function App() {
  return (
    <>
      <Greeting name="Alice" age={25} />
      <Greeting name="Bob" /> {/* age 是可选的 */}
      {/* <Greeting /> */}    {/* ❌ 缺少必需的 name 属性 */}
    </>
  );
}

注意 :现代 React 开发中,React.FC 的使用存在争议,因为它隐式包含 children 属性。许多团队更倾向于直接为 props 参数标注类型。

7.2 带 children 的组件

tsx 复制代码
interface CardProps {
  title: string;
  children: React.ReactNode; // 接受任何可渲染内容
}

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

// 使用
function App() {
  return (
    <Card title="Welcome">
      <p>This is some content inside the card.</p>
      <button>Action</button>
    </Card>
  );
}

children 类型选择

  • React.ReactNode --- 最宽泛,接受任何可渲染内容(推荐)
  • React.ReactElement --- 只接受 JSX 元素
  • string / number --- 仅接受特定类型

7.3 useState Hook

tsx 复制代码
import { useState } from "react";

function Counter() {
  // TS 自动推断类型为 number
  const [count, setCount] = useState(0);

  // 显式指定类型(初始值为 null/undefined 时必需)
  const [user, setUser] = useState<User | null>(null);

  // 复杂对象
  interface FormState {
    username: string;
    password: string;
    isLoading: boolean;
  }

  const [form, setForm] = useState<FormState>({
    username: "",
    password: "",
    isLoading: false,
  });

  const handleUpdate = (field: keyof FormState, value: string | boolean) => {
    setForm((prev) => ({ ...prev, [field]: value }));
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>Increment</button>

      <input
        value={form.username}
        onChange={(e) => handleUpdate("username", e.target.value)}
      />
    </div>
  );
}

7.4 useRef Hook

tsx 复制代码
import { useRef, useEffect } from "react";

function TextInputWithFocusButton() {
  // DOM 引用
  const inputRef = useRef<HTMLInputElement>(null);

  // 存储不触发重新渲染的值
  const renderCount = useRef<number>(0);

  useEffect(() => {
    renderCount.current++;
  });

  const handleClick = () => {
    // inputRef.current 可能是 null,需要可选链或类型守卫
    inputRef.current?.focus();
  };

  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={handleClick}>Focus the input</button>
      <p>Rendered {renderCount.current} times</p>
    </>
  );
}

7.5 事件处理

tsx 复制代码
import { useState } from "react";

function Form() {
  const [value, setValue] = useState("");

  // 输入事件
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  // 表单提交事件
  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    console.log("Submitted:", value);
  };

  // 鼠标事件
  const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
    console.log("Clicked at:", event.clientX, event.clientY);
  };

  // 键盘事件
  const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
    if (event.key === "Enter") {
      console.log("Enter pressed");
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={value}
        onChange={handleChange}
        onKeyDown={handleKeyDown}
      />
      <button type="submit" onClick={handleClick}>
        Submit
      </button>
    </form>
  );
}

常见事件类型速查表

事件 类型
onChange (input) React.ChangeEvent<HTMLInputElement>
onClick React.MouseEvent<HTMLButtonElement>
onSubmit React.FormEvent<HTMLFormElement>
onKeyDown React.KeyboardEvent<HTMLInputElement>
onScroll React.UIEvent<HTMLDivElement>
onFocus / onBlur React.FocusEvent<HTMLInputElement>

7.6 自定义 Hook

tsx 复制代码
import { useState, useEffect } from "react";

// 泛型 Hook:获取异步数据
function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const json = await response.json();
        setData(json);
      } catch (err) {
        setError(err as Error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

// 使用
interface User {
  id: number;
  name: string;
  email: string;
}

function UserList() {
  const { data: users, loading, error } = useFetch<User[]>("/api/users");

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  if (!users) return null;

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

8. 常见类型定义技巧

8.1 为第三方库补充类型

当某个库没有类型定义时:

typescript 复制代码
// types/my-untyped-lib.d.ts
declare module "some-untyped-library" {
  export function doSomething(input: string): number;
  export const version: string;
}

8.2 全局类型声明

typescript 复制代码
// types/global.d.ts
declare global {
  interface Window {
    myApp: {
      version: string;
      config: Record<string, unknown>;
    };
  }
}

// 现在可以在代码中使用
window.myApp.version;

8.3 实用工具类型(Utility Types)

typescript 复制代码
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  createdAt: Date;
}

// Partial:所有属性变为可选
type PartialUser = Partial<User>;

// Pick:选取指定属性
type UserPreview = Pick<User, "id" | "name">;

// Omit:排除指定属性
type PublicUser = Omit<User, "password">;

// Required:所有属性变为必需
type StrictUser = Required<PartialUser>;

// Record:构建键值对类型
type UsersById = Record<number, User>;

// ReturnType:提取函数返回类型
type ApiResponse = ReturnType<typeof fetchUser>;

// Parameters:提取函数参数类型
type FetchUserParams = Parameters<typeof fetchUser>;

8.4 组件 Props 提取

tsx 复制代码
import { ComponentProps } from "react";

// 提取原生 button 的所有 props
type ButtonProps = ComponentProps<"button">;

// 在自定义组件中扩展原生属性
interface MyButtonProps extends ComponentProps<"button"> {
  variant?: "primary" | "secondary" | "danger";
  isLoading?: boolean;
}

function MyButton({ variant = "primary", isLoading, children, ...rest }: MyButtonProps) {
  return (
    <button className={`btn btn-${variant}`} disabled={isLoading} {...rest}>
      {isLoading ? "Loading..." : children}
    </button>
  );
}

9. 最佳实践与避坑指南

9.1 ✅ 推荐做法

  1. 启用严格模式

    json 复制代码
    {
      "compilerOptions": {
        "strict": true
      }
    }
  2. 优先使用 interface 定义对象,用 type 定义联合/工具类型

  3. 避免使用 any

    typescript 复制代码
    // ❌ 不好
    function process(data: any) { ... }
    
    // ✅ 好
    function process<T>(data: T) { ... }
    // 或
    function process(data: unknown) { ... }
  4. 使用 satisfies 关键字(TS 4.9+)

    typescript 复制代码
    const config = {
      host: "localhost",
      port: 3000,
    } satisfies { host: string; port: number };
  5. 为 Hook 依赖项提供完整类型

    typescript 复制代码
    useEffect(() => {
      // ...
    }, [userId]); // TS 会检查 userId 类型是否稳定

9.2 ❌ 常见错误

错误 说明 修正
any 滥用 失去类型保护 unknown + 类型守卫替代
可选链忘记处理 null user?.name 整体可能是 undefined 提供默认值或类型守卫
忘记给 useState 传泛型 useState(null) 推断为 null 类型 `useState<string
as 类型断言滥用 强制转换可能不安全 优先用类型守卫缩小类型
事件类型写错 e: Event 太宽泛 React.MouseEvent<HTMLButtonElement>

9.3 快速排错指南

typescript 复制代码
// 问题:Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'XXX'
// 解决:使用 Record 或添加索引签名
interface Config {
  [key: string]: string;
}

// 问题:Type 'string | undefined' is not assignable to type 'string'
// 解决:非空断言(确定有值时)或提供默认值
const name = user.name ?? "Anonymous";
// 或
const name = user.name!; // 确定不会为 null/undefined

// 问题:No overload matches this call
// 解决:检查函数重载定义,确保参数类型匹配

附录:学习资源


恭喜!完成本教程后,你已经掌握了 TypeScript 的核心类型系统和在 React 中使用 JSX 的基础知识。建议通过实际项目练习来巩固这些概念。

相关推荐
Artech3 小时前
[对比学习LangChain和MAF-03]完全不同的Agent设计哲学
python·ai·langchain·c#·agent·maf
lihaozecq3 小时前
Agent 开发 Todo 机制设计,让 Agent 拥有规划能力
前端·agent·ai编程
想ai抽3 小时前
hermes-kanban-安装与操作手册
ai·agent·hermes
lihaozecq4 小时前
Agent 开发的 skills 机制设计 - 渐进式披露
前端·agent·ai编程
DO_Community4 小时前
Token聚合平台 vs 传统云 vs AI原生云,AI推理应用怎么选?
人工智能·agent·token·ai-native·deepseek
counterxing4 小时前
Agent Skill 不是越多越好:别把能力清单塞成系统 Prompt 垃圾场
agent·ai编程·claude
扫地的小何尚4 小时前
掌握 Agentic AI 技术:AI Agent 定制方法全景与实践路径
大数据·人工智能·算法·ai·llm·agent·nvidia
倾颜4 小时前
AI 应用里的第一个 Agent:我如何做一个可控的 Tasklist Agent
langchain·agent·next.js
counterxing12 小时前
Agent 跑起来之后,难的是复用、观测和评测
node.js·agent·ai编程