Element Plus 组件库相关技术:7. 组件实现的基本流程及 Icon 组件的实现

news2025/7/19 6:32:50

前言

本章节我们将要实现 Icon 组件,Icon 组件应该是所有组件里面最简单的一个组件了,所以我们由简入深,循序渐进进行学习。Icon 组件虽然简单,但它却包含了一个组件的全部基础流程,通过实现 Icon 组件进行理解 Element Plus 组件实现的基本原理。

我们其实在上篇《6. CSS 架构模式之 BEM 在组件库中的实践》已经实现了最简易的一个 Icon 组件,本章节将继续完善它。

组件目录结构

首先我们按以下目录结构完善我们的 Icon 组件目录,其他组件的基本目录结构跟此类似。

├── packages
│   ├── components
│   │   ├── icon
│   │   │   ├── __tests__       # 测试目录
│   │   │   ├── src             # 组件入口目录
│   │   │   │   ├── icon.ts     # 组件属性与 TS 类型
│   │   │   │   └── icon.vue    # 组件模板内容
│   │   │   ├── style           # 组件样式目录
│   │   │   └── index.ts        # 组件入口文件
│   │   └── package.json
复制代码

通过上面的 Icon 组件目录结构,我们看出 Vue3 Composition API 的优势,可以根据逻辑功能来组织代码,从而实现高内聚,低耦合。上述 Icon 组件具体操作就是把组件属性与 TS 类型抽离放在了独立的一个文件中,这样就使得我们的程序代码在可维护性和灵活性方面可以做得非常好,从而让我们的项目维护成本降低。 没有 Composition API 之前 Vue 相关业务的代码需要配置到 Option 的特定的区域,导致代码可复用性不高,这样当项目非常庞大的时候会让后期的维护变得比较困难,从而导致项目成本增加。其实当你的项目非常庞大的时候,共享和复用代码则变得尤为重要。

定义组件属性 prop

我们知道父组件可以通过 prop 向子组件传递数据。首先需要在组件内部注册一些自定义的属性,称为 prop,这些 prop 是在组件的 props 选项中定义的。在使用组件的时候,就可以将在组件 props 选项中定义的属性名称作为组件元素的属性名来使用,通过属性向组件传递数据。

同时定义组件属性也是组件封装的一项重要步骤,首先我们在封装组件的时候,就要考虑我们的组件需要哪些属性,比如我们 Element Plus 中的 Icon 组件就只有下面两项属性。

属性名说明类型默认值
colorsvg 的 fill 颜色Pick<CSSProperties, 'color'>继承颜色
sizeSVG 图标的大小,size x sizenumber 、 string继承字体大小

我们希望父组件通过 prop 传递的数据类型是符合要求的,可以例如 Vue 提供的 prop 验证机制,在定义 props 选项时,可以使用一个带验证需求的对象。即在 packages/components/icon/src/icon.ts 文件进行如下定义:

export const iconProps = {
  color: String,
  size: [Number, String], // size 可以是数字,也可以是字符串
}
复制代码

验证的类型(type)可以是下列原生构造函数中的一个:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbool

单向数据流

通过 prop 传递数据是单向的,父组件的属性变化会向下传递给子组件,但是反过来不行。这可以防止子组件意外改变父组件的状态,从而导致组件的数据流难以理解。基于这个特性在我们使用 TypeScript 开发的时候,就会对 props 定义成只读属性。通过 as const 则可以快速将一个对象变成只读类型,常量断言可以把一个值标记为一个不可篡改的常量,从而让 TS 以最严格的策略来进行类型推断。

export const iconProps = {
  color: String,
  size: [Number, String], // size 可以是数字,也可以是字符串
} as const
复制代码

as const 是 TS 的语法,它告诉 TS 它所断言的值以及该值的所有层级的子属性都是不可篡改的,故对每一级子属性都会做最严格的类型推断。

但 TypeScript 的自动类型推断并不能推断出我们想要对象的类型。

在 TypeScript 中类有 2 种类型, 静态类型实例类型, 如果是构造函数类型, 那么返回的则是实例类型。我们在原生 Vue3 中定义 props 类型,其实是一个构造函数,比如上述我们定义 color 的类型是 String,但 String 只是一个构造函数,并不是 TypeScript 中的 string 类型,String 构造函数在 TypeScript 的类型是它的构造函数类型: StringConstructor ,但这并不是我们需要的,我们希望 String 构造函数返回的是字符串类型 string

在 Vue3 中提供了自带的 Props 类型声明:ExtractPropTypes ,它的作用是接收一个类型,然后把对应的所接收的 props 类型返回出来,同时如果是构造函数类型则转换成对应的类型,比如 StringConstructor 转换成 string

import type { ExtractPropTypes, PropType } from 'vue'

export const iconProps = {
  color: String,
  size: [Number, String] as PropType<number | string>,
} as const

export type Props = ExtractPropTypes<typeof iconProps>
复制代码

其中数组项,还需要通过 Vue3 内置的 PropType 类型声明进行具体的类型断言声明。

此外我们看到在导入相关类型声明的时候使用的是 import type,在此我们也稍微补充一些 import type 小知识:import type 仅仅导入被用于类型注解或声明的声明语句,它总是会被完全删除,因此在运行时将不会留下任何代码。与此类似的 export type 也是仅仅提供一个用于类型的导出,在 TypeScript 输出文件中,它也将会被删除。那么使用 import 的话,TypeScript 是无法判断你是想导出类型还是一个 JavaScript 的方法或者变量,而当你导入的是仅仅是类型的时候,当 TypeScript 编译之后,类型会被删除,你的代码就会报错,但通过TypeScript 的 isolatedModules 编译选项也可以进行预警这种写法是错误的。所以 TypeScript 提供了 import type or export type,用来明确表示我引入/导出的是一个类型,而不是一个变量或者方法。

import type xxx from 'xxx'
export type xxx
复制代码

最后我们还需要把 SFC 的 icon.vue 文件的实例类型返回出去:

import type Icon from './icon.vue'
export type IconInstance = InstanceType<typeof Icon>
复制代码

TypeScript 中的 InstanceType 函数:该函数返回(构造) 由某个构造函数构造出来的实例类型组成的类型。

在 Element Plus 中在创建 props 的类型定义的 TypeScript 类型是非常复杂的,日后有机会将在单独 TypeScript 的章节来展开说明,这里更多讲解的是组件的逻辑流程。

通过 script setup 编写 SFC 组件

我们在上一篇文章《6. CSS 架构模式之 BEM 在组件库中的实践》中已经实现了以下内容:

<template>
  <i :class="bem.b()">
    <slot />
  </i>
</template>

<script setup lang="ts">
import { useNamespace } from '@cobyte-ui/hooks'
const bem = useNamespace('icon')
</script>
复制代码

Icon 组件只是一个标签然后接收一个图标,所以 template 部分非常简单,一个 i 标签通过插槽接收图标内容,插槽也是父子组件通讯方式的一种,同时我们在上一篇中实现了 CSS 的 BEM 相关的逻辑,这一块内容本文便不再进行过多讲解了。

我们在上文中已经在 icon.ts 文件中定义好了 Icon 组件的 Props,接下来我们要在 icon.vue 中实现它。我们是通过 script setup 方式编写的 SFC 组件,那么通过这种方式编写的组件,我们则是通过 defineProps 编译宏命令来进行声明 props,同时声明的 props 会自动暴露给模板。

import { iconProps } from './icon'
const props = defineProps(iconProps)
复制代码

defineProps 会返回一个对象,其中包含了可以传递给组件的所有 props。

我们在 icon.ts 中定义了两个 Icon 组件的 props: size 和 color,然后用户可以通过这两个属性设置 Icon 组件的样式。接下来我们则要去实现这两个功能。

import type { CSSProperties } from 'vue'
// CSSProperties 是 Vue3 提供的 CSS 属性的类型
const style = computed<CSSProperties>(() => {
  if (!props.size && !props.color) return {}

  return {
    fontSize: isUndefined(props.size) ? undefined : addUnit(props.size),
    '--color': props.color, // 通过 CSS 变量方式进行设置 color
  }
})
复制代码

我们通过计算属性去计算出通过 props 传递过来的 size 和 color 属性得到 Icon 组件的样式。最后我们的 template 中需要在 i 标签中添加 style 的属性绑定::style="style"

Vue 组件中的 CSS 变量

CSS 变量(CSS variable)又叫做 "CSS 自定义属性"(CSS custom properties),声明变量的时候,变量名前面要加两根连词线(--),例如上文的颜色变量:--color。为什么选择两根连词线(--)表示变量?因为 $ 被 Sass 用掉了,@ 被 Less 用掉了。为了不产生冲突,官方的 CSS 变量就改用两根连词线了。

我们知道 Icon 的图标有可能是一个字体类型的图标,或者是 SVG 的图标,字体类型的图标直接可以通过设置 CSS 的 color 属性来设置图标颜色;而 SVG 图标可以在 SVG 文件中更改 fill 属性进行修改图片,将 fill 属性改成 currentColor,然后通过继承父元素 color 属性可以改变颜色。这样就同样可以通过设置 CSS 的 color 属性来设置图标的颜色了。

在 Vue 中可以通过在行内的 style 属性中定义 CSS 变量,然后就可以通过 Vue 的动态变量控制 CSS 变量,再在 style 标签中使用行内定义好的 CSS 变量。使用 CSS 变量可以通过 var 关键进行获取定义的 CSS 变量,例如:var(--color)

template 中的设置:

<template>
	<div class="content">
       <i class="el-icon" :style="{'--color': color}">
           
        </i> 
    </div>
</template>
复制代码

script setup 中的设置:

import { computed, ref } from 'vue'
const color = ref('green')
复制代码

通过以上设置就可以在 style 标签中通过 var 获取 .info 行内设置的 CSS 变量了。

.info {
    color: var(--color)
}
复制代码

最终渲染到页面的结果如下图:

变量范围

  • CSS 变量可以在任何元素内定义
  • 将 CSS 自定义属性添加到 :root 使其可用于页面中的所有元素
  • 如果在某个选择器内添加变量,则只可以在该选择器中可使用,这也就是 CSS 的变量作用域
  • 在 Vue 组件中,如果要该组件都可以使用,则必须放置在根元素下

在 Vue3 中的 SFC 组件中,可以在 style 标签通过 vars 进行绑定:<style vars="{ color }">

<template>
  <div class="text">稀土掘金</div>
</template>

<script>
export default {
  data() {
    return {
      color: "green",
    };
  },
};
</script>

<style vars="{ color }">
.text {
  color: var(--color);
}
</style>
复制代码

其中的原理就是这些变量会直接绑定到组件的根元素上,这就符合我们上面所说的 CSS 变量范围的规则了。

最后我们的主题样式中的 icon.scss 需要修改成以下内容:

@include b(icon) {
  --color: inherit;
  height: 1em;
  width: 1em;
  line-height: 1em;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  position: relative;
  fill: currentColor; /* 常规css中是没有fill属性的,只在XML-CSS中存在,用于设置当前元素的填充内容,例如颜色,图片 */ 
  color: var(--color);
  font-size: inherit;

  svg {
    height: 1em;
    width: 1em;
  }
}
复制代码

主要是字体颜色是通过 CSS 变量进行设置的,另外添加 fill 属性的内容,常规 CSS 中是没有 fill 属性的,只在XML-CSS 中存在,用于设置当前元素的填充内容,例如颜色,图片。这里主要是将 fill 属性改成 currentColor,然后 SVG 图片可以通过继承父元素 color 属性可以改变颜色。

组件注册的方式

我们去到 play 目录下的 src 目录中的 App.vue 文件中把上面写的 Icon 组件进行引入测试。在 Vue3 中有两种写 SFC 组件的方式,一种就是我们上面所介绍到的 script setup 方式,如果是通过 script setup 方式,那么相关代码如下:

<template>
  <div>
    <el-icon :color="'green'" :size="12">Icon</el-icon>
  </div>
</template>
<script setup lang="ts">
import ElIcon from '@cobyte-ui/components/icon'
import '@cobyte-ui/theme-chalk/src/index.scss'
</script>
<style scoped></style>
复制代码

还有一种就是不是使用 script setup 的方式,也就是使用 defineComponent 定义组件的方式,代码如下:

<template>
  <div>
    <el-icon :color="'green'" :size="12">Icon</el-icon>
  </div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import ElIcon from '@cobyte-ui/components/icon'
import '@cobyte-ui/theme-chalk/src/index.scss'

export default defineComponent({
  name: 'App',
  components: {
    ElIcon,
  },
  setup() {},
})
</script>
<style scoped></style>
复制代码

我们可以看到使用 defineComponent 定义组件,引入其他组件需要使用 components 选项进行注册,才能在 template 中使用。这两种都是属于局部注册组件的方式,我们知道除了局部注册组件,还可以进行全局注册组件。我们在本专栏的第一篇文章《Vue3 组件库的设计和实现原理》中已经解析了组件库的设计原理。在这里我们再进行复习和实践。

通过《Vue3 组件库的设计和实现原理》这篇文章我们可以知道 Vue3 组件库的基本实现原理:就是为每一个组件进行安装一个插件,然后通过插件进行组件的全局安装,再把所有的组件设置到一个数组中组成一个组件库(其实就是一个包含各种组件的数组),再编写一个组件库插件,在组件库插件里面进行循环数组组件库里的每一个组件,因为每一个组件都拥有插件所需的 install() 方法,所以每一个组件又是一个插件,又可以调用 use() 方法进行安装,最后就会执行每一个组件的 install() 方法,然后进行进行组件的全局安装,这样组件库里面的每个组件都将被注册到全局组件中去了。

