侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

图标使用新姿势- react 按需引用 svg 的实现

前端  /  管理员 发布于 6年前   194

前言

图标是前端在业务开发中不得不写的一个东西,以我司的几个部门为例,每个组在写图标上都有不一样的方式:

用户平台:单色图标用 iconfont 上提供的字体文件,彩色图标用 img 引入代替或者使用iconfont 上提供的 symbol.js 。saas:引入 svg 文件,通过 react-svg-loader 将其包裹成一个 react 组件使用。到店购:引入 svg 文件,通过 svg-sprite-loader 将所有 svg 图标处理成 svg 雪碧图的方式使用。

这几种使用方式各有千秋,下面谈一谈他们的优缺点:)

用户平台的使用方式【简单】,不需要手动引入每个 svg 文件,缺点是字体图标不如 svg 文件【可扩展性好】,同时为了引入一个图标引入一个完整的字体图标也会带来一定冗余。

而其他两个组的问题在于【图标的引入】以及【管理】方面,需要手动引入 svg 文件,当然优点也非常可观。

这里明确一个事实:svg 图标的综合表现是远大于字体图标的,从 antd 从 3.9.0 的更新就可以看出来。

摘自官方文档


antd 的图标使用体验一直很好,比如下面的代码就可以定义一个 home 图标

<Icon type="home" />

不需要事先引入任何资源 ,只需要指定 type = "home" 就可以使用。但是 antd 没有解决一个问题,那就是如何做到图标的按需引用?

摘自官方文档


即便是这里提到的 webpack 插件 也不过是图标改成了后置引入,并没有解决图标的按需引用问题。

当然 antd 不好优雅的这个问题是由它的使用方式决定的(合理猜测),作为一个流行的组件库,antd 在引入新的技术的同时又要照顾之前使用者的使用体验,不可避免的会出现一些瑕疵。这是可以理解的,不过换成我们普通业务开发而言,我们没有必要去追求太过完美的开发体验,做出略微的牺牲即可实现【既保持 antd Icon 一样的使用方式,又按需引用了 svg 文件】,怎么实现呢?


如何处理 svg

svg-sprite-loader 是一个在 webpack 中应用比较广泛的 svg 处理库,它可以将代码里引入的 svg 文件合并到一起,然后以 svg symbol 的方式使用,关于它的使用方式网上有大量的文章,所以本文不会再描述它如何使用,请读者自行查阅,

值得一提的是,介绍此 loader 的的文章中,一般都会附带如何一次性引入项目中需要的所有 svg 的方法,那就是利用 webpack 的 require.context api,这个 api 可以获取一个特定的上下文,主要用来实现自动化导入模块,所以为了不再每个模块中一一写 import 'xxx.svg 这样的语句,使用这个 api 是有必要的。

借助 require.contet 和 svg-sprite-loader 能够使图标开发体验上升一个档次,也能配合 react 组件实现类似 antd Icon 的使用方式。

但是这种使用方式存在一个缺点,那就是【如何避免引入不必要的 svg】,要知道 require.contet 可不会区分哪些 svg 是真正需要的,当然对于个人项目而言,我们可以给一个页面固定一个文件夹存在真正需要的 svg 文件,但是对于多页面的 repo 而言,我们无法也没必要给每一个页面都设置一个专门存放该页面需要的 svg 的文件夹。

作为一个挑剔的程序员,我需要一种更智能更自动化的方式去引入我真正需要的 svg 图标。


思路分析

现在要解决的问题是我需要在写下类似以下代码的时候:

<Icon type="close" />

有种工具能同时在文件中帮我 import 一个 close.svg 。

比如下面的代码:

import Icon from './Icon.jsx';ReactDOM.render(<Icon type="close"/>);

经过处理后变成这样:

import Icon from './Icon.jsx';import './assets/close.svg'ReactDOM.render(<Icon type="close"/>);

想一想,之前使用过什么工具?会自动帮我们引入我们所需要的代码呢?

答案是: babel-plugin-transform-runtime ,一个自动帮前端工程师导入 polyfill 的 babel 插件,

以下是官网介绍

Externalise references to helpers and builtins, automatically polyfilling your code without polluting globals

所以,参考 babel-plugin-transform-runtime 的原理和作用 ,我们想要自动导入一个 svg,也可以借用 babel-plugin 实现。


实现原理

