前文链接
- 【Vue-Element-Admin 分析】- 01 Mock 是怎么实现的?
- 【Vue-Element-Admin 分析】- 02 网络请求的是怎么封装的?
- 【Vue-Element-Admin 分析】- 03 权限管理是怎么实现的?
分析
图标组件的线索是非常多的,这里从 main.js
入手,可以看到引入了一个当前目录下的 icons
:
直接跟过去即可,代码非常简单:
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon' // svg component
// register globally
Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext =>
requireContext.keys().forEach(requireContext)
requireAll(req)
前面的部分一眼就能明白,注册了一个 vue 的全局组件。而后面的部分设计到 webpack api,我们可以看一下:依赖管理,再结合当前目录文件:
就能得知这段代码的功能是导入 ./svg
下的所有图标文件。
紧接着,跳转到 SvgIcon 看看它又是如何工作的:
<template>
<div
v-if="isExternal"
:style="styleExternalIcon"
class="svg-external-icon svg-icon"
v-on="$listeners"
/>
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
import { isExternal } from '@/utils/validate'
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
computed: {
isExternal() {
return isExternal(this.iconClass)
},
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
},
styleExternalIcon() {
return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
}
}
}
}
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover !important;
display: inline-block;
}
</style>
代码并不复杂,可以看到,它将图标分为了内部和外部两种,具体如下:
- 它接收两个字符类型的属性,
iconClass
和className
- 通过
iconClass
,它会计算出三个计算属性:isExternal
:用于判断是否为外部图标iconName
:用于指定use
的xlink:href
属性styleExternalIcon
:设定外部图标的样式
- 通过 className 则会计算出第四个计算属性:
svgClass
:显然这是图标类名
整体原理并不算特别复杂,就是利用 Vue.component 注册全局组件和 require.context 加载文件,接下来我们就简单尝试一下将其改为 vue3 版本吧。
Vue3 版本
组件注册
通过文档我们可以知道,vue3 中要注册全局组件需要通过 createApp
创建的实例进行注册,所以这里直接在 main 中引入 SvgIcon 即可:
import { createApp } from "vue";
import App from "./App.vue";
import SvgIcon from "@/components/SvgIcon/index.vue";
import "./icons";
export const app = createApp(App);
app
.component("svg-icon", SvgIcon)
.mount("#app");
图标组件
这里其实变化不大,将计算属性改一下即可:
<template>
<div
v-if="isExternal"
:style="styleExternalIcon"
class="svg-external-icon svg-icon"
v-on="$attrs"
/>
<svg v-else :class="svgClass" aria-hidden="true">
<use :xlink:href="iconName" v-on="$attrs" />
</svg>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from "vue";
import { isExternal as isExternalUtil } from "@/utils/validate";
export default defineComponent({
name: "index",
props: {
iconClass: {
type: String as PropType<string>,
required: true
},
className: {
type: String as PropType<string>
}
},
setup(props) {
const iconName = computed(() => `#icon-${props.iconClass}`);
const isExternal = computed(() => isExternalUtil(props.iconClass));
const styleExternalIcon = computed(() => {
return {
mask: `url(${props.iconClass}) no-repeat 50% 50%`,
"-webkit-mask": `url(${props.iconClass}) no-repeat 50% 50%`
};
});
const svgClass = computed(() => {
if (props.className) {
return "svg-icon " + props.className;
} else {
return "svg-icon";
}
});
return { isExternal, styleExternalIcon, iconName, svgClass };
}
});
</script>
<style scoped lang="scss">
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover !important;
display: inline-block;
}
</style>
最后的问题
一切都按部就班完成之后运行项目会发现还是无法使用图标组件,这是为什么呢?
究其原因是因为没有 svg 的 loader,具体可以参考官方文档。
这里我们也可以直接把 vue-element-admin 中的配置拷贝过来:
chainWebpack(config) {
// it can improve the speed of the first screen, it is recommended to turn on preload
config.plugin("preload").tap(() => [
{
rel: "preload",
// to ignore runtime.js
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
include: "initial"
}
]);
// when there are many pages, it will cause too many meaningless requests
config.plugins.delete("prefetch");
// set svg-sprite-loader
config.module
.rule("svg")
.exclude.add(resolve("src/icons"))
.end();
config.module
.rule("icons")
.test(/\.svg$/)
.include.add(resolve("src/icons"))
.end()
.use("svg-sprite-loader")
.loader("svg-sprite-loader")
.options({
symbolId: "icon-[name]"
})
.end();
config.when(process.env.NODE_ENV !== "development", config => {
config
.plugin("ScriptExtHtmlWebpackPlugin")
.after("html")
.use("script-ext-html-webpack-plugin", [
{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/
}
])
.end();
config.optimization.splitChunks({
chunks: "all",
cacheGroups: {
libs: {
name: "chunk-libs",
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: "initial" // only package third parties that are initially dependent
},
elementUI: {
name: "chunk-elementUI", // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
},
commons: {
name: "chunk-commons",
test: resolve("src/components"), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
});
// https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
config.optimization.runtimeChunk("single");
});
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!