LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

React 与 TypeScript:提升代码质量的 10 个模式

admin
2025年7月20日 9:7 本文热度 132

构建可扩展且可维护的 React 应用常面临诸多挑战,包括类型安全性缺失、项目膨胀带来的维护难题、不可靠的属性验证以及脆弱的 DOM 操作等。虽然普通 JavaScript 能解决大部分问题,但它缺乏为代码库提供长期保障的安全机制。这正是 TypeScript 的价值所在——它能以一致且可扩展的方式解决这些反复出现的问题。

本文将探讨若干经过验证的模式,帮助您在 React 和 TypeScript 中编写更安全、更清晰且更易读的代码。

TypeScript 在 React 中的优势

TypeScript 为 React 应用带来多重优势,既能提升代码质量,又能提高开发效率:

  • 可维护性:使代码更具可读性和自解释性,助力团队高效管理和扩展项目
  • 早期错误检测:在编译阶段识别错误,让开发者能在问题影响终端用户前及时修复
  • 更佳工具支持:提供卓越的 IDE 支持,包括自动补全、重构和代码导航等功能,优化开发体验
  • 类型安全:在开发过程中捕获类型相关错误,减少运行时错误,提升代码可靠性
  • 重构信心:通过即时标记错误的类型使用,确保代码变更更安全

类型化组件属性与默认属性

在 TypeScript 中,接口非常适合描述组件属性,特别是在需要多处扩展或实现时。以下展示如何通过接口声明和使用属性:

import Reactfrom'react';

interfaceMyEmployeeProps {
namestring;
agenumber;
isEmployed?: boolean// 可选属性
}

constMyEmployeeReact.FC<MyEmployeeProps> = ({ name, age, isEmployed }) => {
return (
    <div>
      <p>姓名: {name}</p>
      <p>年龄: {age}</p>
      {isEmployed !== undefined && <p>雇佣状态: {isEmployed ? '是' : '否'}</p>}
    </div>

  );
};

当需要组合联合类型或交叉类型时,可用 type 替代 interface,但出于可扩展性考虑,通常更推荐使用 interface

import Reactfrom'react';

typeSubmitButtonProps = {
textstring;
onClick() =>void;
variant?: 'primary' | 'secondary'// 联合类型
};

