当前位置:  首页>> 技术小册>> TypeScript入门指南

React 和 TypeScript 都是非常流行的技术,在组合使用时能够大大提高代码的可靠性和可维护性。TypeScript 的强类型检查能够帮助我们在编写 React 应用时更早地发现问题,而 React 的声明式编程模型则让我们能够更加专注于 UI 的实现。

本章节将介绍在 React 中愉快地使用 TypeScript 的一些技巧和注意事项,包括内置类型和泛型的使用,同时也会通过一些代码示例来说明。


1、内置类型

React.FC

React.FC 是 React 中的一个内置类型,它表示一个 React 函数组件。使用 React.FC 可以为函数组件的 Props 添加类型注解,从而让 TypeScript 在编译时对 Props 进行类型检查。

下面是一个使用 React.FC 的示例:

  1. import React from 'react';
  2. interface Props {
  3. name: string;
  4. }
  5. const Greeting: React.FC<Props> = ({ name }) => {
  6. return <div>Hello, {name}!</div>;
  7. };
  8. export default Greeting;

在上面的代码中,我们首先定义了一个 Props 接口,它包含一个名为 name 的属性,类型为 string。然后使用 React.FC 来定义 Greeting 组件的类型,指定 Props 的类型为 Props。最后,我们实现了 Greeting 组件,并将其导出。

在这个示例中,我们使用了解构赋值来从 Props 中获取 name 属性,并在组件中使用它。如果我们尝试将 Props 中的 name 属性的类型改为 number,TypeScript 就会在编译时报错,提示我们该属性的类型不正确。

React.ReactNode

React.ReactNode 是另一个内置类型,它表示一个 React 组件的子元素。使用 React.ReactNode 可以为组件的子元素添加类型注解,从而让 TypeScript 在编译时对子元素进行类型检查。

下面是一个使用 React.ReactNode 的示例:

  1. import React from 'react';
  2. interface Props {
  3. children: React.ReactNode;
  4. }
  5. const Box: React.FC<Props> = ({ children }) => {
  6. return <div style={{ border: '1px solid black' }}>{children}</div>;
  7. };
  8. export default Box;

在上面的代码中,我们定义了一个 Props 接口,它包含一个名为 children 的属性,类型为 React.ReactNode。然后使用 React.FC 来定义 Box 组件的类型,指定 Props 的类型为 Props。最后,我们实现了 Box 组件,并将其导出。

在这个示例中,我们使用了 Props 中的 children 属性来渲染组件的子元素。由于我们将 children 的类型注解为 React.ReactNode,因此如果我们尝试将一个不是 React 组件的元素传递给 Box 组件作为子元素,TypeScript 就会在编译时报错。

2、泛型

泛型是 TypeScript 中非常强大的一种特性,它可以让我们编写更加通用的代码。在 React 中,我们可以使用泛型来创建更加灵活的组件和函数,从而提高代码的复用性和可读性。

泛型组件

下面是一个使用泛型创建通用表单组件的示例:

  1. import React, { useState } from 'react';
  2. interface FieldProps<T> {
  3. value: T;
  4. onChange: (value: T) => void;
  5. }
  6. interface Props<T> {
  7. fields: Array<{ name: string; label: string } & FieldProps<T>>;
  8. }
  9. function Form<T>({ fields }: Props<T>) {
  10. const [values, setValues] = useState<T>(() => {
  11. const initial: Partial<T> = {};
  12. fields.forEach((field) => (initial[field.name] = field.value));
  13. return initial as T;
  14. });
  15. function handleChange(name: string, value: T) {
  16. setValues((prev) => ({ ...prev, [name]: value }));
  17. }
  18. function handleSubmit(event: React.FormEvent) {
  19. event.preventDefault();
  20. console.log(values);
  21. }
  22. return (
  23. <form onSubmit={handleSubmit}>
  24. {fields.map((field) => (
  25. <div key={field.name}>
  26. <label htmlFor={field.name}>{field.label}</label>
  27. <input
  28. type="text"
  29. id={field.name}
  30. value={values[field.name]}
  31. onChange={(event) =>
  32. handleChange(field.name, event.target.value as T)
  33. }
  34. />
  35. </div>
  36. ))}
  37. <button type="submit">Submit</button>
  38. </form>
  39. );
  40. }
  41. export default Form;

在上面的代码中,我们定义了一个 FieldProps 接口,它包含一个 value 属性和一个 onChange 方法,用于表示表单控件的值和值变化的回调函数。然后定义了一个 Props 接口,它包含一个名为 fields 的属性,类型为 FieldProps 的数组。最后我们实现了 Form 组件,使用泛型来表示表单的值的类型。

在 Form 组件中,我们使用 useState 钩子来管理表单的值。在 handleChange 函数中,我们使用 setValues 方法来更新表单的值,而 handleSubmit 函数则用于处理表单的提交。

使用上面的 Form 组件,我们可以创建一个包含多个输入框的表单,如下所示:

  1. import React from 'react';
  2. import Form from './Form';
  3. interface User {
  4. name: string;
  5. age: number;
  6. }
  7. function App() {
  8. return (
  9. <div>
  10. <Form<User>
  11. fields={[
  12. {
  13. name: 'name',
  14. label: 'Name',
  15. value: '',
  16. onChange: (value: string) => console.log(value),
  17. },
  18. {
  19. name: 'age',
  20. label: 'Age',
  21. value: 0,
  22. onChange: (value: number) => console.log(value),
  23. },
  24. ]}
  25. />
  26. </div>
  27. );
  28. }
  29. export default App;

