一、React与TypeScript搭配核心优势
TypeScript(简称TS)是JavaScript的超集,核心优势是静态类型检查,能在开发阶段发现类型错误,避免运行时bug;同时提供更清晰的代码提示、更好的代码可维护性和可扩展性,尤其适合中大型React项目。
React与TS搭配的核心价值:
- 组件Props类型约束:明确组件接收的参数类型、必填项,减少传参错误;
- 状态(State)类型定义:规范状态的数据结构,避免状态赋值错误;
- 减少类型相关注释:类型定义即文档,提升团队协作效率;
- IDE友好提示:自动补全组件属性、方法,降低开发成本。
二、环境搭建(React + TS)
2.1 快速创建React+TS项目
使用create-react-app快速初始化,自带TS配置,无需手动配置webpack、tsconfig.json:
# 方式1:npx(推荐,无需全局安装)
npx create-react-app react-ts-demo --template typescript
# 方式2:yarn
yarn create react-app react-ts-demo --template typescript
项目创建完成后,核心文件说明:
- .tsx:React组件文件后缀(包含JSX语法,必须用.tsx);
- .ts:非组件的TS文件(如工具函数、类型定义);
- tsconfig.json:TS的核心配置文件(指定编译选项、类型检查规则);
- react-app-env.d.ts:React与TS的类型声明文件(自动生成,无需修改)。
2.2 核心配置(tsconfig.json关键项)
无需手动修改默认配置,重点了解以下关键项,便于后续自定义:
{
"compilerOptions": {
"target": "ESNext", // 目标JS版本
"module": "ESNext", // 模块规范
"jsx": "react-jsx", // 支持JSX语法(React 17+ 推荐)
"strict": true, // 开启严格模式(推荐,强制类型检查)
"esModuleInterop": true, // 兼容CommonJS模块
"skipLibCheck": true, // 跳过第三方库类型检查
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true, // 支持导入JSON文件
"isolatedModules": true,
"noEmit": true // 不生成编译后的JS文件(由create-react-app处理)
},
"include": ["src"] // 需要编译的文件目录
}
三、React+TS基础使用(核心语法)
3.1 组件Props类型定义(最常用)
通过interface(接口)定义Props类型,明确组件接收的参数,支持必填/可选、默认值、联合类型等。
import React from 'react';
// 1. 定义Props接口(首字母大写,约定俗成)
interface UserCardProps {
// 必填项(无?)
name: string;
age: number;
// 可选项(加?)
gender?: 'male' | 'female' | 'other'; // 联合类型,限制可选值
// 函数类型(定义回调函数)
onBtnClick: (id: number) => void;
}
// 2. 组件接收Props,指定类型为UserCardProps
const UserCard: React.FC<UserCardProps> = (props) => {
// 解构Props(更简洁)
const { name, age, gender = 'other', onBtnClick } = props;
return (
<div className="user-card">
<h3>{name}</h3>
<p>年龄:{age}</p>
<p>性别:{gender}</p>
<button onClick={() => onBtnClick(123)}>点击</button>
</div>
);
};
export default UserCard;
说明:React.FC 是React函数组件的类型,泛型参数即为Props的类型;可选参数通过?标记,可设置默认值避免undefined。
3.2 组件State类型定义
使用useState时,TS会自动推断状态类型(类型推导),复杂状态(如对象、数组)需手动指定类型。
import React, { useState } from 'react';
// 1. 简单状态(自动推断类型)
const SimpleState = () => {
// TS自动推断count为number类型,setCount只能接收number
const [count, setCount] = useState(0);
// 错误示例:setCount('123') → 类型不匹配,开发阶段报错
return <button onClick={() => setCount(count + 1)}>计数:{count}</button>;
};
// 2. 复杂状态(对象类型,手动指定)
interface UserState {
name: string;
age: number;
isLogin: boolean;
}
const ComplexState = () => {
// 手动指定状态类型为UserState,初始值需符合该类型
const [user, setUser] = useState<UserState>({
name: '张三',
age: 20,
isLogin: false,
});
// 修改状态(必须符合UserState类型)
const login = () => {
setUser({ ...user, isLogin: true });
};
return (
<div>
<p>{user.name}({user.age}岁)</p>
<button onClick={login}>{user.isLogin ? '已登录' : '登录'}</button>
</div>
);
};
export default ComplexState;
3.3 事件处理类型定义
React事件有固定的TS类型(如点击事件React.MouseEvent、输入事件React.ChangeEvent),需指定事件类型和目标元素类型。
import React, { useState } from 'react';
const EventDemo = () => {
const [inputValue, setInputValue] = useState('');
// 1. 点击事件(React.MouseEvent,可指定目标元素类型)
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log('按钮点击', e.target.innerText);
};
// 2. 输入框变化事件(React.ChangeEvent,目标为输入框)
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// e.target.value 自动推断为string类型
setInputValue(e.target.value);
};
return (
<div>
<input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder="请输入内容"
/>
<button onClick={handleClick}>提交</button>
</div>
);
};
export default EventDemo;
3.4 自定义Hook类型定义
自定义Hook返回值为多个数据时,需指定返回值类型(可通过元组、对象),确保使用时类型正确。
import React, { useState, useEffect } from 'react';
// 自定义Hook:获取窗口宽度
// 定义返回值类型(元组类型,固定顺序)
type UseWindowWidthReturn = [number, () => void];
const useWindowWidth = (): UseWindowWidthReturn => {
const [width, setWidth] = useState(window.innerWidth);
const updateWidth = () => {
setWidth(window.innerWidth);
};
useEffect(() => {
window.addEventListener('resize', updateWidth);
return () => window.removeEventListener('resize', updateWidth);
}, []);
// 返回值必须符合UseWindowWidthReturn类型
return [width, updateWidth];
};
// 使用自定义Hook
const WindowWidthDemo = () => {
// 自动推断width为number,updateWidth为() => void
const [width, updateWidth] = useWindowWidth();
return <p>当前窗口宽度:{width}px</p>;
};
export default WindowWidthDemo;
四、React+TS实战案例(2个核心场景)
案例1:TodoList(基础综合案例)
涵盖Props、State、事件处理、数组类型,适合新手入门,完整实现“添加、删除、切换完成状态”功能。
import React, { useState } from 'react';
// 1. 定义Todo类型(单个任务)
interface Todo {
id: number;
text: string;
done: boolean;
}
// 2. 定义TodoItem组件Props
interface TodoItemProps {
todo: Todo;
onToggle: (id: number) => void;
onDelete: (id: number) => void;
}
// 3. 子组件:TodoItem
const TodoItem: React.FC<TodoItemProps> = ({ todo, onToggle, onDelete }) => {
return (
<li style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)} style={{ marginLeft: 10 }}>
删除
</button>
</li>
);
};
// 4. 父组件:TodoList
const TodoList: React.FC = () => {
// 状态:todo列表(数组类型,元素为Todo)
const [todos, setTodos] = useState<Todo[]>([
{ id: 1, text: '学习React+TS', done: false },
{ id: 2, text: '完成实战案例', done: true },
]);
// 状态:输入框内容
const [inputText, setInputText] = useState('');
// 添加Todo
const addTodo = (e: React.FormEvent) => {
e.preventDefault(); // 阻止表单默认提交
if (!inputText.trim()) return;
const newTodo: Todo = {
id: Date.now(), // 用时间戳作为唯一id
text: inputText,
done: false,
};
setTodos([...todos, newTodo]);
setInputText(''); // 清空输入框
};
// 切换Todo完成状态
const toggleTodo = (id: number) => {
setTodos(todos.map((todo) => (todo.id === id ? { ...todo, done: !todo.done } : todo)));
};
// 删除Todo
const deleteTodo = (id: number) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
return (
<div style={{ maxWidth: 500, margin: '0 auto', padding: 20 }}>
<h2>TodoList(React+TS)</h2>
<form onSubmit={addTodo} style={{ marginBottom: 20 }}>
<input
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="请输入任务"
style={{ padding: 8, width: 300 }}
/>
<button type="submit" style={{ padding: 8, marginLeft: 10 }}>
添加
</button>
</form>
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
))}
</ul>
</div>
);
};
export default TodoList;
案例2:用户列表(接口请求+复杂类型)
涵盖接口请求(fetch/axios)、加载状态、错误处理、复杂对象类型,贴近真实项目场景,使用axios请求接口(需先安装:npm install axios @types/axios)。
import React, { useState, useEffect } from 'react';
import axios from 'axios';
// 1. 定义用户类型(接口返回数据结构)
interface User {
id: number;
name: string;
username: string;
email: string;
phone: string;
website: string;
}
// 2. 定义接口返回类型(假设接口返回{ data: User[] })
interface UserResponse {
data: User[];
}
const UserList: React.FC = () => {
// 状态:用户列表
const [users, setUsers] = useState<User[]>([]);
// 状态:加载状态
const [loading, setLoading] = useState<boolean>(true);
// 状态:错误信息
const [error, setError] = useState<string | null>(null);
// 接口请求( useEffect 模拟组件挂载时请求)
useEffect(() => {
const fetchUsers = async () => {
try {
setLoading(true);
// 调用接口(示例接口:JSONPlaceholder)
const response = await axios.get<UserResponse>('https://jsonplaceholder.typicode.com/users');
// 接口返回数据符合UserResponse类型,data为User数组
setUsers(response.data.data);
setError(null);
} catch (err) {
setError('请求用户列表失败,请稍后再试');
console.error('请求错误:', err);
} finally {
setLoading(false);
}
};
fetchUsers();
}, []);
// 加载中
if (loading) return <div style={{ textAlign: 'center', padding: 50 }}>加载中...</div>;
// 错误提示
if (error) return <div style={{ textAlign: 'center', padding: 50, color: 'red' }}>{error}</div>;
// 渲染用户列表
return (
<div style={{ maxWidth: 800, margin: '0 auto', padding: 20 }}>
<h2>用户列表(React+TS+接口请求)</h2>
<table border="1" style={{ width: '100%', borderCollapse: 'collapse', marginTop: 20 }}>
<thead>
<tr style={{ backgroundColor: '#f0f0f0' }}>
<th style={{ padding: 10, textAlign: 'center' }}>ID</th>
<th style={{ padding: 10, textAlign: 'center' }}>姓名</th>
<th style={{ padding: 10, textAlign: 'center' }}>用户名</th>
<th style={{ padding: 10, textAlign: 'center' }}>邮箱</th>
<th style={{ padding: 10, textAlign: 'center' }}>电话</th>
<th style={{ padding: 10, textAlign: 'center' }}>网站</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id}>
<td style={{ padding: 10, textAlign: 'center' }}>{user.id}</td>
<td style={{ padding: 10, textAlign: 'center' }}>{user.name}</td>
<td style={{ padding: 10, textAlign: 'center' }}>{user.username}</td>
<td style={{ padding: 10, textAlign: 'center' }}>{user.email}</td>
<td style={{ padding: 10, textAlign: 'center' }}>{user.phone}</td>
<td style={{ padding: 10, textAlign: 'center' }}>
<a href={`http://${user.website}`} target="_blank" rel="noopener noreferrer">
{user.website}
</a>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default UserList;
五、常见问题及解决方案
- 问题1:“Property 'xxx' does not exist on type 'never'”? 解决方案:复杂状态(如数组、对象)初始值为空时,TS无法推断类型,需手动指定泛型(如
useState<User[]>([]))。 - 问题2:组件Props报错“Type 'undefined' is not assignable to type 'xxx'”? 解决方案:检查Props是否为必填项,若为可选项添加
?,或给Props设置默认值。 - 问题3:事件对象e报错“Property 'target' does not exist on type 'Event'”? 解决方案:指定事件类型(如
React.MouseEvent<HTMLButtonElement>),明确目标元素类型。 - 问题4:接口请求返回数据类型不匹配? 解决方案:定义接口返回类型(如案例2中的UserResponse),axios请求时指定泛型(
axios.get<UserResponse>(url))。
六、总结
React+TS的核心是“类型约束”,重点掌握3个核心点:Props类型定义(interface)、State类型推导与手动指定、事件类型定义。通过基础语法练习和实战案例,能快速适应TS在React中的使用,尤其在中大型项目中,TS能显著提升代码质量和开发效率。
后续可深入学习:React组件泛型、Redux+TS、React Router+TS等进阶内容,进一步完善技术栈。