又因为我们的组件库又许多组件,我们需要给每一个组件都添加一个 install 方法,我们可以把这个方法进行封装成为一个公共方法。我们在 packages/utils/vue/install.ts 中添加以下代码:

import type { Plugin } from 'vue'
// 通过 Vue 提供的 Plugin 类型和传进来的组件类型 T 的集合进行确定我们的组件类型具有 Plugin 类型方法,如 install 方法
export type SFCWithInstall<T> = T & Plugin
export const withInstall = <T>(comp: T) => {
  ;(comp as SFCWithInstall<T>).install = function (app) {
    // 组件的注册名称参数暂时是写死了 ElIcon,在后面的小节,我们再详细说明如何进行设置动态组件名称
    app.component('ElIcon', comp as SFCWithInstall<T>)
  }
  return comp as SFCWithInstall<T>
}
复制代码

注意,此时我们在 app.component('ElIcon', comp as any) 组件的注册名称参数是写死了 ElIcon,因为我们的组件是通过 script setup 方式实现的,无法设置组件的名称。在后面的小节,我们再详细说明如何进行设置动态组件名称。

packages/components/icon/index.ts 中的代码为:

import { withInstall } from '@cobyte-ui/utils'
import Icon from './src/icon.vue'
// 通过 withInstall 方法给 Icon 添加了一个 install 方法
const ElIcon = withInstall(Icon)
export default ElIcon
// 导出 Icon 组件的 props
export * from './src/icon'
复制代码

接下来,我们按组件库的方式在 play/main.ts 中进行全局安装引入 Icon 组件。

import { createApp } from 'vue'
import ElIcon from '@cobyte-ui/components/icon'
import '@cobyte-ui/theme-chalk/src/index.scss'
import App from './src/App.vue'
// 组件库
const components = [ElIcon]
// 是否已安装标识
const INSTALLED_KEY = Symbol('INSTALLED_KEY')
// 组件库插件
const ElementPlus = {
  install(app: any) {
    // 如果该组件库已经安装过了,则不进行安装
    if (app[INSTALLED_KEY]) return
    // 将标识值设置为 true,表示已经安装了
    app[INSTALLED_KEY] = true
    // 循环组件库中的每个组件进行安装
    components.forEach((c) => app.use(c))
  },
}

const app = createApp(App)
// 安装组件库
app.use(ElementPlus)
app.mount('#app')
复制代码

这个时候我们把 play/src/App.vue 中原来按需引入的 Icon 组件代码进行注释:

<script setup lang="ts">
// import ElIcon from '@cobyte-ui/components/icon'
// import '@cobyte-ui/theme-chalk/src/index.scss'
</script>
复制代码

然后我们再把 play 测试项目运行起来:pnpm run dev

这样我们通过全局注册的方式也把组件运行起来了。

全局组件类型声明

Vue3 并没有对自定义全局组件做 TypeScript 类型支持处理。

我们可以看到通过全局注册的 Icon 组件此时在模板中是没有任何类型提示的。我们如果想要全局注册的组件在模板中获得类型提示,就需要利用 TypeScript 的增强类型系统,进行扩展 Vue3 原来的类型系统。具体就是声明一个的 *d.ts 类型文件,通过使用声明文件对类型接口进行类型模块扩充并导出。

Vue3 SFC 文件的智能提示是 Volar 提供的, 其实在 Volar 的 README.md 文件中就有相关的提示:

Define Global Components

PR: vuejs/core#3399

Local components, Built-in components, native HTML elements Type-Checking is available with no configuration.

For Global components, you need to define GlobalComponents interface, for example:

// components.d.ts
declare module '@vue/runtime-core' {  // Vue 3
// declare module 'vue' {   // Vue 2.7
// declare module '@vue/runtime-dom' {  // Vue <= 2.6.14
  export interface GlobalComponents {
    RouterLink: typeof import('vue-router')['RouterLink']
    RouterView: typeof import('vue-router')['RouterView']
  }
}

export {}
/** 当我们的 tsconfig.json 中的 isolatedModules 设置为 true 时,如果某个 ts 文件中没有一个
import or export 时,ts 则认为这个模块不是一个 ES Module 模块,它被认为是一个全局的脚本,
这个时候在文件中添加任意一个 import or export 都可以解决这个问题。
**/
复制代码

参考 Volar 的文档,我们可以在根目录的 typings 文件夹下新建一个 components.d.ts 文件进行以下代码的实现:

import type Icon from '@cobyte-ui/components/icon'
// For this project development
import '@vue/runtime-core'