熟悉 babel 的同学,应该知道 babel 插件作用原理,是通过对转化成 ast 的 js 代码做一些更改、替换之类的操作,不熟悉的同学可以点 这里 了解一下 babel 插件是如何开发的。

以前文我们提到的这一句代码 <Icon type="close"/> 为例,它经过 babel 转化后的 ast 长这个样子


转化成 json 会更清晰一些:

{ "expression": {    "type": "JSXElement",    "start": 0,    "end": 20,    "openingElement": {      "type": "JSXOpeningElement",      "start": 0,      "end": 20,      "attributes": [        {          "type": "JSXAttribute",          "start": 6,          "end": 18,          "name": {            "type": "JSXIdentifier",            "start": 6,            "end": 10,            "name": "type"          },          "value": {            "type": "Literal",            "start": 11,            "end": 18,            "value": "close",            "raw": "\"close\""          }        }      ],      "name": {        "type": "JSXIdentifier",        "start": 1,        "end": 5,        "name": "Icon"      },      "selfClosing": true    },    "closingElement": null,    "children": []    }  }

因为用的是 Jsx 语法,所以这个表达式的 type 是 JSXElement , 同时设置了了 props.type的值为 close , 所以他会有个 name 为 type 而 value 为 close 的 JSXAttribute .

我们在 babel plugin 中可以拿到上述的分析结果,自然也知道了这条语句产生的作用是:

我写下了一个 type 为 close 的 Icon Component ,我希望它能够放一个 close.svg 在这里

所以我们可以 new 一个 Set() 对象,将当前 close 这个关键词存放进去, 为什么用 Set ,因为 Set 中的对象是不想等的,免去重复添加关键词然后再去重的必要。

代码演示:

function plugin({ types: t }) {  return {    visitor: {      Program: {        enter(path, state) {          state.svgSet = new Set();        }      }    }  };}

在初次访问整个语法树的时候,创建一个 Set 对象,注意 svgSet 一定要挂在 state 上。

然后借用 babel plugin 分析此文件内的所有 JSXElement ,直到整个文件的代码被处理完毕,这样我们就能拿到一个装满了所有关键词的 Set 对象。

代码片段:

function plugin({ types: t }) {  return {    visitor: {      Program: {       ...      },      JSXElement(path, state) {        const {          openingElement: {            attributes          }        } = path.node;        attributes          .forEach(({ name, value }) => {            // 判断 name.name 是否等于 "type" 或者是其他设置好的关键词            state.svgCache.add(value.value);          });      }    }  };}

最后,将 Set 里存放的 svg ,遍历之后,用 babel 工具库生成如下的语句:

import 'xxx.svg'

然后插入到此文件的最顶端,剩下的事情就交给 webpack 以及其他 loader 处理了。

我已经将上述代码封装了一个 npm 包 ,欢迎大家下载和体验,当然目前还比较简陋,源码和详细文档也将在不久后发布。

还有 vue 版本的工具也在开发中。


后记

这篇文章实现的 babel 插件原理并不复杂,记录下来希望能够帮助到大家:遇到项目中的问题的时候可以参考社区的实现来解决。最后欢迎大家关注酷家乐前端团队,可以找我私聊或者内推,我的邮箱:[email protected]

代码参考:babel-plugin-transform-runtime,babel-plugin-import

工具使用:在线预览 ast

原文  https://webfe.kujiale.com/babel-plugin-jsx-svg-import/ 

 


  • 上一条:
    使用imba.io框架,得到比 vue 快50倍的性能基准
    下一条:
    如何正确选型,React Native 还是 Native?
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 使用 Alpine.js 排序插件对元素进行排序(0个评论)
    • 在js中使用jszip + file-saver实现批量下载OSS文件功能示例(0个评论)
    • 在vue中实现父页面按钮显示子组件中的el-dialog效果(0个评论)
    • 使用mock-server实现模拟接口对接流程步骤(0个评论)
    • vue项目打包程序实现把项目打包成一个exe可执行程序(0个评论)
    • 近期文章
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-06
    • 2017-07
    • 2017-08
    • 2017-09
    • 2017-10
    • 2017-11
    • 2018-03
    • 2018-04
    • 2018-05
    • 2018-06
    • 2018-09
    • 2018-11
    • 2018-12
    • 2019-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2021-04
    • 2021-05
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客