constUserButtonReact.FC<SubmitButtonProps> = ({ text, onClick, variant }) => {
return (
    <button
      onClick={onClick}
      className={variant === 'primary' ? 'primary-button: 'secondary-button'}
    >

      {text}
    </button>

  );
};

在 TypeScript 与 React 结合使用时,组件属性默认视为必填,除非添加 ? 标记为可选。无论使用接口还是类型别名描述属性,此规则均适用。

必填属性示例

interface MyEmployeeProps {
  requiredFullNamestring;
  requiredAgenumber;
}

const MyEmployeeReact.FC<MyEmployeeProps> = ({ requiredFullName, requiredAge }) => {
  return (
    <div>
      {requiredFullName} {requiredAge}
    </div>

  );
};

可选属性示例

interface MyEmployeeProps {
  requiredFullNamestring;
  optionalAge?: number;
}
const MyEmployeeReact.FC<MyEmployeeProps> = ({ requiredFullName, optionalAge }) => {
  return (
    <div>
      {requiredFullName} {optionalAge}
    </div>

  );
};

默认属性与函数组件参数默认值

// 类组件
classUserComponentextendsReact.Component<UserProps> {
render(){
    return (
      <div style={{ color: this.props.colorfontSize: this.props.fontSize}}>
        {this.props.title}
      </div>

    );
  }
}

UserComponent.defaultProps = {
color'blue'
fontSize20,
};

// 函数组件
constUserFunctionalComponentReact.FC<UserProps> = ({
  title,
  color = "blue",
  fontSize = 20 
}
) =>
 {
return<div style={{ color: colorfontSize: fontSize }}>{title}</div>;
};

通过类组件的 defaultProps 属性,您可以为属性设置默认值,确保即使某些属性未提供时组件行为仍可预测。而在函数组件中,只需直接在函数参数中为可选属性分配默认值即可。这种方式不仅使代码更简洁,还能有效防止因缺失属性导致的运行时错误。

处理子元素

interface UserComponentProps {
  titlestring;
  childrenReact.ReactNode;
}
const UserComponentReact.FC<UserComponentProps> = ({ title, children }) => {
  return (
    <div>
      <h1>{title}</h1>
      {children}
    </div>

  );
};

如上所示,children 属性允许您传递文本、其他组件甚至多个元素等广泛数据类型的内容,使组件通过"包裹"或显示您放入其中的任何内容而变得更灵活和可复用。

使用可辨识联合进行条件渲染

什么是可辨识联合?何时使用?

当您使用 TypeScript 和 React 构建应用时,经常需要处理可能处于不同状态的单一数据:加载中、错误或成功。可辨识联合(有时称为标记联合或代数数据类型)为建模这些不同形式提供了整洁的方式。通过将相关类型分组到一个标签下,您可以在保持类型安全的同时减轻编码时的思维负担。

这种清晰的分离使得在组件中决定显示哪个 UI 变得简单,因为每个状态都带有自己的特征。在以下示例中,我们将看到这种方法如何帮助我们编写更安全、更可读且仍具表现力的代码:

type DataLoadingState = {
status'request loading...';
};

typeDataSuccessState<T> = {
status'request success';
data: T;
};

typeDataErrorState = {
status'request error';
messagestring;
};

typeDataState<T> = DataLoadingState | DataSuccessState<T> | DataErrorState;

从上述代码片段可见,每种类型都有一个共同特征(通常称为判别器或标记)来标识其种类,类似于状态标签。当这些形状被合并为联合类型时,TypeScript 依赖此标记来区分它们。由于每种形状对该特征都有不同的固定值,语言能准确知道当前是哪种类型并相应缩小类型范围。一旦定义了这些形状,您就可以用 | 操作符将它们捆绑在一起,从而以保持安全且可预测的方式对复杂状态进行建模。

使用 never 类型进行穷尽检查

TypeScript 中通过 never 类型进行穷尽检查是一种技术,可确保在 switch 语句或条件逻辑中显式处理可辨识联合的所有可能情况,使开发者能通过类型安全在编译时捕获未处理的场景。

值得注意的是,never 类型表示永远不会出现的值(即不可达代码),用于穷尽检查以确保正确处理可辨识联合的所有情况。如果添加了新情况但未处理,编译器将抛出错误,从而增强类型安全:

function DisplayData<T>({ state }: { stateDataState<T> }) {
switch (state.status) {
    case'loading':
      return<p>数据加载中</p>;
    case'success':
      return<p>数据: {JSON.stringify(state.data)}</p>;
    case'error':
      return<p>错误: {state.message}</p>;
    default:
      return<p>未知状态</p>;
  }
}

上述代码展示了在 React 组件中有效使用可辨识联合的最后一步——基于判别属性(status)使用 switch  if 语句等条件逻辑。这将允许您根据当前状态渲染不同的 UI 元素,并在编译时捕获缺失的分支,保持组件既类型安全又抗错误。

使用 ReturnType  typeof 从 API 推断类型

TypeScript 提供了两个强大的实用工具:typeof  ReturnType<T>,分别用于从现有值推断类型和提取函数的返回类型,特别是在处理服务、API 和实用函数时,能实现更安全且更易维护的代码。

使用 typeof 从函数或常量推断类型

对于常量,typeof 用于推断变量(字符串)的类型,使其可复用而无需硬编码,如下所示:

const API_BASE_URL = 'https://api.newpayment.com/services/api/v1/transfer';
type ApiBaseUrlType = typeof API_BASE_URL;

您也可以使用 typeof 获取函数类型,这对类型化回调很有用:

const getEmployeeDetails = (employeeIdnumber) => ({
  employeeId,
  employeeName'Peter Aideloje',
  employeeEmail'aidelojepeter123@gmail.com',
  position'Software Engineer',
});

// 使用 typeof 获取函数类型
type GetEmployeeDetailsFnType = typeof getEmployeeDetails;

利用 ReturnType<T> 获取函数结果

当实用/服务函数返回结构化数据时,此模式非常有用。通过 ReturnType 自动派生结果类型,确保代码库中的一致性。结合 ReturnType  typeof,可使类型与函数签名保持同步,避免手动重复并降低类型不匹配的风险:

// 获取 getUser 函数的返回类型
const employeeDetailsEmployeeDetails = {
  employeeId = 3,
  employeeName'Peter Aideloje',
  employeeEmail'aidelojepeter123@gmail.com',
  position'Software Engineer',
};

type EmployeeDetails = ReturnType<typeof getEmployeeDetails>;

从服务和实用函数提取类型

这有助于从实用或服务函数的结构化数据中自动派生结果类型,从而确保消费组件的一致性,如下所示:

// 实用函数
functioncalculateTotalFee(pricenumberquantitynumber) {
return {
    total: price * quantity,
    currency'GBP',
  };
}

// 提取实用函数的返回类型
typeTotalSummary = ReturnType<typeof calculateTotalFee>;

constsummaryTotalSummary = {
total100,
currency'GBP',
};

实用类型:PickOmitPartialRecord

TypeScript 提供了一组内置实用类型,可灵活地从已定义的类型构建新类型。这些工具能帮助塑造组件属性、组织状态、减少冗余并提升 React 项目的代码可维护性。以下是 React + TypeScript 设置中最常用实用类型的实际用例。

各实用类型的实际用例

  1. Pick<Type, Keys>

Pick 实用类型通过从大型 Type 中选择特定属性来构造新类型,从而增强类型安全并减少冗余:

interface Employee {
employeeIdnumber;
employeeNameString;
employeeEmailString;
employeePositionString;
}
typeEmployeePreview = Pick<Employee'employeeId' | 'employeeName'>;
constpreviewEmployeepreview = {
employeeId35,
employeeName'Peter Aideloje',
};

这非常适合在列表或组件中显示最小数据量。

  1. Omit<Type, Keys>

Omit 实用类型与 Pick 直接相反,用于通过排除现有类型中的特定属性来创建新类型:

interface Employee {
employeeIdnumber;
employeeNameString;
employeeEmailString;
employeePositionString;
}

typeEmployeeWithoutEmail = Omit<Employee'employeeEmail'>;
constemployeeEmployeeWithoutEmail = {
employeeId35,
employeeName'Peter Aideloje',
employeePosition'Software Engineer',
};

这非常适合排除不必要的信息或敏感字段,如密码、电子邮件或数据库 ID。

  1. Partial<Type>

Partial 实用类型使类型中的所有属性变为可选。这在更新对象且不需要提供所有属性时非常有用:

interface Employee {
employeeIdnumber;
employeeNameString;
employeeEmailString;
employeePositionString;
}

typePartialEmployee = Partial<Employee>;
constpartialEmployeePartialEmployee = {
employeeName'Peter Aideloje',
};
  1. Record<Keys, Type>

Record 实用类型创建具有特定键集和类型的对象:

type Roles = "admin" | "employee" | "viewer";

type Permissions = Record<Rolestring[]>;

const permissionsPermissions = {
    admin["read""write""delete"],
    employee["read""write"],
    viewer["read"],
};

TypeScript 中的实用类型通过重用和重塑现有类型,在定义属性或状态时有助于减少代码重复。它们也非常适合建模灵活的数据结构,如动态表单输入或 API 响应,使代码库更清晰且更易于维护。

泛型组件与钩子

使用泛型编写可复用组件

TypeScript 中的泛型帮助开发者创建可管理多种数据类型的可复用 UI 元素,同时保持强大的类型安全。在 React 中设计不绑定特定数据类型的组件时,它们表现更出色且更重要。这种灵活性使您的 React 组件更具动态性,并能适应应用程序任何部分所需的各种类型。要实现这一点,请按照以下步骤设置您的项目:

首先,打开终端或命令提示符运行命令以使用 TypeScript 创建新的 React 项目:

npx create-react-app react-project --template typescript

接下来,此命令将导航到项目目录:

cd react-project

文件夹结构

接下来,我们将创建一个通用的 List 组件,可以使用以下代码片段展示任何类型的项目列表:

import Reactfrom'react';

// 泛型组件
typeProps<T> = {
items: T[];
renderItem(item: T) =>React.ReactNode;
};

functionGenericComponent<T>({ items, renderItem }: Props<T>): JSX.Element {
return<div>{items.map(renderItem)}</div>;
}

exportdefaultGenericComponent;

GenericComponent 在 React + TypeScript 设置中定义了一个可复用的泛型列表组件。它接受两个属性:一个项目数组和一个 renderItem 函数,该函数决定如何显示每个项目。泛型的使用使该组件能够处理任何数据类型,使其成为跨多种用例渲染列表的更灵活且类型安全的解决方案。

类型化引用和 DOM 元素

  1.  useRef 与 DOM 元素结合使用

在 React 开发中,有必要利用库提供的 useRef 等内置工具。当将 useRef  HTMLInputElement 等 DOM 元素结合使用时,您需要如下指定引用:

import React, { useRef, useEffect } from'react';
constFocusInputReact.FC = () => {
const nameInputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
    nameInputRef.current?.focus();
  }, []);
