在 TypeScript 中使用 React 可以提高代码的可维护性、可读性和可靠性。TypeScript 提供了静态类型检查和丰富的类型系统,这些功能在 React 开发中非常有用。下面详细介绍如何在 React 项目中使用 TypeScript,并结合泛型和 infer 来定义类型。
1. 项目初始化
首先,创建一个使用 TypeScript 的 React 项目:
pnpm create vite my-app --template react-ts
cd my-app
2. 基本用法
在 TypeScript 中,可以为组件的 props 和 state 定义类型。
函数组件:
import React from 'react';
interface GreetingProps {name: string;
}const Greeting: React.FC<GreetingProps> = (( name )) => {return <h1>Hello, {name}!</h1>;
};export default Greeting;
类组件:
import React, { Component } from 'react';interface CounterProps {initialCount?: number;
}interface CounterState {count: number;
}class Counter extends Component<CounterProps, CounterState> {static defaultProps = {initialCount: 0,};constructor(props: CounterProps) {super(props);this.state = { count: props.initialCount || 0 };render() {return (<div><p>Count: {this.state.count}</p><button onClick={() => this.setState({count: this.state.count + 1})}>Increment</button></div>);}}
}export default Counter;
3. 使用泛型
泛型允许我们定义可以在多种类型之间复用的组件和函数。
要想定义出灵活的组件类型,泛型和 infer 的使用必不可少,如果你在 React 开发过程中大量用到 any ,说明你你没有真正掌握 Typescript,因为只要你灵活掌握了 Typescript,那么在项目中所有的类型均能通过类型定义来推导约束。
示例:泛型列表组件
import React from 'react';interface ListProps<T> {items: T[];renderItem: (item: T) => React.ReactNode;
}function List<T>({ items, renderItem }: ListProps<T>) {return (<ul>{items.map((item, index) => (<li key={index}>{renderItem(item)}</li>))}</ul>);
}// 使用泛型列表组件
const NumberList = () => {const numbers = [1, 2, 3, 4, 5];return <List items={numbers} renderItem={(num) => <span>{num}</span>} />;
};const StringList = () => {const strings = ['one', 'two', 'three'];return <List items={strings} renderItem={(str) => <strong>{str}</strong>} />;
};export { NumberList, StringList };
4. 使用 infer 定义类型
infer 关键字可以在条件类型中使用,用于推断类型。
示例:推断函数返回类型
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;function getUser() {return { name: 'John', age: 30 };
}type User = GetReturnType<typeof getUser>; // { name: string; age: number; }
示例:推断组件 props 类型
import React, { ComponentType } from 'react';type InferProps<T> = T extends ComponentType<infer P> ? P : never;const MyComponent = (props: { name: string; age: number }) => {return <div>{props.name} is {props.age} years old.</div>;
};type MyComponentProps = InferProps<typeof MyComponent>; // { name: string; age: number }
5. 综合示例
综合示例:使用 TypeScript、泛型和 infer 定义类型并在 React 组件中使用。
import React from 'react';// 定义一个数据类型
interface User {id: number;name: string;
}// 定义一个泛型函数组件,接收数据和渲染函数作为参数
interface DataListProps<T> {data: T[];renderItem: (item: T) => React.ReactNode;
}const DataList = <T,>({ data, renderItem }: DataListProps<T>) => {return (<ul>{data.map((item) => (<li key={(item as any).id}>{renderItem(item)}</li>))}</ul>);
};// 使用 infer 推断组件 props 类型
type InferProps<T> = T extends React.ComponentType<infer P> ? P : never;// 创建一个渲染 user 数据的组件
const UserList: React.FC<DataListProps<User>> = (props) => {return <DataList {...props} />;
};// 使用 UserList 组件
const App: React.FC = () => {const users: User[] = [{ id: 1, name: 'John' },{ id: 2, name: 'Jane' },];return (<div><h1>User List</h1><UserList data={users} renderItem={(user) => <span>{user.name}</span>} /></div>);
};export default App;
在这个综合示例中:
1. 定义了一个 User 类型;
2. 创建了一个泛型组件 Datalist,用于渲染任意类型的数据列表;
3. 使用 infer 推断 UserList 组件的 props 类型;
4. 在 App 组件中使用 UserList 组件渲染用户数据列表;
6. 针对 React 的进阶版 tsconfig
tsconfig.json 是 TypeScript 项目配置的重要文件,通过合理配置,可以提高开发效率和代码质量。
针对 React 项目,重点配置项包括目标版本、库、模块解析、JSX 支持以及严格类型检查选项。
根据项目需求,可以进一步调整和扩展配置,确保最佳的开发体验。
这是一个基础的 tsconfig.json 示例,适用于大多数 React 项目:
{"compilerOptions": {"target": "es2017", // 指定 ECMAScript 目标版本。ES5 兼容性最好,可以在大多数浏览器中运行"lib": ["dom", "dom.iterable", "esnext"], // 指定编译时包含的库文件。通常包括 dom、dom.iterable和esnext"allowJs": true, // 允许编译 .js 文件。适用于项目中混合使用 TypeScript 和 JavaScript 文件"skipLibCheck": true, // 跳过类型声明文件的类型检查。可以加快编译速度"esModuleInterop": true, // 允许对 ES 模块默认导入进行编译时的兼容处理"allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块中默认导入。与 esModuleInterop 配合使用"strict": true, // 启用所有严格类型检查选项。建议开启以确保类型安全"forceConsistentCasingInFileNames": true, // 禁止文件名大小写不一致。保证跨平台一致性"module": "esnext", // 指定模块代码生成方式。esnext 适用于现代 JavaScript 运行环境"moduleResolution": "node", // 指定模块解析策略。node 适用于 Node.js 生态系统"resolveJsonModule": true, // 允许导入 JSON 文件"isolatedModules": true, // 将每个文件作为独立的模块。对 Babel 等工具很有用"noEmit": true, // 禁止生成输出文件。适用于仅进行类型检查的项目"jsx": "react-jsx", // 指定 JSX 代码生成方式。React 17 及以上版本推荐使用 react-jsx"baseUrl": "./src", // 配置基地址,使导入模块时更简洁"paths": { // 配置路径别名,使导入模块时更简洁"@components/*": ["components/*"],"@utils/*": ["utils/*"]},"strictNullChecks": true, // 启用严格的 null 检查"noImplicitAny": true, // 禁止隐式的 any 类型"noImplicitThis": true, // 禁止隐式的 this 类型"alwaysStrict": true, // 如终以严格模式"noUnusedLocals": true, // 报告未使用的局部变量"noUnusedParameters": true, // 报告未使用的函数参数"noImplicitReturns": true, // 所有代码路径必须显式返回值"noFallthroughCasesInSwitch": true // 禁止 switch 语句中的 case 语句贯穿},"include": ["src"], // 指定要包含的文件和目录。通常指定src 目录"exclude": ["node_modules", "build"] // 排除指定的目录
}