在上面的代码中,我们将 User 类型作为泛型参数传递给 Form 组件,同时为每个输入框传递一个包含 value 和 onChange 属性的对象。

泛型函数

下面是一个使用泛型创建通用的 map 函数的示例:

  1. function map<T, U>(array: T[], callback: (item: T) => U): U[] {
  2. const result: U[] = [];
  3. for (let i = 0; i < array.length; i++) {
  4. result.push(callback(array[i]));
  5. }
  6. return result;
  7. }
  8. export default map;
  1. 在上面的代码中,我们定义了一个 map 函数,它使用泛型来表示输入数组的类型和输出数组的类型。map 函数接收两个参数:一个数组和一个回调函数,回调函数接收一个数组元素作为参数,并返回一个新的元素。map 函数遍历输入数组,对每个元素应用回调函数,将结果添加到一个新的数组中,最后返回这个数组。
  2. 使用上面的 map 函数,我们可以轻松地创建一个新的数组:
  3. ```tsx
  4. import React from 'react';
  5. import map from './map';
  6. function App() {
  7. const numbers = [1, 2, 3, 4, 5];
  8. const doubled = map(numbers, (n) => n * 2);
  9. return (
  10. <div>
  11. <p>Original numbers: {numbers.join(', ')}</p>
  12. <p>Doubled numbers: {doubled.join(', ')}</p>
  13. </div>
  14. );
  15. }
  16. export default App;

在上面的代码中,我们创建了一个包含 1 到 5 的数字的数组,并使用 map 函数将数组中的每个数字翻倍。最后,我们在页面上显示了原始数字和翻倍后的数字。

3、坑位

虽然使用 TypeScript 和泛型可以帮助我们编写更加安全和健壮的代码,但是在使用时还是需要注意一些坑位,下面是一些需要注意的问题:

类型推断

在 TypeScript 中,类型推断是非常重要的。在编写泛型代码时,如果不小心定义了错误的类型,可能会导致类型推断出现问题。例如,下面的代码:

  1. function identity<T>(arg: T): T {
  2. return arg;
  3. }
  4. const result = identity('hello');
  5. console.log(result.length);

在上面的代码中,我们定义了一个 identity 函数,它使用泛型来表示输入和输出的类型。然后我们调用这个函数,并将一个字符串作为参数传递给它。最后,我们尝试在控制台上打印函数返回值的 length 属性。然而,TypeScript 编译器并没有报错,而是将 result 推断为 string 类型,因此在访问 length 属性时不会出现错误。

为了避免这种情况发生,我们可以显式指定泛型类型,如下所示:

  1. const result = identity<string>('hello');

类型限制

在使用泛型时,需要注意泛型类型的限制。例如,下面的代码:

  1. interface Lengthwise {
  2. length: number;
  3. }
  4. function loggingIdentity<T extends Lengthwise>(arg: T): T {
  5. console.log(arg.length);
  6. return arg;
  7. }
  8. loggingIdentity(3); // error: number does not have a 'length' property

在上面的代码中,我们定义了一个接口 Lengthwise,它包含一个 length 属性。然后我们定义了一个 loggingIdentity 函数,它使用泛型 T,并限制泛型 T必须具有 length 属性,以便我们可以打印出这个属性的值。最后,我们尝试调用 loggingIdentity 函数,并将一个数字作为参数传递给它。由于数字类型没有 length 属性,TypeScript 编译器会报错。

在使用泛型时,我们需要仔细考虑泛型类型的限制,以确保函数不会收到无效的参数。

类型重载

在 TypeScript 中,我们可以使用类型重载来定义多个函数签名,以处理不同类型的参数。例如,下面的代码:

  1. function reverse<T>(arg: T[]): T[];
  2. function reverse<T>(arg: T): T;
  3. function reverse(arg: any) {
  4. if (Array.isArray(arg)) {
  5. return arg.reverse();
  6. } else {
  7. return arg.split('').reverse().join('');
  8. }
  9. }
  10. console.log(reverse([1, 2, 3, 4, 5])); // [5, 4, 3, 2, 1]
  11. console.log(reverse('hello')); // 'olleh'

在上面的代码中,我们定义了两个函数签名来处理数组和字符串类型的参数。然后我们定义了一个 reverse 函数,并使用类型重载来定义两个函数签名。第一个函数签名接收一个数组,并返回一个数组。第二个函数签名接收一个字符串,并返回一个字符串。最后,我们在控制台上分别调用了这两个函数。

使用类型重载可以帮助我们更好地处理不同类型的参数,并提高代码的可读性。

小结
在 React 中使用 TypeScript 和泛型可以帮助我们编写更加安全和健壮的代码。通过使用类型限制、类型推断和类型重载等技术,我们可以避免很多常见的编程错误,并提高代码的可维护性和可读性。如果你是一名 React 开发者,并且想要提高自己的编程技能,那么学习 TypeScript 和泛型是非常重要的一步。


该分类下的相关小册推荐: