@axa-ch/react-polymorphic-types

中文文档
1.4.2发布于 2026/05/21MITTypeScriptES Module零依赖近 30 天下载 4014

关于反应的零运行时多态组件定义

中文使用文档
文档目录7 节
  1. 关于
  2. 解释
  3. 什么是反应多态成分?
  4. 这个包能解决什么问题?
  5. 安装和使用
  6. 多态构件
  7. 贷项

反应-多态类型

Build Status MIT License NPM version NPM downloads

关于

反应-多态类型 是允许在React中创建零运行时多态组件定义的库.

解释

什么是反应多态成分?

在 React 中构建设计系统或重新使用 UI 组件时,您可能会发现需要 多态组件。一个多态组件是一个多能组件,可以使基于道具的不同的 HTML 基本元素或自定义组件.

反应多态构件为消费者提供了灵活性,使他们能够指定使用道具制造的所希望的元素或构件类型.

例如,让我们考虑一个多形态标题组件:

import { createElement, ElementType, PropsWithChildren, ComponentProps } from 'react';

// Define the props for the polymorphic heading component
type HeadingProps<T extends ElementType = 'h1'> = PropsWithChildren<
  {
    as?: T;
  } & ComponentProps<T>
>;

// Define the polymorphic heading component
export const Heading = <T extends ElementType = 'h1'>({ as = 'h1', children, ...rest }: HeadingProps<T>) =>
  createElement(as, rest, children);

在上述例子中 Heading 组件可以使不同的标题级别(h1, (中文(简体) ), h2, (中文(简体) ), h3等)),基于: as 道具 (英语). 默认情况下,它使 h1 键.

你可以用这个 Heading 应用程序中的组件 :

const App = () => (
  <article>
    <Heading>My Main Headline</Heading>
    <Heading as='h2'>A Subtitle</Heading>
    <p>A description</p>
  </article>
);

在这种情况下,同样的 Heading 组件用于制取两个不同的语义标记, h1 财务报告和审定财务报表 h2,允许您控制标题级别并保持应用程序的一致性.

多态构件为React中构建可弹性和可再使用的UI构件提供了优雅的解决方案,使您能够创建一个具有一致语义的一致设计系统.

这个包能解决什么问题?

使用 as 属性在给您提供的标记添加限制时或使用第三方组件时会变得复杂。 宣告每个组件的多态类型也可以是您想要抽象的乏味的任务.

与 @axa-ch/react-polymorphic-types,可以轻松地在多态反应组件中添加约束并避免冗余类型定义.

安装和使用

通过 npm 安装类型脚本 :

npm i @axa-ch/react-polymorphic-types -D

多态构件

以下配方为创建多形态成分提供了一个起点. 您可按规定复制并修改.

基本示例

这个例子展示了一个简单的多态标题元素. 它允许您独立定义其大小并使用道具进行标记.

import { ComponentPropsWithoutRef, createElement, ElementType } from 'react';
import { PolymorphicProps } from '@axa-ch/react-polymorphic-types';

// Default HTML element if the "as" prop is not provided
export const HeadingDefaultElement: ElementType = 'h1';
// List of allowed HTML elements that can be passed via the "as" prop
export type HeadingAllowedElements = typeof HeadingDefaultElement | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
export type HeadingSizes = 1 | 2 | 3 | 4 | 5 | 6;

// Component-specific props
export type HeadingOwnProps<T extends HeadingAllowedElements> = ComponentPropsWithoutRef<T> & {
  size?: HeadingSizes;
};

// Extend own props with others inherited from the underlying element type
// Own props take precedence over the inherited ones
export type HeadingProps<T extends HeadingAllowedElements = typeof HeadingDefaultElement> = PolymorphicProps<
  HeadingOwnProps<T>,
  T,
  HeadingAllowedElements
>;

export const Heading = <T extends HeadingAllowedElements>({
  as = HeadingDefaultElement,
  size,
  className,
  children,
  ...rest
}: HeadingProps<T>) =>
  createElement(
    as,
    {
      ...rest,
      className: `${className} size-${size || 1}`,
    },
    children,
  );

你可以用这个 Heading 应用程序中的组件如下:

const App = () => (
  <article>
    <Heading
      as='h1'
      size={2}
    >
      My Main Headline
    </Heading>
    <Heading
      as='h2'
      size={5}
    >
      A Subtitle
    </Heading>

    {/* The following component will throw a TypeScript error because 'div' elements are not allowed here */}
    <Heading
      as='div'
      size={5}
    >
      A Subtitle
    </Heading>
    <p>A description</p>
  </article>
);
参考文献的基本示例

这个例子与前作相似,但也允许使用React refs.

