Material Design IconsをVue (Composition API+script setup+TypeScript)で使う
Pictogrammers - Open-source iconography for designers and developersの公式に書かれている方法は、TypeScriptの型が活かせません。
型が活かせるように、componentを書き直しました。
公式のやり方
まずは、公式。
公式で紹介されているVueでの利用方法は下記です。
ab-testing - Material Design Icons - Pictogrammers
(参考情報: webフォントは非推奨とのことですWebfont Alternatives - Docs - Pictogrammers)
<template>
<svg-icon type="mdi" :path="path"></svg-icon>
</template>
<script>
import SvgIcon from '@jamescoyle/vue-icon';
import { mdiAbTesting } from '@mdi/js';
export default {
name: "my-component",
components: {
SvgIcon
},
data() {
return {
path: mdiAbTesting,
}
}
}
</script>
SVGのpath文字列のの情報を@mdi/js
からimportし、@jamescoyle/vue-icon
のSvgIconコンポーネントで表示するというものです。
素直で良いのですが'@jamescoyle/vue-icon'
に型がないのが唯一気になります。
該当componentを定義しているファイルは以下です。
上記componentをTypeScript (特にComposition API + script setup + TypeScript)で書き直すチャレンジをしました。
Composition API + script setup + TypeScript化
早速成果物です。
本componentは、Vue 2.7および Vue 3.2以降であれば動くと思います。
コードを以下に転記します。。
<template>
<svg
:width="sizeValue"
:height="sizeValue"
:viewBox="viewboxValue"
>
<path :d="path" />
</svg>
</template>
<script setup lang="ts">
// https://github.com/Pictogrammers/vue-icon/blob/master/lib/svg-icon.vue
import { computed } from 'vue';
interface Props {
type?: 'mdi' | 'simple-icons' | 'default';
path: string;
size?: string | number;
viewbox?: string;
flip?: 'horizontal' | 'vertical' | 'both' | 'none';
rotate?: number;
}
const props = withDefaults(defineProps<Props>(), {
type: 'default',
rotate: 0,
});
const types = new Map([
['mdi', { size: 24, viewbox: '0 0 24 24' }],
['simple-icons', { size: 24, viewbox: '0 0 24 24' }],
['default', { size: 0, viewbox: '0 0 0 0' }],
]);
const defaults = computed(() => {
const t = types.get(props.type);
if (!t) {
throw new Error(`Unknown type ${props.type}`);
} else {
return t;
}
});
const rotateValue = computed(() => {
return isNaN(props.rotate) ? props.rotate : props.rotate + 'deg';
});
const scaleHorizontalValue = computed(() => {
return props.flip && ['both', 'horizontal'].includes(props.flip) ? '-1' : '1';
});
const scaleVerticalValue = computed(() => {
return props.flip && ['both', 'vertical'].includes(props.flip) ? '-1' : '1';
});
const sizeValue = computed(() => {
return props.size || defaults.value.size;
});
const viewboxValue = computed(() => {
return props.viewbox || defaults.value.viewbox;
});
</script>
<style scoped lang="css">
svg {
transform: rotate(v-bind(rotateValue)) scale(v-bind(scaleHorizontalValue), v-bind(scaleVerticalValue));
}
path {
fill: currentColor;
}
</style>
使い方は、'@jamescoyle/vue-icon'
とほぼ同じ。
たとえば、上記を src/components/SvgIcon.vue
に保存した場合はimport文をいかに書き換えるだけです。
<script>
// import SvgIcon from '@jamescoyle/vue-icon';
import SvgIcon from 'src/components/SvgIcon.vue';
// 省略
</script>
書き直した箇所の解説
数点書き直した箇所を解説します。
Vue 2.7およびVue3.2以降で使える、CSSのv-bindでstylesを代替しました。
CSS内のv-bind(rotateValue)
で JavaScript rotateValue
をbindしています。
svg {
transform: rotate(v-bind(rotateValue)) scale(v-bind(scaleHorizontalValue), v-bind(scaleVerticalValue));
}
v-bind代替に伴い、svgタグ内で存在していたstyles attributeは削除しました。
<svg
:width="sizeValue"
:height="sizeValue"
:viewBox="viewboxValue"
>
v-bindのおかげで直感的なコードにできました。
interface Props {
type?: 'mdi' | 'simple-icons' | 'default';
path: string;
size?: string | number;
viewbox?: string;
flip?: 'horizontal' | 'vertical' | 'both' | 'none';
rotate?: number;
}
flipのvalidatorをTypeScriptのUnion型でシンプルに記述できました。
const types = new Map([
['mdi', { size: 24, viewbox: '0 0 24 24' }],
['simple-icons', { size: 24, viewbox: '0 0 24 24' }],
['default', { size: 0, viewbox: '0 0 0 0' }],
]);
ObjectをMapにしました。Objectのままでも良いです。
keyが文字列であることが明示的な方が読みやすいかとおもって、Mapにしました。
以上。
所感
Composition API + script setup + TypeScript 最高。
書いていて楽しい。
Discussion