在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)

做者:張立理 百度資深前端工程師

相關文章
相關標籤/搜索