TypeScript-React 高级模式:按使用场景分类的最佳实践

TypeScript-React 高级模式:按使用场景分类的最佳实践

react Cheatsheets for experienced React developers getting started with TypeScript react 项目地址: https://gitcode.com/gh_mirrors/rea/react

引言

在 TypeScript 与 React 的结合使用中,开发者经常会遇到一些特定的场景需要特殊的类型处理模式。本文将深入探讨几种常见的高级模式,帮助开发者更好地利用 TypeScript 的类型系统来增强 React 组件的类型安全性和灵活性。

包装/镜像 HTML 元素

场景描述

当你需要创建一个自定义组件(如 <Button>),它需要继承原生 HTML 元素(如 <button>)的所有属性,并添加一些额外的功能时。

解决方案

使用 React.ComponentPropsWithoutRef<'button'> 来扩展按钮属性:

// 使用示例
function App() {
  // 类型检查会阻止无效的 type 值
  // return <Button type="foo"> sldkj </Button> // 错误

  // 正确的用法
  return <Button type="button">文本</Button>;
}

// 实现
export interface ButtonProps extends React.ComponentPropsWithoutRef<"button"> {
  specialProp?: string; // 自定义属性
}

export function Button(props: ButtonProps) {
  const { specialProp, ...rest } = props;
  // 对 specialProp 进行特殊处理
  return <button {...rest} />;
}

为什么选择 ComponentPropsWithoutRef?

  1. 明确性:清楚地表明不转发 ref
  2. 简洁性:比直接使用 React.JSX.IntrinsicElementsReact.ButtonHTMLAttributes 更简洁
  3. 准确性:避免了 React.HTMLPropsReact.HTMLAttributes 可能导致的类型过宽问题

转发 Ref 的情况

如果需要将内部 DOM 节点的 ref 暴露给父组件,应该使用 ComponentPropsWithRef 并配合 forwardRef

import { forwardRef } from "react";

export const FancyButton = forwardRef<HTMLButtonElement, ButtonProps>(
  (props, ref) => (
    <button ref={ref} className="MyCustomButtonClass" {...props} />
  )
);

包装/镜像 React 组件

场景描述

当你需要包装一个现有的 React 组件,但无法直接访问其 props 类型定义时。

解决方案

通过类型推断提取组件的 props 类型:

// 工具类型定义
declare type $ElementProps<T> = T extends React.ComponentType<infer Props>
  ? Props extends object
    ? Props
    : never
  : never;

// 使用示例
const Box = (props: CSSProperties) => <div style={props} />;

const Card = ({
  title,
  children,
  ...props
}: { title: string } & $ElementProps<typeof Box>) => (
  <Box {...props}>
    {title}: {children}
  </Box>
);

多态组件(使用 as 属性)

场景描述

创建可以渲染为不同元素类型的组件,例如通过 as 属性指定渲染的目标组件。

解决方案

使用 React.ElementType 类型:

function PassThrough(props: { as: React.ElementType<any> }) {
  const { as: Component } = props;
  return <Component />;
}

// 实际应用示例
const PrivateRoute = ({ component: Component, ...rest }: PrivateRouteProps) => {
  const { isLoggedIn } = useAuth();
  return isLoggedIn ? <Component {...rest} /> : <Redirect to="/" />;
};

泛型组件

场景描述

创建可以处理多种数据类型的可复用组件,同时保持类型安全。

基本实现

interface Props<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

function List<T>(props: Props<T>) {
  const { items, renderItem } = props;
  const [state, setState] = useState<T[]>([]);
  
  return (
    <div>
      {items.map(renderItem)}
      <button onClick={() => setState(items)}>克隆</button>
      {JSON.stringify(state, null, 2)}
    </div>
  );
}

箭头函数形式

const List = <T extends unknown>(props: Props<T>) => {
  // 实现同上
};

类组件形式

class List<T> extends React.PureComponent<Props<T>, State<T>> {
  state: Readonly<State<T>> = { items: [] };
  
  render() {
    const { items, renderItem } = this.props;
    const clone: T[] = items.slice(0);
    
    return (
      <div>
        {items.map(renderItem)}
        <button onClick={() => this.setState({ items: clone })}>克隆</button>
        {JSON.stringify(this.state, null, 2)}
      </div>
    );
  }
}

根据 Props 进行类型收窄

场景描述

根据传入的 props 不同,组件需要渲染不同的元素类型并保持类型安全。

解决方案

使用类型守卫(Type Guards):

// 按钮 props
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
  href?: undefined;
};

// 锚点 props
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement> & {
  href: string;
};

// 类型守卫
const hasHref = (props: ButtonProps | AnchorProps): props is AnchorProps =>
  "href" in props;

// 组件实现
const Button = (props: ButtonProps | AnchorProps) => {
  if (hasHref(props)) return <a {...props} />;
  return <button {...props} />;
};

总结

本文介绍了 TypeScript 与 React 结合使用时的几种高级模式:

  1. 包装 HTML 元素和 React 组件的最佳实践
  2. 创建多态组件的类型安全方法
  3. 泛型组件在构建可复用组件时的应用
  4. 根据 props 动态确定组件行为的类型安全实现

这些模式能够帮助开发者在保持代码灵活性的同时,充分利用 TypeScript 的类型系统来提高代码的可靠性和可维护性。

react Cheatsheets for experienced React developers getting started with TypeScript react 项目地址: https://gitcode.com/gh_mirrors/rea/react

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宫萍润

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值