declare module '@vue/runtime-core' {
  // GlobalComponents for Volar
  export interface GlobalComponents {
    ElIcon: typeof Icon
  }
}

export {}
复制代码

通过上述设置我们的通过全局注册的 Icon 组件便有类型提示了。

script setup 组件的名称设置

我们在前面的小节进行全局注册组件的时候,组件名称是写死了的,那么写死了肯定是不行,我们需要动态设置组件的名称,我们现在这个小节就来解决这个问题。

我们知道通过 defineComponent 定义的 Vue3 组件是可以通过 name 属性进行设置组件的名称的

export default defineComponent({
  name: 'App',
  setup() {},
})
复制代码

而通过 script setup 方式则 Vue3 并没有提供设置组件名称的 API,但可以通过一种别捏的方式实现,代码如下:

<script lang="ts">
    export default{
        name: 'App'
    }
</script>
<script setup lang="ts">
// ...
</script>
复制代码

可以通过两个 script 标签来实现,但这种方式太不优雅了,在 Element Plus 中,官方开发者三咲智子 则提供了一个叫 unplugin-vue-define-options 来解决这个问题。

unplugin-vue-define-options

在 <script setup> 中可使用 defineOptions 宏,以便在 <script setup> 中使用 Options API。 尤其是能够在一个函数中设置 namepropsemit 和 render 属性。

特性

  • ✨ 有了这个宏,你就可以在 <script setup> 使用 Options API;
  • 💚 开箱即用支持 Vue 2 和 Vue 3;
  • 🦾 完全支持 TypeScript;
  • ⚡️ 支持 Vite、Webpack、Vue CLI、Rollup、esbuild 等, 由 unplugin 提供支持。

在根目录下进行安装

pnpm install unplugin-vue-define-options -D -w
复制代码

TypeScript 支持,在 tsconfig.web.json 文件中进行以下设置:

{
  "compilerOptions": {
    // ...
    "types": ["unplugin-vue-define-options/macros-global" /* ... */]
  }
}
复制代码

又因为我们的 play 项目中有使用到了 Vite,所以我们还需要 play 项目中的 vite.config.ts 文件中进行以下设置:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import DefineOptions from 'unplugin-vue-define-options/vite'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), DefineOptions()],
})
复制代码

这样我们就可以在 packages/components/icon/src/icon.vue 中使用 defineOptions 宏,在 <script setup> 中通过 Options API 设置组件的名称了。代码如下:

<script setup lang="ts">
    defineOptions({
  		name: 'ElIcon',
	})
</script>
复制代码

最后我们就可以在组件的安装插件的方法上进行动态设置组件名称了。

import type { Plugin } from 'vue'
// 通过 Vue 提供的 Plugin 类型和传进来的组件类型 T 的集合进行确定我们的组件类型具有 Plugin 类型方法,如 install 方法
export type SFCWithInstall<T> = T & Plugin
export const withInstall = <T>(comp: T) => {
  ;(comp as SFCWithInstall<T>).install = function (app) {
    // 动态设置组件的名称
    const { name } = comp as unknown as { name: string }
    app.component(name, comp as SFCWithInstall<T>)
  }
  return comp as SFCWithInstall<T>
}
复制代码

Icon 中的图标

SVG 组件图标

我们在上面已经稍微讲过,Icon 中的图标有两种方式实现,其中一种是通过 SVG 图片,而通过 SVG 图片的实现方式本质就是实现一个 SVG 的组件。在 Vue3 中的实现是非常简单的,代码如下:

<template>
  <svg
    viewBox="0 0 1024 1024"
    xmlns="http://www.w3.org/2000/svg"
    data-v-029747aa=""
  >
    <path
      fill="currentColor"
      d="M832 512a32 32 0 1 1 64 0v352a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32h352a32 32 0 0 1 0 64H192v640h640V512z"
    />
    <path
      fill="currentColor"
      d="m469.952 554.24 52.8-7.552L847.104 222.4a32 32 0 1 0-45.248-45.248L477.44 501.44l-7.552 52.8zm422.4-422.4a96 96 0 0 1 0 135.808l-331.84 331.84a32 32 0 0 1-18.112 9.088L436.8 623.68a32 32 0 0 1-36.224-36.224l15.104-105.6a32 32 0 0 1 9.024-18.112l331.904-331.84a96 96 0 0 1 135.744 0z"
    />
  </svg>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'EditIcon',
})
</script>
复制代码

其中 template 中的内容便是一张 SVG 图片的源码,而 SVG 图片的源码获取方式有很多,我们这里提供其中一种,我们可以把设计师设计的图标上传到阿里的 iconfont 图标库,然后在下载该图标的时候,可以选择复制 SVG 源码。

有了 SVG 图标的组件之后,我们就可以直接导入在 Icon 组件中进行使用了。

<template>
  <div>
    <el-icon :color="'green'" :size="15"><EditIcon /></el-icon>
  </div>
</template>
<script setup lang="ts">
import EditIcon from './EditIcon.vue'
</script>
<style scoped></style>
复制代码

运行结果如下:

我们可以看到我们的图标成功渲染出来了。

字体图标

我们还可以通过字体图标进行设置:

<template>
  <div>
      <el-icon :color="'green'" :size="15">
      	<i class="iconfont iconlogistics-car"></i>
      </el-icon>
  </div>
</template>
<script setup lang="ts"></script>
<style>
@font-face {
  font-family: 'iconfont';
  src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAPIAAsAAAAACDAAAAN8AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACDMgqDKIJ8ATYCJAMUCwwABCAFhGcHXRszB8ieGjszMAURLRpbqK1NZLgai+Bpjd+83b37rkkUPJlGTXgTT1Q8iYdGtZBInqmizTP5JueqHACqHCL0ukm3u5zmUBE5JMlCIiqkp/Fvf/7/Tv0JTozFtidAC2TM7gl21TvSDl1Am7qfB7DYYs8/WVNbJGPOvRCigKCAWDUvXH2FqqsDgBEpuczPzki9YezKi3iOQLvV4mnu8nJLgemFOlUAR9qMziDOjQt9jgH63KDU5IZWoW67sYhHKtLtdMFD//vjn7WhT1JlxjH7T6WJwO4vsajNnU0n+Su3Q4CTEypkLF+ZKS7WOy7QCsPLaWkvG65tG6BdqySN2i+2qPxZahpiyd5U7eY/PEKSFaJmZHeCbShS+GlrIwQ/YxESPysRMj9rRW+nvALtsOg1wHOsmRzzL5IQXV0r2kzGaq3jaLnF+/VicyC71TjxsrzcdtZ20pCJG88Y4c4TKNefsuzjtxfkuCfx/iB25DlF3RiftC3VEO8xZFM2A0dvvO7J4xbdb/Dzbn5g56Vx48CdycHBDQeFt09l735ICJWYiystE0rLmTzH+aew04a1cR72nVcdHZHCyh8UhZbPmZ99R0vcYoHNW8yxRb45HFSQVL7tbneSLJMwwGzJPEoPP7L7Kzr+FhzVSmlQNP3r8HLHTW1uTh22uJ5mODzg2H9/M2RxRIVPtldGhle2zxbiIm7ETaq3xGEVEWLX0etr2H3G1qBe/aFD8yrrADTzzQyQLtFTkKU/+jve5Sz732AW+1+/o4AfK/GoX9YoMv6IUmvB/1Mea4laVB5SlrJm2yaoRvsTXom/1PDDPP3exlENtM4mtBqIIWkxAVmrKWwhLkGlw1qotdoM7ZZJ295hgIqWKG2Y0wMg9FqFpNsLyHqdYAvxHirDfkOt1z9odyBMDuswF6xNEEORCDa1Q55VKrClpn6vxYhrljGkKFCOiMBDHwzyD8zGMpACkSGWCC1cMKUYYqKUw3R6HZLJlFBFlBLEUn8xparogABc9iX+rFIOUhAIg0JEoCbtIB5LSQG76DT984ohnGYyDNISWx4mBDzsHQriL7AFNkOhaNVyJccIWnCCURQGYQRE5aB0bkNkSlgJUpWPk0BYlD9xj6BKtAATDbdV+M+vkj/gLmhnHFFFihwlqqh1XZhyvHAdKYIfyxCDO8jzo0ighmFbELUHAwA=')
    format('woff2');
}

.iconfont {
  font-family: 'iconfont' !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.iconlogistics-car:before {
  content: '\e616';
}
</style>
复制代码

运行结果:

字体图标也是可以通过阿里的 iconfont 图标库进行设置,这里就不作过多深入讲解了。

Element Plus 中的图标库

Element Plus 中的图标库又是通过一个独立的项目进行实现的,也就是你安装了 Element Plus 库之后,还要使用她的图标库,那么你还需要安装一个 npm 包:@element-plus/icons-vue。它的实现原理也就是我们上面的第一种 SVG 图标组件的方式,通过遍历读取每个 SVG 图标的源码,然后生成各自的 SVG 图标组件,然后引入的时候是通过全局注册的方式进行使用。这里就不作过多深入解析了。

总结

在本章节中我们通过实现 Icon 组件进行理解 Element Plus 组件实现的基本原理,Icon 组件虽然简单,但它却包含了一个组件的全部基础流程。

通过 icon 组件目录结构,我们看出 Vue3 Composition API 的优势,可以根据逻辑功能来组织代码。通过定义组件属性 prop,我们了解一些组件 prop 的特性,通过 prop 传递数据是单向的,父组件的属性变化会向下传递给子组件,但是反过来不行。这可以防止子组件意外改变父组件的状态,从而导致组件的数据流难以理解。

接着在实现 Icon 组件的过程,我们学习了如何在 Vue 组件中使用 CSS 变量。 Vue 中可以通过在行内的 style 属性中定义 CSS 变量,然后就可以通过 Vue 的动态变量控制 CSS 变量,再在 style 标签中使用行内定义好的 CSS 变量。

接着我们通过局部注册组件和全局注册组件的方式实现了 Icon 组件在 play 项目中的渲染展示。然而 Vue3 并没有对自定义全局组件做 TypeScript 类型支持处理,我们学习了利用 TypeScript 的增强类型系统,进行扩展 Vue3 原来的类型系统。

我们还学习了 Element Plus 中通过使用 defineOptions 宏,在 <script setup> 中使用 Options API 来解决 <script setup> 无法设置组件名称的问题。

最后我们讲解了在 Icon 组件中如何实现图标的,主要有两种方式,一种是 SVG 组件图标,一种是字体图标。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/37913.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

疫情失业之下,测试的未来在哪里

前天和测试圈子里一个朋友聊了关于今年求职招聘市场行情和个人认知以及发展副业的话题。 聊起了今年的求职招聘行情&#xff0c;他说他们公司已经裁了一波人了&#xff0c;估计年底还会有一波裁员。 今年的市场冷的有点吓人&#xff0c;在这么下去&#xff0c;他也会担心自己…

nacos实现负载均衡、权重

文章目录一、nacos服务分级存储模型二、Nacos-NacosRule 实现负载均衡三、nacos-服务实例的权重设置一、nacos服务分级存储模型 修改 application.yml 配置文件&#xff1a; spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: HZ #集群位置&#xff0c…

Linux C/C++ 学习笔记(九):百万并发的服务器实现

本文内容参考自(2条消息) Linux C/C 开发&#xff08;学习笔记十三)&#xff1a;百万并发的服务器实现_菊头蝙蝠的博客-CSDN博客_linux百万并发 一、connection_refuesed ---->文件系统最大的进程fd个数 nat 模式&#xff0c;物理机的VMnet8网卡&#xff0c;连接到了VMnet…

selenium--关闭窗口,指定窗口大小,前进,后退,刷新等等

关闭窗口跳转到指定页面窗口大小设置返回上个页面前进到下一个页面页面刷新关闭窗口 在selenium中执行完关闭窗口一般有两种方法&#xff1a; driver.close() driver.quit()这两个都是常用的方法&#xff0c;但是他们有什么区别呢&#xff1f; 对于driver.close(),他是关闭当…

【FME实战教程】003:FME读取地理空间数据(矢量、栅格、点云、三维模型、数据库、地理服务)大全

FME读取地理空间数据&#xff08;矢量、栅格、点云、三维模型、空间数据库、地理服务&#xff09;大全。 文章目录1. FME读取数据1.1 读取矢量1.1.1 读取Shapefile1.1.2 读取dwg1.2 读取栅格数据1.2.1 影像DOM1.3 读取地理数据库1.3.1 读取文件数据库&#xff08;.gdb&#xff…

机械原理复习试题

​ 编辑切换为居中 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; ​ 编辑切换为居中 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; ​ 编辑 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; ​ 编辑…

聚类分析的基本概念和方法

聚类分析的基本概念和方法 文章目录聚类分析的基本概念和方法前言一、什么是聚类分析1、聚类分析基本流程与步骤2、 什么是好的聚类方法3、聚类的模型评估4、聚类分析的比较5、聚类分析的挑战二、基本聚类方法概述三、划分算法1、基本概念2、k-means 聚类方法1、k-means 方法的…

CMake中configure_file的使用

CMake中的configure_file命令用于将一个文件拷贝到另一个位置并修改其内容&#xff0c;其格式如下&#xff1a; configure_file(<input> <output>[NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |FILE_PERMISSIONS <permissions>...][COPYONLY] [ESCAPE_…

01 一条SQL 语句是如何执行的?

select * from teacher where id 10 1、一条简单的sql语句底层的执行过程是怎么样的&#xff1f; 答&#xff1a;一条sql执行会经过连接器、查询缓存、分析器、优化器和执行器等步骤。 2、连接器的作用是什么&#xff1f; 答&#xff1a;sql查询&#xff0c;首先连接到这个数…

【机器学习项目实战10例】(四):利用XGBoost实现短期电力负荷预测

💥 项目专栏:【机器学习项目实战10例】 文章目录 一、利用XGBoost实现短期电力负荷预测二、数据集介绍三、将数据进行标准化四、形成训练数据五、划分训练集、测试集六、定义模型七、模型训练八、训练集、测试集验证九、网络搜索十、绘制结果一、利用XGBoost实现短期电力负荷…

分布式事务

一、事务 1.1、什么是事务&#xff1f; 事务&#xff08;transaction&#xff09;是访问并操作数据库中数据的一个程序执行单元&#xff0c;由开始事务和提交事务之间的所有的语句组成。事务的结束有两种&#xff0c;一个是事务中间的所有操作执行成功&#xff0c;提交事务。一…

UE5笔记【九】蓝图BluePrint;

新建一个第三视角游戏。然后打开关卡蓝图。 长得跟材料编辑器一样。 这里是我们创建Node和新功能的地方。 首先我们新建一个游戏开始的地方。右键&#xff1a;Begin搜索。 我们需要打印一行字&#xff1a;欢迎来到游戏世界。我们需要添加一个打印文本的结点&#xff1a;PrintT…

APS自动排产 — 排产结果拉动物料需求计划

一、APS系统生产计划前应该注意哪些 建立好基础资料 标准产能&#xff1a;所有产品的标准产能&#xff0c;来自于工程技术部。如果工程技术部无法提供标准产能&#xff0c;则请生产部门根据实际提供相对准确的标准产能。技术资料&#xff1a;产品的物料清单(BOM)、图纸、工程…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java线上学习系统8e88w

做毕业设计一定要选好题目。毕设想简单&#xff0c;其实很简单。这里给几点建议&#xff1a; 1&#xff1a;首先&#xff0c;学会收集整理&#xff0c;年年专业都一样&#xff0c;岁岁毕业人不同。很多人在做毕业设计的时候&#xff0c;都犯了一个错误&#xff0c;那就是不借鉴…

ArcGIS中ArcMap图层属性表的中文字段乱码的解决方法

本文介绍ArcMap软件打开图层的属性表后&#xff0c;出现字段中汉字乱码情况的解决方法。 有时在使用ArcMap软件时&#xff0c;会发现一些图层的属性表中&#xff0c;原本应该是中文的字段却出现乱码的情况&#xff1b;如下图所示&#xff0c;其中NAME99一栏应该是图层中各个要素…

50-51 - C++对象模型分析

---- 整理自狄泰软件唐佐林老师课程 1. 回归本质 1.1 class是一种特殊的struct 在内存中class依旧可以看作 变量的集合class与struct遵循相同的 内存对齐 规则class中的成员函数与成员变量是 分开存放 的 每个对象有独立的成员变量所有对象共享类中的成员函数 1.2 值得思考…

OpenGL ES 学习(四) -- 正交投影

这里的内容基本参考于 https://www.jianshu.com/p/51a405bc52ed &#xff0c;因为写得很好&#xff0c;也没啥补充的&#xff0c;就当做记录一下。 这里先简单解决变形的问题&#xff0c;关于 OpenGL 更多图形矩阵变换&#xff0c;等后面再详细讲。 一. 归一化设备坐标 在Ope…

forplo | 冲冲冲!这个画森林图的包好flexible哦~

1写在前面 我想大家肯定都用过森林图&#xff0c;应用比较多的场景可能是展示meta分析&#xff0c;回归分析结果的时候。&#x1f973; 画森林图的包还是挺多的&#xff0c;今天介绍一下forplo包的用法。&#x1f618; 2用到的包 rm(list ls())library(tidyverse)library(forp…

(第九十三篇)C规范编辑笔记(五)

往期文章&#xff1a; C规范编辑笔记(一) C规范编辑笔记(二) C规范编辑笔记(三) C规范编辑笔记(四) 正文&#xff1a; 继第四篇C规范编辑笔记之后&#xff0c;我们今天来分享第五篇C规范编辑笔记&#xff0c;讲解部分类型初始化时候的建议&#xff0c;话不多说&#xff0c;我…

wodP2P ActiveX 最新版 Crack

wodP2P ActiveX 组件 网络P2P ActiveX 客户端 OCX/DLL&#xff0c;V-P-N 组件&#xff0c;P2P 组件&#xff0c;P2P 库 wodP2P 是 P2P 点对点 ActiveX 组件&#xff0c;用于在两个对等点之间建立虚拟专用网络。所有 P2P 通信都经过加密和保护。对等点能够转发本地和远程端口、发…