import { ComponentPropsWithoutRef, createElement, ElementType, forwardRef } from 'react';
import { PolymorphicProps, PolymorphicForwardedRef } from '@axa-ch/react-polymorphic-types';

// Default HTML element if the "as" prop is not provided
export const HeadingDefaultElement: ElementType = 'h1';
// List of allowed HTML elements that can be passed via the "as" prop
export type HeadingAllowedElements = typeof HeadingDefaultElement | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
export type HeadingSizes = 1 | 2 | 3 | 4 | 5 | 6;

// Component-specific props
export type HeadingOwnProps<T extends HeadingAllowedElements> = ComponentPropsWithoutRef<T> & {
  size?: HeadingSizes;
};

// Extend own props with others inherited from the underlying element type
// Own props take precedence over the inherited ones
export type HeadingProps<T extends HeadingAllowedElements> = PolymorphicProps<
  HeadingOwnProps<T>,
  T,
  HeadingAllowedElements
>;

const HeadingInner = <T extends HeadingAllowedElements>(
  { as = HeadingDefaultElement, size, className, children, ...rest }: HeaadingProps<T>,
  // notice the use of the PolymorphicForwardedRef type here
  ref: PolymorphicForwardedRef<T>,
) =>
  createElement(
    as,
    {
      ...rest,
      ref,
      className: `${className} size-${size || 1}`,
    },
    children,
  );

// Forward refs with generics is tricky
// see also https://fettblog.eu/typescript-react-generic-forward-refs/
export const Heading = forwardRef(HeadingInner) as unknown as <T extends HeadingAllowedElements>(
  props: HeadingProps<T> & { ref?: PolymorphicForwardedRef<T> },
) => ReturnType<typeof HeadingInner>;

使用 @axa-ch/react-polymorphic-types 类型可以自动推断合适的 DOM 节点.

const App = () => {
  // The use of HTMLHeadingElement type is safe
  const ref = useRef<HTMLHeadingElement | null>(null);

  return (
    <Heading
      ref={ref}
      as='h2'
    />
  );
};
与 Ref 一起反应 19 示例

这个例子与前一个相似,但与React 19 the 调试员转发起来容易多了

import { ComponentPropsWithoutRef, createElement, ElementType, forwardRef } from 'react';
import { PolymorphicProps, PolymorphicForwardedRef } from '@axa-ch/react-polymorphic-types';

// Default HTML element if the "as" prop is not provided
export const HeadingDefaultElement: ElementType = 'h1';
// List of allowed HTML elements that can be passed via the "as" prop
export type HeadingAllowedElements = typeof HeadingDefaultElement | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
export type HeadingSizes = 1 | 2 | 3 | 4 | 5 | 6;

// Component-specific props
export type HeadingOwnProps<T extends HeadingAllowedElements> = ComponentPropsWithoutRef<T> & {
  size?: HeadingSizes;
  ref?: PolymorphicForwardedRef<T>;
};

// Extend own props with others inherited from the underlying element type
// Own props take precedence over the inherited ones
export type HeadingProps<T extends HeadingAllowedElements> = PolymorphicProps<
  HeadingOwnProps<T>,
  T,
  HeadingAllowedElements
>;

const Heading = <T extends HeadingAllowedElements>({
  as = HeadingDefaultElement,
  size,
  className,
  ref,
  children,
  ...rest
}: HeadingProps<T>) =>
  createElement(
    as,
    {
      ...rest,
      ref,
      className: `${className} size-${size || 1}`,
    },
    children,
  );

使用 @axa-ch/react-polymorphic-types 类型可以自动推断合适的 DOM 节点.

const App = () => {
  // The use of HTMLHeadingElement type is safe
  const ref = useRef<HTMLHeadingElement | null>(null);

  return (
    <Heading
      ref={ref}
      as='h2'
    />
  );
};
带有备忘录的基本示例

这个例子显示了使用React.memo的多态成分.

import { ComponentPropsWithoutRef, createElement, ElementType, memo } from 'react';
import { PolymorphicProps } from '@axa-ch/react-polymorphic-types';

// Default HTML element if the "as" prop is not provided
export const HeadingDefaultElement: ElementType = 'h1';
// List of allowed HTML elements that can be passed via the "as" prop
export type HeadingAllowedElements = typeof HeadingDefaultElement | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
export type HeadingSizes = 1 | 2 | 3 | 4 | 5 | 6;

// Component-specific props
export type HeadingOwnProps<T extends HeadingAllowedElements> = ComponentPropsWithoutRef<T> & {
  size?: HeadingSizes;
};

// Extend own props with others inherited from the underlying element type
// Own props take precedence over the inherited ones
export type HeadingProps<T extends HeadingAllowedElements> = PolymorphicProps<
  HeadingOwnProps<T>,
  T,
  HeadingAllowedElements
>;

const HeadingInner = <T extends HeadingAllowedElements>({
  as = HeadingDefaultElement,
  size,
  className,
  children,
  ...rest
}: HeadingProps<T>) =>
  createElement(
    as,
    {
      ...rest,
      className: `${className} size-${size || 1}`,
    },
    children,
  );

// Memo with generics is tricky
// see also https://fettblog.eu/typescript-react-generic-forward-refs/
export const Heading = memo(HeadingInner) as <T extends HeadingAllowedElements>(
  props: HeadingProps<T>,
) => ReturnType<typeof HeadingInner>;

上述部分可在不增加间接费用的情况下消耗如下:

const App = () => (
  <>
    <Heading as='h2' />
  </>
);
Exotic 组件示例

多态异域组件允许您为 HTML 使用 DOM 节点或自定义渲染功能 .

import { ComponentPropsWithoutRef, createElement, ElementType, ExoticComponent } from 'react';
import { PolymorphicExoticProps, PolymorphicProps } from '@axa-ch/react-polymorphic-types';

// Default HTML element if the "as" prop is not provided
export const ContainerDefaultElement: ElementType = 'div';
// List of allowed HTML elements that can be passed via the "as" prop
export type ContainerAllowedDOMElements = typeof ContainerDefaultElement | 'article' | 'section';
export type ContainerAllowedElements = ContainerAllowedDOMElements | ExoticComponent;

// Component-specific props
export type ContainerOwnProps<T extends ContainerAllowedDOMElements> = ComponentPropsWithoutRef<T>;

// Extend own props with others inherited from the underlying element type
// Own props take precedence over the inherited ones
export type ContainerProps<T extends ContainerAllowedElements> = T extends ContainerAllowedDOMElements
  ? PolymorphicProps<ContainerOwnProps<T>, T, ContainerAllowedDOMElements>
  : PolymorphicExoticProps<ContainerOwnProps<ContainerAllowedDOMElements>, T, ContainerAllowedDOMElements>;

export const Container = <T extends ContainerAllowedElements>({
  as = ContainerDefaultElement,
  className,
  children,
  ...rest
}: ContainerProps<T>) =>
  createElement(
    as,
    {
      ...rest,
      className,
    },
    children,
  );

以上组件与直通HTML节点或外部异域组件相配合,例如由HTML节点提供的组件 帧移动。 。 。 .

import { motion } from 'framer-motion';

const App = () => (
  <>
    <Container as='div' />
    {/* Notice that the exotic props here will be automatically inferred */}
    <Container
      as={motion.article}
      layout
    />
  </>
);
带有参考文献的 Exotic 组件示例

使用 refs 的多态异域组件略为复杂,需要一些额外的代码才能正常工作.

import { ComponentPropsWithoutRef, createElement, ElementType, ExoticComponent, forwardRef } from 'react';
import { PolymorphicProps, PolymorphicForwardedRef, PolymorphicExoticProps } from '@axa-ch/react-polymorphic-types';

// Default HTML element if the "as" prop is not provided
export const ContainerDefaultElement: ElementType = 'div';
// List of allowed HTML elements that can be passed via the "as" prop
export type ContainerAllowedDOMElements = 'div' | 'article' | 'section';
export type ContainerAllowedElements = ContainerAllowedDOMElements | ExoticComponent;

// Component-specific props
export type ContainerOwnProps<T extends ContainerAllowedDOMElements> = ComponentPropsWithoutRef<T>;

// Extend own props with others inherited from the underlying element type
// Own props take precedence over the inherited ones
export type ContainerProps<T extends ContainerAllowedElements> = T extends ContainerAllowedDOMElements
  ? PolymorphicProps<ContainerOwnProps<T>, T, ContainerAllowedDOMElements>
  : PolymorphicExoticProps<ContainerOwnProps<ContainerAllowedDOMElements>, T, ContainerAllowedDOMElements>;

// Forwarded ref component
const ContainerInner = <T extends ContainerAllowedElements>(
  { as = ContainerDefaultElement, className, children, ...rest }: ContainerProps<T>,
  ref: PolymorphicForwardedRef<T>,
) =>
  createElement(
    as,
    {
      ...rest,
      ref,
      className,
    },
    children,
  );

// Forward refs with generics is tricky
// see also https://fettblog.eu/typescript-react-generic-forward-refs/
export const Container = forwardRef<ContainerAllowedElements>(ContainerInner) as <T extends ContainerAllowedElements>(
  props: ContainerProps<T> & { ref?: PolymorphicForwardedRef<T> },
) => ReturnType<typeof ContainerInner>;

根据上述例子,DOM节点将自动被推断出,包括在使用第三方异地渲染功能时.

import { motion } from 'framer-motion';

const App = () => {
  const div = useRef<HTMLDivElement | null>(null);
  // Article and other HTML5 tags are just of type HTMLElement
  const article = useRef<HTMLElement | null>(null);

  return (
    <>
      <Container
        ref={div}
        as='div'
      />
      <Container
        ref={article}
        as={motion.article}
        layout
      />
    </>
  );
};
复杂的外观/功能组件示例

这个例子结合了您组件的多渲染策略,使其消费者有最大的灵活性.

// We need to infer the functional component properties so 'any' is used in this case
// You can also add strict types for your functional components, but it will reduce flexibility
import { ComponentPropsWithoutRef, createElement, ElementType, ExoticComponent, FC } from 'react';
import { PolymorphicFunctionalProps, PolymorphicExoticProps, PolymorphicProps } from '@axa-ch/react-polymorphic-types';

// Default HTML element if the "as" prop is not provided
export const ContainerDefaultElement: ElementType = 'div';
// List of allowed HTML elements that can be passed via the "as" prop
export type ContainerAllowedDOMElements = 'div' | 'article' | 'section';
export type ContainerAllowedElements = ContainerAllowedDOMElements | ExoticComponent | FC<any>;

// Component-specific props
export type ContainerOwnProps<T extends ContainerAllowedDOMElements> = ComponentPropsWithoutRef<T>;

// Extend own props with others inherited from the underlying element type
// Own props take precedence over the inherited ones
export type ContainerProps<T extends ContainerAllowedElements> = T extends ContainerAllowedDOMElements
  ? PolymorphicProps<ContainerOwnProps<T>, T, ContainerAllowedDOMElements>
  : T extends FC<any>
    ? PolymorphicFunctionalProps<ContainerOwnProps<ContainerAllowedDOMElements>, T, ContainerAllowedDOMElements>
    : PolymorphicExoticProps<ContainerOwnProps<ContainerAllowedDOMElements>, T, ContainerAllowedDOMElements>;

export const Container = <T extends ContainerAllowedElements>({
  as = ContainerDefaultElement,
  className,
  children,
  ...rest
}: ContainerProps<T>) =>
  createElement(
    as,
    {
      ...rest,
      className,
    },
    children,
  );

让我们看看如何使用上面的组件 及其所有可能的渲染选项:

import { motion } from 'framer-motion';

type FooProps = ComponentPropsWithoutRef<'div'> & { size: 'small' | 'large'; name: string };

const Foo: FC<FooProps> = ({ className, size = 'large', ...rest }) => (
  <div
    {...rest}
    className={`${className} the-foo ${size}`}
  />
);

const App = () => (
  <>
    <Container as='div' />
    <Container
      size='small'
      name='foo'
      as={Foo}
    />
    <Container
      as={motion.div}
      layout
      animate
    />
  </>
);

贷项

没有这个工程就不会存在 反应-多态类型

版本与发布记录

@axa-ch/react-polymorphic-types 共发布 16 个版本,首次发布于 2023/06/14;过去 12 个月发布了 1 个版本。

版本发布日期距今
1.4.2latest2026/05/215 个月前
1.4.12025/08/201.2 年前
1.4.02025/05/201.5 年前
1.3.12025/05/201.5 年前
1.3.02025/01/071.8 年前
1.2.62024/10/102 年前

关于 @axa-ch/react-polymorphic-types 的常见问题

@axa-ch/react-polymorphic-types 是什么?

关于反应的零运行时多态组件定义

如何安装 @axa-ch/react-polymorphic-types?

在项目目录运行 npm install @axa-ch/react-polymorphic-types,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^18.0.0 || ^19.0.0)、@types/react(^18.0.0 || ^19.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@axa-ch/react-polymorphic-types 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@axa-ch/react-polymorphic-types 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@axa-ch/react-polymorphic-types 可以商用吗?

@axa-ch/react-polymorphic-types 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@axa-ch/react-polymorphic-types 的下载量有多少?

根据 npm 官方下载统计,@axa-ch/react-polymorphic-types 在 2026/10/08 – 2026/11/06 这 30 天内被下载 4,014 次,日均约 134 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@axa-ch/react-polymorphic-types 还在维护吗?

最新版本 1.4.2 发布于 2026/05/21(5 个月前);至今共发布 16 个版本,其中过去 12 个月发布了 1 个;首次发布于 2023/06/14。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。