首页 > 其他分享 >图标组件的封装与管理(React/svg)

图标组件的封装与管理(React/svg)

时间:2024-07-12 15:18:40浏览次数:9  
标签:return const svg React import type 图标

一 概要

1.1 背景

最近在项目中使用了很多从iconfont拿到的图标。使用官网的导入方法有些繁琐,也不易管理。于是捣鼓了一下...

1.2 目的

  1. 能够像组件一样使用,具有规范性。比如暴露一个type属性,根据不同的type使用不同的主题色。
  2. 高自由度。可以直接在项目中管理图标,只需要处理从其他图标网站拿到svg即可。
  3. 提供一个页面总览全部图标,方便使用。

1.3 实现效果

  1. 图标组件

    import { BillIcon } from '@/components/icons';  
    
    <BillIcon type="primary" size={16} badge={false}/>
    
  2. Js调用(基于前者,如果图标需要根据某种条件来选用,这种方式很推荐)

    import { getIcon } from '@/utils';
    
    <div> { 
        getIcon('bill', 
            { type:'primary', size:16, badge:false })
        }
    </div>
    
  3. 图标总览(不重要,无所谓)

    2wcfi-hhwri.gif
    项目使用的是webpack打包,所以在配置文件中新增了一个入口和出口,让图标库跟项目绑定,项目用到了什么,就显示什么。如图所示,我直接通过icon.html来访问,这在调试项目的时候非常好用。

1.4 实现步骤

  1. 从免费网站中拿到svg。比如我在iconfont
    image.png
    image.png

  2. (重点)把svg封装成组件:每个svg对应一个tsx/jsx文件,存储在项目的目录中(比如我存储在src/components/icons中)

  3. 创建一个入口文件,导出所有的图标组件。同时创建一个字典(map),让每一个图标对应一个key,比如bill对应的是<BillIcon/>(控制图标和key的命名会让后续管理更方便,比如我这里的key取的是图标名称前面第一个单词,同时小写。)

二 图标组件封装与管理

2.1 了解svg的属性

在使用图标的时候,我们实际上只关注它的颜色和大小(我们不设计图标,我们只是图标的搬运工)。

下面是复制过来的一段svg代码,我们只需要关注它的widthheight和里面path标签的fill属性(颜色)。

<svg
   t="1720764743617"
   class="icon"
   viewBox="0 0 1024 1024"
   version="1.1"
   xmlns="http://www.w3.org/2000/svg"
   p-id="4480"
   width="16"
   height="16"
>
   <path
      d="M853.333333 938.666667H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666667V128a42.666667 42.666667 0 0 1 42.666667-42.666667h682.666666a42.666667 42.666667 0 0 1 42.666667 42.666667v768a42.666667 42.666667 0 0 1-42.666667 42.666667zM341.333333 384v85.333333h341.333334V384H341.333333z m0 170.666667v85.333333h341.333334v-85.333333H341.333333z"
      p-id="4481"
      fill=""
   ></path>
</svg>

其中t属性和class属性在jsx/tsx中会显示有问题,删掉即可。

2.2 封装图标组件

现在问题来了,如果直接在svg外层包一层,意味着我们需要传入widthheightfill的值,这在使用中不方便。我只想传一个typesize, 希望内部直接帮我处理成对应的值赋值给svg。

size比较好处理,而fill接收的是一个颜色,svg是不可能识别我们定义的type的。行,那改写一下:

// BackIcon.tsx

type TIconType = 'primary' | 'disabled' | 'white' | 'danger' 

const BackIcon = (props: TIconProps) => {
   const getColor = (type: TIconType) => {
       switch (type) {
          case 'primary':
             return '#13227a';
          case 'white':
             return '#ffffff';
          case 'danger':
             return '#ef6b6b';
          default:
             return '#8f8f8f';
       }
    };
    const color = getColor(props.type);

   return (
      <svg
         viewBox="0 0 1024 1024"
         version="1.1"
         xmlns="http://www.w3.org/2000/svg"
         p-id="2674"
         width={props.size}
         height={props.size}
      >
         <path
            d="M672 896c-8.533333 0-17.066667-2.133333-21.333333-8.533333l-362.666667-352c-6.4-6.4-10.666667-14.933333-10.666667-23.466667 0-8.533333 4.266667-17.066667 10.666667-23.466667L652.8 136.533333c12.8-12.8 32-12.8 44.8 0s12.8 32 0 44.8L356.266667 512l339.2 328.533333c12.8 12.8 12.8 32 0 44.8-6.4 8.533333-14.933333 10.666667-23.466667 10.666667z"
            fill={color}
            p-id="2675"
         ></path>
      </svg>
   );
};

那问题又来了,我不想在每个图标组件都加这么一串又臭又长的代码呀。没问题!只需要在中间再包一层就好了!

// withIconColor.tsx

// 图标类型
type TIconType = 'primary' | 'disabled' | 'white' | 'danger' 
// 图标组件属性
interface IconProps {
    type?: TIconType;
    size?: number;
    badge?: boolean; // 扩展的,给图标右上角加红点,它跟svg无关
}

const getColor = (type: TIconType) => {
   switch (type) {
      case 'primary':
         return '#13227a';
      case 'white':
         return '#ffffff';
      case 'danger':
         return '#ef6b6b';
      default:
         return '#8f8f8f';
   }
};

// 参数是一个组件,作用是处理在外层传递进来的props,转化成一定内容后,再还给原组件
function withIconColor(WrappedIcon: React.ComponentType<any>) {
   return function (props: IconProps) {
      const {
         type,
         size,
         badge = false,
         ...others
      } = props;
      const color = getColor(props.type);

      return (
         <span {...others}>
            {badge && (
               <div className="absolute w-2 h-2 text-[4px] text-white bg-red-500 rounded-full right-0"></div>
            )}
            <WrappedIcon color={color} size={size || 24} />
         </span>
      );
   };
}

export default withIconColor;

怎么使用呢?

import withIconColor from './withIconColor';
type TIconProps = {
    color: string,
    size: number
}

const BackIcon = (props: TIconProps) => {
   return (
      <svg
         viewBox="0 0 1024 1024"
         version="1.1"
         xmlns="http://www.w3.org/2000/svg"
         p-id="2674"
         width={props.size}
         height={props.size}
      >
         <path
            d="M672 896c-8.533333 0-17.066667-2.133333-21.333333-8.533333l-362.666667-352c-6.4-6.4-10.666667-14.933333-10.666667-23.466667 0-8.533333 4.266667-17.066667 10.666667-23.466667L652.8 136.533333c12.8-12.8 32-12.8 44.8 0s12.8 32 0 44.8L356.266667 512l339.2 328.533333c12.8 12.8 12.8 32 0 44.8-6.4 8.533333-14.933333 10.666667-23.466667 10.666667z"
            fill={props.color}
            p-id="2675"
         ></path>
      </svg>
   );
};

export default withIconColor(BackIcon); // 在这里用!

此后,如果还要加什么图标,就创建一个文件,把上面的内容复制一份,换个命名,把中间的svg替换掉就好了!(当然还要把svg原来的width、height和fill属性换成动态传进来的。)

2.3 整体结构

结构因人而异,这里列下我的项目的结构

- src
    - components
        - icons
            - BillIcon.tsx
            - UserIcon.tsx
            - index.ts
            - withIconColor.tsx
   - utils
       - getIcon
  1. 组件入口

    // src\components\icons\index.ts
    
    type IconKey = "bill" | "user"  // 图标的key
    
    import BillIcon from './BillIcon'
    import UserIcon from './UserIcon'
    
    const keyToIconMap = {
        bill: BillIcon,
        user: UserIcon,
    }
    
    const ICON_KEYS = Object.keys(keyToIconMap)  as Array<IconKey>
    
    export {
        BillIcon,
        UserIcon,
    
        keyToIconMap,
        ICON_KEYS
    }
    

    之后就能在其他地方使用了

    import { BillIcon } from '@/components/icons';  
    
    <BillIcon />
    
  2. getIcon

    import { IconKey, IconProps } from '@/types';
    import { keyToIconMap } from '@/components/icons'
    
    export default (key: IconKey, props?: IconProps) => {
       const Icon = keyToIconMap[key];
       return <Icon {...props} />;
    };
    

    之后就能在其他地方使用了

    import { getIcon } from '@/utils';
    
    <div> { getIcon('bill', { type:'primary'}) } </div>
    

三 图标总览页面

在根目录加个入口(其他地方也行)

- src
    - iconIndex.tsx

这一步也没那么重要了,有需要就copy吧,样式根据自己需求自定义了。

import './style/index.css';
import { createRoot } from 'react-dom/client';
import { getIcon } from '@/utils';
import { ICON_KEYS } from '@/components/icons';
import toast, { Toaster } from 'react-hot-toast';
import { RadioButton } from './components';
import { useState } from 'react';

const root = createRoot(document.getElementById('root')); // 因为我webpack定义的模板html有个root,所以这样写,因人而异了

function capitalizeFirstLetter(string: string) {
   if (!string) return string;
   return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
}

const IconPage = () => {
   const handleCopy = async (value) => {
      await navigator.clipboard.writeText(value);
      toast.success(`复制成功: ${value}`);
   };

   const [type, setType] = useState('js');

   const handleChange = (value) => {
      setType(value);
   };

   return (
      <div className="p-2 font-mono">
         <Toaster />
         <div className="my-2">
            <RadioButton
               value={type}
               onChange={handleChange}
               options={[
                  { value: 'component', label: '使用组件' },
                  { value: 'js', label: '使用js' },
               ]}
            />
            <div className="flex justify-center space-x-2">
               {type === 'component' ? (
                  <div className="p-2 border bg-slate-900 text-slate-50 min-w-[430px]">
                     {`import { BillIcon } from '@/components/icons'; `}
                     <br />
                     <br />
                     {` <BillIcon type="primary" size={16}/> `}
                  </div>
               ) : (
                  <div className="p-2 border bg-slate-900 text-slate-50 w-fit min-w-[430px]">
                     {`import { getIcon } from '@/utils'; `}
                     <br />
                     <br />
                     {` <div> { getIcon('bill', { type:'primary', size: 16 }) } </div> `}
                  </div>
               )}
            </div>
         </div>
         <div className="grid-cols-2 lg:grid-cols-8 grid gap-2 ">
            {ICON_KEYS.map((key) => {
               const value =
                  type === 'component'
                     ? `${capitalizeFirstLetter(key)}Icon`
                     : key;
               return (
                  <div
                     className="rounded-sm  flex justify-center cursor-pointer items-center pt-4 shadow-sm flex-col hover:scale-105 transition duration-300 ease-out hover:bg-indigo-100"
                     onClick={handleCopy.bind(null, value)}
                  >
                     {getIcon(key, {
                        type: 'primary',
                     })}
                     <span className="text select-none">{value}</span>
                  </div>
               );
            })}
         </div>
      </div>
   );
};

root.render(<IconPage />);

配置webpack

{
    // entry: path.ENTRY,   // 之前单入口是这样写的
    entry: {
         main: path.ENTRY,
         icon: path.ICON_ENTRY  // path.resolve(__dirname,  'src', 'iconIndex.tsx')
    },
    // ...
    plugins: [
         new HtmlWebpackPlugin({
            template: path.TEMPLATE,  // path.resolve(__dirname, 'public', 'index.html'),
            filename: 'index.html', // 输出文件名
            chunks: ['main'], // 对应entry里边定义的
         }),
         new HtmlWebpackPlugin({
            template: path.TEMPLATE,
            filename: 'icon.html', // 输出文件名
            chunks: ['icon'], // 对应entry里边定义的
         }),
   ],
}

标签:return,const,svg,React,import,type,图标
From: https://www.cnblogs.com/sanhuamao/p/18298475

相关文章

  • 界面组件Kendo UI for React 2024 Q2亮点 - 生成式AI集成、设计系统增强
    随着最新的2024年第二季度发布,KendoUIforReact为应用程序开发设定了标准,包括生成式AI集成、增强的设计系统功能和可访问的数据可视化。新的2024年第二季度版本为应用程序界面提供了人工智能(AI)提示,从设计到代码的生产力增强、可访问性改进、一系列新的UI组件等。KendoUI致力......
  • react hooks实现对元素拖拽及鼠标滚轮缩放
    page.jsximport'./index.less';import{useDrag,useZoom}from'./hooks';constDragZoom=()=>{const{handleMouseDown,handleMouseMove,handleMouseUp}=useDrag();const{handleWheel,scale}=useZoom();re......
  • WPF 实现 图标按钮
    假设需要实现一个图标和文本结合的按钮,普通做法是直接重写该按钮的模板;如果想作为通用的呢?两种做法:附加属性自定义控件推荐使用附加属性的形式第一种:附加属性创建Button的附加属性 ButtonExtensions1publicstaticclassButtonExtensions2{3//Using......
  • Ubuntu创建图标
    很多时候我们喜欢省事双击图标运行软件,那么怎么创建图标呢?下面介绍两种主流的方法。一.使用vim创建文件如果你没有安装vim,请先安装:sudoaptinstallvim接下来按照下面指令设置图标,以PyCharm为例:cd/usr/share/applicationssudovimpycharm.desktop注意这里......
  • [email protected](53)[email protected](2)- action
    目录1,特点1.1,payload1.2,type1.3,bindActionCreators1,特点是一个平面对象(plain-object)。换句话说,就是通过字面量创建的对象,它的__proto__指向Object.prototype。该对象有2个属性:constaction={ type:'add', payload:3}1.1,payload表示额外需要传递的附......
  • 使用Java9 Flow API进行Reactive Programming
    importjava.util.concurrent.Flow;importjava.util.concurrent.Flow.Publisher;importjava.util.concurrent.Flow.Subscriber;publicclassReactiveExample{publicstaticvoidmain(String[]args){//创建一个发布者,发布一系列的数字Publisher......
  • 275:vue+openlayers 点图标的大小随着分辨率而变化
    作者:还是大剑师兰特,曾为美国某知名大学计算机专业研究生,现为国内GIS领域高级前端工程师,CSDN知名博主,深耕openlayers、leaflet、mapbox、cesium,canvas,echarts等技术开发,欢迎加微信(gis-dajianshi),一起交流。查看本专栏目录-本文是第275个示例文章目录一......
  • react或vue中页面多个echarts,只有最后一个能自适应的处理方法
    页面多个echarts时,自适应绑定方式必须是addEventListenerwindow.addEventListener("resize",()=>{myChart.resize();myChart2.resize();})myChart,myChart2是echart实例   ......
  • vitepress如何添加favicon.ico图标
    第一次在csdn发文章,写的不好,还请理解,直接解决文章标题中的问题,直接上干货。head:[//添加图标['link',{rel:'icon',href:'/favicon.ico'}]],复制以上代码,然后找到config.mjs这个文件。如下图。找到这个文件后,我们先别着急,我们先要建立一个文件夹......
  • 【vueUse库Reactivity模块各函数简介及使用方法--上篇】
    vueUse库是一个专门为Vue打造的工具库,提供了丰富的功能,包括监听页面元素的各种行为以及调用浏览器提供的各种能力等。其中的Browser模块包含了一些实用的函数,以下是这些函数的简介和使用方法:vueUse库Sensors模块各函数简介及使用方法vueUseReactivity函数1.com......