return (
    <div>
      <label htmlFor='name'>姓名:</label>
      <input id='name' type='text' ref={nameInputRef} />
    </div>

  );
};

exportdefaultFocusInput;
  1. 使用 React.forwardRef 转发引用

在 React 中,forwardRef 是一个方便的功能,允许您将引用从父组件传递到子组件。当子组件包装了 DOM 元素但不直接暴露它时,这非常有用。本质上,React.forwardRef 允许父组件直接访问内部 DOM 节点(子组件的 DOM),即使它被隐藏或包装在其他抽象层中。在使用 TypeScript 时,您需要定义引用的类型以保持安全性和可预测性。这是使组件更灵活且更易维护的好方法:

import React, { forwardRef, useRef, useImperativeHandle } from'react';

typeButtonProps = {
handleClick?: () =>void;
};

constCustomerButton = forwardRef<HTMLButtonElementButtonProps>((props, ref) => {
const internalRef = useRef<HTMLButtonElement>(null);

useImperativeHandle(ref, () => ({
    focus() => {
      internalRef.current?.focus();
    },
  }));

return (
    <button ref={internalRef} onClick={props.hanldeClick}>
      点击这里
    </button>

  );
});

constWrapperComponent = () => {
const refToButton = useRef<HTMLButtonElement>(null);

consttriggerFocus = () => {
    refToButton.current?.focus();
  };
return (
    <div>
      <customButton ref={refToButton} handleClick={triggerFocus} />
    </div>

  );
};

