在Vue.JS中使用图标组件

原文连接:https://gist.github.com/Justineo/fb2ebe773009df80e80d625132350e30html

本文对原文进行一次翻译,并从React开发者的角度简单地作了一些解读。前端

此文不包含字体图标和SVG sprite。仅在此讨论容许用户按需导入的图标系统。vue

There are three major ways of exposing API of an icon component in Vue.js and each one of them has its own pros & cons:react

在Vue.js的生态里,有3种主流的API形态,它们有各自的优缺点:webpack

  1. 使用单一的组件(如<v-icon>),让乃经过name或者type属性来指定真正的图标。git

    图标的数据经过一个全局的“池子”来注册。github

    // v-icon/flag.js
    	import Icon from 'v-icon'
    	import { mdiFlag } from '@mdi/js'
    	Icon.add('flag', mdiFlag)

    而后这样子使用:web

    <template>
    	  <v-icon name="flag" />
    	</template>
    
    	<script>
    	import VIcon from 'v-icon'
    	import 'v-icon/flag'
    
    	export default {
    	  components: {
    		VIcon
    	  }
    	}
    	</script>

    在我维护的VueAwesome(内置了FontAwesome图标的组件库)中用了这个方案,同时我认为这是当前最符合人机工程学的形式。不过图标的name属性和那些纯反作用的模块的导入之间的关系比较隐式,图标的数据也在全局注册。若是你有多个不一样版本的v-icon,就可能出现问题。npm

    FontAwesome官方的Vue.js组件用了一个稍微不一样的方案,它们让用户本身主动把图标加到全局的池子中(也可能我不该该把这个方式归类到这个方案中):安全

    import { library } from '@fortawesome/fontawesome-svg-core'
    	import { faUserSecret } from '@fortawesome/free-solid-svg-icons'
    
    	library.add(faUserSecret)
  2. 用一个单一的维护(如<v-icon),用户经过datacontent之类的属性建立真正的图标。

    用户主动把图标的数据传递给组件:

    <template>
    	  <v-icon :content="mdiFlag" />
    	</template>
    
    	<script>
    	import VIcon from 'v-icon'
    	import { mdiFlag } from '@mdi/js'
    
    	export default {
    	  components: {
    		VIcon
    	  },
    	  created() {
    		Object.assign(this, {
    		  mdiFlag
    		})
    	  }
    	}
    	</script>

    这是Vuetify支持的方式(Vuetify经过这种方式支持多种图标的使用方式),这种试在人机工程和直观性上有些损失,但没有方案1的缺点。

  3. 每一个组件表明不一样的图标(如<icon-flag /><icon-star />等)。

    这个方案里,每一个组件经过一个图标工厂创造出来:

    // icon-flag.js
    	import { mdiFlag } from '@mdi/js'
    	import { createIcon } from 'v-icon'
    
    	export default createIcon('flag', mdiFlag)

    并经过这种方式使用:

    <template>
    	  <icon-flag />
    	</template>
    
    	<script>
    	import { IconFlag } from 'v-icon'
    
    	export default {
    	  components: {
    		VIcon,
    		IconFlag
    	  }
    	}
    	</script>

    这种方案在React社区里被普遍采用,我在本文的后续部分将展开讨论。

每一个组件表明一个图标

我将更深刻地说一下这种方案在Vue.js中的使用。

在Vue.js中,模板和脚本是分开的,组件经过components选项注册。不过就像咱们知道的,若是一个组件要用不少图标的话,这种方式会挺麻烦。

Vue 2

<template>
  <div>
    <!-- inline -->
    <icon-flag />

    <!-- conditional -->
    <icon-flag v-if="flag" />
    <icon-star v-else />

    <!-- dynamic -->
    <component :is="flag ? IconFlag : IconStar" />
  </div>
</template>

<script>
import { IconFlag, IconStar } from 'foo-icons'

export default {
  components: {
    IconFlag,
    IconStar
  },
  data() {
    return {
      flag: true
    }
  },
  created() {
    Object.assign(this, {
      IconFlag,
      IconStar
    })
  }
}
</script>

能够看到若是想用图标的is绑定,咱们必须把components手动暴露到渲染上下文中。咱们能够用字符串去替换组件定义来绕过,但对代码检查和类型系统来讲就不那么友好。

<template>
  <div>
    <!-- inline -->
    <icon-flag />

    <!-- conditional -->
    <icon-flag v-if="flag" />
    <icon-star v-else />

    <!-- dynamic -->
    <component :is="flag ? 'icon-flag' : 'icon-star'" />
  </div>
</template>

<script>
import { IconFlag, IconStar } from 'foo-icons'

export default {
  components: {
    IconFlag,
    IconStar
  },
  data() {
    return {
      flag: true
    }
  }
}
</script>

Vue 3

<template>
  <!-- inline -->
  <icon-flag />

  <!-- conditional -->
  <icon-flag v-if="flag" />
  <icon-star v-else />

  <!-- dynamic -->
  <component :is="flag ? IconFlag : IconStar" />
</template>

<script>
import { ref } from 'vue'
import { IconFlag, IconStar } from 'foo-icons'

export default {
  components: {
    IconFlag,
    IconStar
  },
  setup() {
    const flag = ref(true)

    return {
      flag,
      IconFlag,
      IconStar
    }
  }
}
</script>

若是用:is绑定,<script>部分会变成这样:

import { ref } from 'vue'
import { IconFlag, IconStar } from 'foo-icons'

export default {
  components: {
    IconFlag,
    IconStar
  },
  setup() {
    const flag = ref(true)

    return {
      flag
    }
  }
}

若是咱们采纳<script components>这样的形式的话:

<template>
  <!-- inline -->
  <icon-flag />

  <!-- conditional -->
  <icon-flag v-if="flag" />
  <icon-star v-else />

  <!-- dynamic -->
  <component :is="flag ? 'icon-flag' : 'icon-star'" />
</template>

<script components>
export { IconFlag, IconStar } from 'foo-icons'
</script>

<script>
import { ref } from 'vue'

export default {
  setup() {
    const flag = ref(true)

    return {
      flag
    }
  }
}
</script>

或者用<script setup>提案:

<script setup>
import { ref } from 'vue'

export const flag = ref(true)
</script>

后记

这很篇文章很精练地介绍了在Vue中按需引入图标的方式,与React社区作比较,能够看到两个生态的差别仍是存在的。在React社区中,使用第3种方式(每一个图标一个组件)很是广泛,如NPM上排名较高的react-icons和知名组件库@ant-design/icons@material-ui/icons都是这一形态。

这多是因为React社区中并不倾向将“组件”这一律念特殊化,组件就是普通的函数、普通的类,因此它的复用于其它的函数、类的复用相同,如同lodash会导出不少个工具函数同样,一个图标库会导出不少个图标组件很是合理。

在文中对于使用createIcon工厂函数的使用有一些能够优化的点。正常使用工厂函数会让建立的组件不可被tree shaking,其缘由是语法分析会认为createIcon函数自己是有反作用的,所以这个调用不能被安全地删除。能够经过terser的特殊注释来标记:

// icon-flag.js
import { mdiFlag } from '@mdi/js'
import { createIcon } from 'v-icon'

export default /*#__PURE__*/createIcon('flag', mdiFlag)

做者:张立理 百度资深前端工程师

相关文章
相关标签/搜索