首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何声明SVG组件的支持类型?[反应,TypeScript和Webpack]

如何声明SVG组件的支持类型?[反应,TypeScript和Webpack]
EN

Stack Overflow用户
提问于 2020-07-03 12:22:10
回答 5查看 52.6K关注 0票数 17

基本上,我想要做的是将一个SVG图标导入我的react组件,并向它添加道具。像size="24px"一样,使它作为一个组件更加灵活。或者通过添加className支柱使其可编辑(因此我可以向它添加悬浮支柱)。由于这是我第一次与Webpack一起使用TypeScript,所以我对如何声明SVG元素的类型感到困惑,我得到了一个错误(如下所示)

由于包含SVG的方法很多,所以我决定将它作为一个ReactComponent导入。

menu-icon.svg

代码语言:javascript
复制
<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图标)

代码语言:javascript
复制
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文件可以作为组件处理)

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

className支柱添加到MenuIcon SVG组件会导致错误:

代码语言:javascript
复制
(JSX attribute) className: string
Type '{ className: string; }' is not assignable to type 'IntrinsicAttributes'.
  Property 'className' does not exist on type 'IntrinsicAttributes'.ts(2322)

我目前为止的理解

  • 我可以将svg组件打包到div中,并像这样在其中添加一个className<div className="c-header__icon"><MenuIcon/></div>,但我觉得这是一个不雅的解决方案,并不是一个很好的实践。
  • 我从这个答案中学到SVG道具不是字符串,因为它们是SVGAnimatedString对象。所以:
  • 我试着创建.tsx文件而不是.svg文件(那时我不需要index.d.ts文件),但是只有当className的类型是string时,它才能工作。另外,我不确定在扩展名与.svg不同的文件中存储SVG图标是否是一个好做法。在我看来,这不利于清晰。如果我错了,请告诉我什么是真正的好做法。下面是例子:
代码语言:javascript
复制
    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>
        );
      }
    }

我觉得我缺少一些基本的东西,只是我很难弄清楚我应该关注什么,因为有几个主题结合在一起

EN

回答 5

Stack Overflow用户

发布于 2020-12-16 15:15:49

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

代码语言:javascript
复制
declare module "*.svg" {
  import { ReactElement, SVGProps } from "react";
  const content: (props: SVGProps<SVGElement>) => ReactElement;
  export default content;
}

此外,我还使用@svgr/webpack作为SVG加载程序以及Next.js

票数 10
EN

Stack Overflow用户

发布于 2020-09-21 08:49:00

您可以通过全局抽象将svgs参数化,以便于JSX.Element级别的自定义。多么?使用React的FC。尝试实现以下功能:

  • 创建以下接口
代码语言:javascript
复制
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文件中
代码语言:javascript
复制
import { FC } from 'react';
import SvgIconConstituentValues from 'types/svg-icons';

// FC can be parameterized via Abstraction
  • 创建扩展SvgIconSvgIconConstituentValuesFC接口
代码语言:javascript
复制
export interface SvgIcon extends FC<SvgIconConstituentValues> {}
  • SvgIcon参数化的svg的性质如下
代码语言:javascript
复制
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) className表示<svg>...</svg>,property JSX.IntrinsicElements.svg: SVGProps<SVGSVGElement>;(2) className2 for <circle /> property JSX.IntrinsicElements.circle: SVGProps<SVGCircleElement>;(3) className3表示<path /> property JSX.IntrinsicElements.path: SVGProps。
  • 注意,const ArIcon: SvgIcon = ({ ... }): JSX.Element => {...}确实是一个JSX.Element。因此,<svg></svg>本身和任何子(圆圈、路径等)都是JSX.IntrinsicElements,每个子类都允许有自己独特的className。这些className调用是手动添加到svg中的,转换调用也是如此(在其他地方将图标内联旋转)。
  • JSX Attribute className of JSX.IntrinsicElements定义如下
代码语言:javascript
复制
SVGAttributes<T>.className?: string | undefined
  • 每个JSX.IntrinsicElement都有权拥有一个className属性。在svg中有100条路径和一个圆?您可以有102个classNames,可以通过抽象进行参数化。
  • 现在是最好的了。下面是我的投资组合中的一个文件,我修改了提取svg参数,使它能够很好地处理暗模式切换(use-dark-mode)和屏幕宽度相关的图标呈现(@artsy/fresnel)。您可以全局导入此图标并在每个JSX.Element内内联调用参数,而无需任何道具传递。
代码语言:javascript
复制
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;
  • 这个项目使用了裁剪器和React的Next.js框架。尽管如此,如果我想让包含图标的JSX.IntrinsicElement圈只在手机上脉冲呢?将顺风的animate-pulse添加到className2中,如下所示
代码语言:javascript
复制
// ...
    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

  • 请查看我的最近的DEV员额,如果黑客攻击了使用类型记录和旁边的react fontawesome来创建自定义的fontawesome图标,这些图标将持续到生产,并且通过库版本保持不变,更新会影响您的兴趣。

干杯

票数 5
EN

Stack Overflow用户

发布于 2022-04-13 21:19:55

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

代码语言:javascript
复制
declare module "*.svg" {
    import {ReactElement, SVGProps} from "react";
    const ReactComponent: (props: SVGProps<SVGElement>) => ReactElement;
    export {ReactComponent}
}
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62715379

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档