exportdefaultWrapperComponent;
  1. 避免任何 DOM 操作

在 React 中,尽量避免直接修改 DOM。相反,采用更可靠且可维护的方法,使用 React 的内置状态系统来管理变更。例如,与其使用引用来手动设置输入字段的值,不如让 React 通过状态控制它。这使您的组件更可预测且更易于调试:

import React, { useState, useRef, useEffect } from'react';

functionControlledInput() {
const [inputValue, setInputValue] = useState('');
const inputRef = useRef<HTMLInputElement>(null);

consthandleInputChange = (eventReact.ChangeEvent<HTMLInputElement>) => {
    setInputValue(event.target.value);
  };

useEffect(() => {
    if (inputRef.current) {
      //安全访问属性
      console.log(inputRef.current.value);
      // 不要直接操作 DOM,改用 React 状态
    }
  }, [inputValue]);
return<input type='text' ref={inputRef} value={inputValue} onChange={handleInputChange} />;
}

强类型化的 Context

使用泛型类型创建和消费 Context

当您使用 React 和 TypeScript 构建应用时,createContext 方法允许您将主题偏好或登录用户详情等内容传递到远距离组件,而无需通过每一层传递属性。为了保持此过程类型安全且易于管理,首先编写一个 TypeScript 类型或接口,明确列出 Context 将保存的每项数据。这样做能让编译器及早标记错误,并在导入 Context 的任何地方保持其形状一致。

定义好类型后,向 React.createContext 传递合理的默认值并将该值作为参数提供。默认值确保任何在 Provider 外部读取 Context 的组件都能获得安全回退,而非导致应用崩溃。React 16 引入的 Context API 已成为以更清晰、更可扩展的方式全局共享状态的首选方法。下面,我们将通过三个简单步骤创建 Context、提供它,然后在组件中消费它。

  1. 用接口定义 Context
interface AppContextType{
  currentValuestring;
  updateValue(updatedstring) => void;
}

创建 Context

import React from 'react';

const AppContext = React.createContext<AppContextType>({
  currentValue'default',
  updateValue() => {}, //临时函数占位符
});
  1. 消费 Context
import React, { useContext } from'react';
import { AppContext } from'./AppContextProvider'//假设 Context 定义在单独文件中

functioninfoDisplay() {
const { currentValue, updateValue } = useContext(AppContext);

return (
    <section>
      <p>当前 Context: {currentValue}</p>
      <button onClick={() => updateValue('updateContext')}>更改值</button>
    </section>

  );
}

 createContext 与默认值和未定义检查结合使用

在 React + TypeScript 设置中使用 createContext 时,必须注意定义默认值并处理 Context 可能为 undefined 的情况。这将帮助您确保应用保持安全、可预测且不易出现运行时错误。

  1. createContext 中的默认值

在 React 中调用 createContext 时,您可以传递默认值作为参数。当读取 Context 的组件不在正确的 Provider 内,或 Provider 本身将值设为 undefined 时,useContext 会返回该值:

interface IThemeContext {
  theme'light' | 'dark';
  switchTheme() => void;
}

const ThemeContext = React.createContext<IThemeContext | null>(null);
  1. 使用 useContext 处理未定义

当您用 React 的 useContext Hook 拉取数据但忘记将组件包装在匹配的 Provider 中,或该 Provider 意外发送 undefined 时,Hook 只会返回 undefined。为了让 TypeScript 满意并为应用提供防止隐蔽运行时错误的安全网,在读取 Context 后始终添加快速检查。这样,当 Context 缺失时,您的组件能冷静应对而非崩溃:

import { createContext, useContext } from'react';

interfaceContextShape {
datastring;
}

const customContext = createContext<ContextShape | undefined>(undefined);

exportfunctionuseCustomContext() {
const ctx = useContext(CustomContext);
if (!ctx) {
    thrownewError('useCustomContext 必须在 customProvider 内使用');
  }
return ctx;
}

exportfunctionCustomProvider({ children }: { children: React.ReactNode }) {
constcontextValue: contextShape = { data'共享 Context 数据' };
return<CustomContext.Provider value={contextValue}>{children}</CustomContext.Provider>;
}

结论

我们已经看到 TypeScript 在现代 React 开发中发挥的关键作用,它帮助团队构建更具可扩展性、健壮性和可维护性的应用,同时提高代码可读性。开发者可以使用 typeofReturnType等特性从 API 推断类型,从而减少手动重复并保持类型与实际实现同步。此外,当您在代码库中启用类型化组件属性和默认属性时,可以及早捕获误用并提高代码可读性,如本文所示。

TypeScript 在处理类型化引用和 DOM 元素等底层关注点,以及在 React Context 中实现强类型化以使消费组件更清晰安全方面也表现出色。

如果您不熟悉这些模式,不必急于一次性全部采用。在 React 中采用 TypeScript 不必令人望而生畏;您可以从在能立即带来价值的地方小规模引入开始,然后逐步扩展。随着时间的推移,这些实践将成为第二天性,并在可维护性、代码质量和投资回报方面带来长期收益。

编码愉快!


· · ·

原文地址:https://blog.logrocket.com/react-typescript-10-patterns-writing-better-code/
作者:Peter Aideloje


该文章在 2025/7/21 11:24:24 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved