如何声明SVG组件的props类型?[React,TypeScript和Webpack]

h6my8fg2  于 2022-11-13  发布在  Webpack
关注(0)|答案(5)|浏览(185)

基本上,我想做的是导入一个SVG图标到我的react组件中,并添加一些道具。比如size="24px",使它作为一个组件更加灵活。或者通过添加className道具,使它可以用CSS编辑(所以我可以添加hover prop)。由于这是我第一次使用TypeScript和Webpack,我对如何声明SVG元素的类型感到困惑,并得到一个错误(如下所示)
由于包含SVG的方法很多,因此我决定将其作为ReactComponent导入。
menu-icon.svg

<svg width="24" height="24" viewBox="0 0 24 24">
  <path fill="currentColor" fillRule="evenodd" d="M4.5 5h15a.5.5 0 1 1 0 1h-15a.5.5 0 0 1 0-1zm0 6h15a.5.5 0 1 1 0 1h-15a.5.5 0 1 1 0-1zm0 6h15a.5.5 0 1 1 0 1h-15a.5.5 0 1 1 0-1z"></path>
</svg>

header.tsx(这里我想要我的svg图标)

import React from 'react';
import MenuIcon from '../assets/menu-icon.svg';

const Header: React.SFC = () => {
  return (
    <header className="c-header u-side-paddings">
      <MenuIcon className="c-header__icon" />  // <-- className prop doesn't match provided type
    </header>
  );
};

export default Header;

index.d.ts(因此.svg文件可以被视为一个组件)

declare module '*.svg' {
  import React = require('react');
  export const ReactComponent: React.SFC<React.SVGProps<SVGSVGElement>>;
  const src: string;
  export default src;
}

className属性添加到MenuIcon SVG组件会导致错误:

(JSX attribute) className: string
Type '{ className: string; }' is not assignable to type 'IntrinsicAttributes'.
  Property 'className' does not exist on type 'IntrinsicAttributes'.ts(2322)

我目前的理解是

  • 我可以将svg组件 Package 在一个div中,然后添加一个className,如下所示:<div className="c-header__icon"><MenuIcon/></div>,但我觉得这是一个不优雅的解决方案,不是一个真正的好做法
  • 我从this answer中了解到SVG属性不是字符串,因为它们是SVGAnimatedString对象。因此:
  • 我尝试创建.tsx文件而不是.svg文件(我不需要index.d.ts文件),但是它只在className的类型是string的情况下才起作用。而且我不确定将SVG图标存储在与.svg不同的扩展名的文件中是否是一个好的做法。在我看来,这对清晰度没有好处。如果我错了,告诉我什么是真正的好做法,下面是一个例子:
import React from 'react';
    
    interface MenuIcon {
      className?: SVGAnimatedString;
    }
    
    export class MenuIcon extends React.PureComponent<MenuIcon> {
      render() {
        return (
          <svg width="24" height="24" viewBox="0 0 24 24">
      <path fill="currentColor" fillRule="evenodd" d="M4.5 5h15a.5.5 0 1 1 0 1h-15a.5.5 0 0 1 0-1zm0 
 6h15a.5.5 0 1 1 0 1h-15a.5.5 0 1 1 0-1zm0 6h15a.5.5 0 1 1 0 1h-15a.5.5 0 1 1 0-1z"></path>
    </svg>
        );
      }
    }

我觉得我缺少一些基础知识,我真的很难弄清楚我应该关注什么,因为有几个主题组合在一起

7hiiyaii

7hiiyaii1#

我一直面临着这个问题,这个解决方案对我来说很有效:

declare module "*.svg" {
  import { ReactElement, SVGProps } from "react";
  const content: (props: SVGProps<SVGElement>) => ReactElement;
  export default content;
}

此外,我使用@svgr/webpack作为SVG加载器沿着使用Next.js

mum43rcc

mum43rcc2#

您可以参数化svgs以轻松实现JSX。通过全局抽象实现元素级定制。如何实现?使用React的FC。尝试实现以下内容:

  • 创建以下接口
interface SvgIconConstituentValues {
    strokeColor?: string;
    strokeWidth?: string;
    strokeWidth2?: string;
    strokeWidth3?: string;
    strokeFill?: string;
    fillColor?: string;
    fillColor2?: string;
    fillColor3?: string;
    fillColor4?: string;
    fillColor5?: string;
    fillColor6?: string;
    fillColor7?: string;
    imageWidth?: string;
    imageHeight?: string;
    width?: string;
    height?: string;
    rotateCenter?: number;
    className?: string;
    className2?: string;
    className3?: string;
    className4?: string;
    className5?: string;
}

export default SvgIconConstituentValues;
  • SvgIconConstituentValues导入tsx文件
  • { FC }从React导入同一tsx文件
import { FC } from 'react';
import SvgIconConstituentValues from 'types/svg-icons';

// FC can be parameterized via Abstraction
  • 创建扩展FCSvgIconConstituentValuesSvgIcon接口
export interface SvgIcon extends FC<SvgIconConstituentValues> {}
  • 通过使用SvgIcon的参数化来抽象SVG的属性,如下所示
export const ArIcon: SvgIcon = ({
    width = '8.0556vw',
    height = '8.0556vw',
    strokeColor = `stroke-current`,
    strokeWidth = '2',
    fillColor = 'none',
    fillColor2 = `fill-primary`,
    rotateCenter = 0,
    className = ` antialiased w-svgIcon max-w-svgIcon`,
    className2 = ` stroke-current`,
    className3 = ` fill-primary`
}): JSX.Element => {
    return (
        <svg
            width={width}
            height={height}
            viewBox='0 0 65 65'
            fill={fillColor}
            xmlns='http://www.w3.org/2000/svg'
            className={className}
            transform={`rotate(${rotateCenter}, 65, 65)`}
        >
            <circle
                cx='32.5'
                cy='32.5'
                r='31.5'
                stroke={strokeColor}
                strokeWidth={strokeWidth}
                className={className2}
            />
            <path
                d='M30.116 39H32.816L27.956 26.238H25.076L20.18 39H22.808L23.87 36.084H29.054L30.116 39ZM26.462 28.992L28.226 33.816H24.698L26.462 28.992ZM40.7482 39H43.5202L40.7842 33.78C42.4582 33.294 43.5022 31.944 43.5022 30.162C43.5022 27.948 41.9182 26.238 39.4342 26.238H34.4482V39H36.9502V34.086H38.2462L40.7482 39ZM36.9502 31.944V28.398H38.9662C40.2262 28.398 40.9642 29.1 40.9642 30.18C40.9642 31.224 40.2262 31.944 38.9662 31.944H36.9502Z'
                fill={fillColor2}
                className={className3}
            />
        </svg>
    );
};
  • 正如您所看到的,有三个单独的className参数(1、2、3)被抽象:(1)类名为<svg>...</svg>,属性为JSX.IntrinsicElements.svg: SVGProps<SVGSVGElement>;(2)className 2为<circle />属性JSX.IntrinsicElements.circle: SVGProps<SVGCircleElement>;(3)className 3为<path />的属性JSX.IntrinsicElements.path:SVGP属性。
  • 请注意,const ArIcon: SvgIcon = ({ ... }): JSX.Element => {...}实际上是一个JSX. Element。因此,<svg></svg>本身和任何子元素(圆圈、路径等)都是JSX.IntrinsicElements,每个子元素都允许有自己唯一的className。这些className调用被手动添加到svg中,就像transform调用一样(在其他地方旋转内联图标)。
  • JSX.IntrinsicElementsJSX Attribute类名定义如下
SVGAttributes<T>.className?: string | undefined
  • 每一个JSX.IntrinsicElement都有一个className属性。如果svg中有100个路径和一个圆圈,那么你就可以有102个可以通过抽象参数化的className。
  • 下面是最好的部分。以下是我的作品集中的一个文件,我修改了抽象的svg参数,使其可以很好地与黑暗模式切换(use-dark-mode)和依赖于屏幕宽度的图标渲染(@artsy/fresnel)配合使用。您可以全局导入此图标,并在每个JSX.Element中内联调用参数,而无需传递任何属性
import { ArIcon } from 'components/svg-icons';
import Link from 'next/link';
import { Media } from 'components/window-width';
import { Fragment } from 'react';
import DarkMode from 'components/lead-dark-mode';

const ArIconConditional = (): JSX.Element => {
    const arIconXs: JSX.Element = (
        <Media at='xs'>
            <Link href='/'>
                <a
                    className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full '
                    id='top'
                    aria-label='top'
                >
                    <ArIcon width='18vw' height='18vw' className='transition-all transform translate-y-90' 
 className2='transition-all duration-1000 delay-200 transform' className3='text-secondary fill-secondary' />
                </a>
            </Link>
        </Media>
    );

    const arIconSm: JSX.Element = (
        <Media at='sm'>
            <Link href='/'>
                <a
                    className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full '
                    id='top'
                    aria-label='top'
                >
                    <ArIcon width='15vw' height='15vw' className='' className2='' className3='' />
                </a>
            </Link>
        </Media>
    );

    const arIconMd: JSX.Element = (
        <Media at='md'>
            <Link href='/'>
                <a
                    className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full '
                    id='top'
                    aria-label='top'
                >
                    <ArIcon width='12.5vw' height='12.5vw' className='' className2='' className3='' />
                </a>
            </Link>
        </Media>
    );

    const arIconDesktop: JSX.Element = (
        <Media greaterThan='md'>
            <Link href='/'>
                <a
                    className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full '
                    id='top'
                    aria-label='top'
                >
                    <ArIcon width='10vw' height='10vw' className='' className2='' className3='' />
                </a>
            </Link>
        </Media>
    );

    const ArIconsCoalesced = (): JSX.Element => (
        <Fragment>
            <div className='relative block justify-between lg:w-auto lg:static lg:block lg:justify-start transition-all w-full min-w-full col-span-5'>
                {arIconXs}
                {arIconSm}
                {arIconMd}
                {arIconDesktop}
            </div>
        </Fragment>
    );
    return (
        <Fragment>
            <div className='select-none relative z-1 justify-between pt-portfolioDivider navbar-expand-lg grid grid-cols-6 min-w-full w-full container overflow-y-hidden overflow-x-hidden transform'>
                <ArIconsCoalesced />
                <div className='pt-portfolio'>
                    <DarkMode />
                </div>
            </div>
        </Fragment>
    );
};

export default ArIconConditional;
  • 这个project使用了tailwindcss和React的Next.js框架。
// ...
    const arIconXs: JSX.Element = (
        <Media at='xs'>
            <Link href='/'>
                <a
                    className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full '
                    id='top'
                    aria-label='top'
                >
                    <ArIcon width='18vw' height='18vw' className='transition-all transform translate-y-90' 
 className2='transition-all duration-1000 delay-200 transform animate-pulse' className3='text-secondary fill-secondary' />
                </a>
            </Link>
        </Media>
    );
// ...
  • fill-primary调用是为.dark-mode.light-mode css类定义的css变量,然后将其传递给:root,并在客户端(onChange={darkMode.toggle})切换darkMode时激活。
  • 因此,onClick={darkMode.enable}触发图标改变其fillColor和strokeColor值作为css变量的函数。利用React的FC通过抽象来参数化道具,产生了真正显著的粒度控制。在JSX.Element级别使用内联调用来全局定制SVG从未如此无缝。
  • darkMode.disable

  • darkMode.enable

  • 看看我的recent DEV post,如果黑客的React字体库使用typescript和下一步创建自定义字体一些SVG图标,坚持生产,并不因库版本更新引起您的兴趣。

干杯

wgx48brx

wgx48brx3#

这在使用Next.js的情况下是有效的

declare module "*.svg" {
    import {ReactElement, SVGProps} from "react";
    const ReactComponent: (props: SVGProps<SVGElement>) => ReactElement;
    export {ReactComponent}
}
kx1ctssn

kx1ctssn4#

这一条对我很有效:

declare module '*.svg' {
  const content: React.ElementType<React.ComponentPropsWithRef<'svg'>>;
  export default content;
}

加布里埃尔回答https://stackoverflow.com/a/65326058/11313928缺少ref属性。

7xzttuei

7xzttuei5#

由于NextJS覆盖了我自定义的.d.ts导出的*.svg模块,我最终将所有svg文件导入到一个Icons.ts文件中,并按如下方式键入它们:

import { ReactElement, SVGProps } from 'react';

import Rotate from './360.svg';
import Clock from './clock.svg';
import Close from './close.svg';
import Discord from './discord.svg';
import Github from './github.svg';
import Hamburger from './hamburger.svg';
import Location from './location.svg';
import Logo from './logo.svg';
import MapPin from './map-pin.svg';
import Medium from './medium.svg';

const importedIcons = {
  Rotate,
  Clock,
  Close,
  Discord,
  Github,
  Hamburger,
  Location,
  Logo,
  MapPin,
  Medium,
};

type IconName = keyof typeof importedIcons;
type ReactComponent = (props: SVGProps<SVGElement>) => ReactElement;
export default importedIcons as Record<IconName, ReactComponent>;

然后,当我想要使用一个图标时,我可以按如下方式导入它,intellisense会拾取svg prop值。

import Icons from '~assets/icons/Icons';

...
<Icons.Rotate fill="green" stroke="purple" height={10} />
...

不是默认配置,但它工作!

相关问题