Element UI:基于 Vue 2 的一个 UI 库,介绍它的基本概况以及源码架构
如果你在国内使用 Vue 2 开发站点网页,特别是后台管理之类的界面。那你肯定接触过 Element UI,这是由饿了么前端团队对外开源的一套 UI 库,国内使用非常广泛。
饿了么前端团队并没有针对 Element UI 进行 Vue 3 版本的升级,这部分工作由社区在开发和维护,为了与 Element UI 做区分,改叫 Element Plus 了。
虽然 Vue 3 发布已经有一段时间了,而且 Vue 2 也已经于 2023 年 12 月 31 号结束了官方更新支持。不过鉴于国内仍有大量使用 Vue 2 的项目在运行中,学习 Element UI 就显得比较有现实意义了——不管是代码维护,还是后续升级,了解 Element UI 的基本概况和源码架构都是必要的。
我们将从基本概况开始讲。
基本概况
安装
Element UI 支持以 npm 包或 CDN 方式安装使用。
一、CDN 方式
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入 Vue 2 -->
<script src="https://unpkg.com/vue@2"></script>
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
二、npm 包方式
安装 element-ui
依赖。
$ npm install element-ui
在项目的 main.js
文件中引入 Element UI。
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
以上我们就完成了 Element UI 的全局引入。如果需要按需引入,可以通过在 .babelrc 中接入 babel-plugin-component 实现。
国际化
Element UI 默认语言环境是中文,如果需要改成其他语言(比如:英文),可以在引入 Element UI 时,指定 locale
属性实现。
// 完整引入 Element
import Vue from 'vue'
import ElementUI from 'element-ui'
import locale from 'element-ui/lib/locale/lang/en'
Vue.use(ElementUI, { locale })
或是借助 Element UI 对外暴露出来的 locale
作用域对象上的 .use()
方法。
// 按需引入 Element
import Vue from 'vue'
import { Button, Select } from 'element-ui'
import lang from 'element-ui/lib/locale/lang/en'
import locale from 'element-ui/lib/locale'
// 设置语言
locale.use(lang)
// 引入组件
Vue.component(Button.name, Button)
Vue.component(Select.name, Select)
接下来,就来介绍 Element UI 提供的一些重要组件能力。
我们可以将这些组件按照功能点进行分类,包括:基础组件、表单组件、数据展示组件、导航类组件、反馈组件。下面分别它们进行概括性的介绍。
基础组件
这里主要包含在构建页面时常用的元素:布局、按钮、图标以及文字展示。
布局组件
布局组件有两种:一种是容器组件 <el-container>
/<el-header>
/<el-main>
/<el-footer>
/<el-aside>
,还有一种是由 24 分栏构成的布局组件 <el-row>
、<el-col>
。
容器组件 <el-container>
:
<template>
<el-container>
<el-header>Header</el-header>
<el-container>
<el-aside width="200px">Aside</el-aside>
<el-container>
<el-main>Main</el-main>
<el-footer>Footer</el-footer>
</el-container>
</el-container>
</el-container>
</template>
分栏组件 <el-row>
、<el-col>
:
<el-row :gutter="20">
<el-col :span="16"><div class="grid-content bg-purple"></div></el-col>
<el-col :span="8"><div class="grid-content bg-purple"></div></el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12"><div class="grid-content bg-purple"></div></el-col>
<el-col :span="12"><div class="grid-content bg-purple"></div></el-col>
</el-row>
<!-- 启用 Flex 布局 -->
<el-row type="flex" class="row-bg" justify="space-between">
<el-col :span="6"><div class="grid-content bg-purple"></div></el-col>
<el-col :span="6"><div class="grid-content bg-purple-light"></div></el-col>
<el-col :span="6"><div class="grid-content bg-purple"></div></el-col>
</el-row>
按钮组件
按钮是网页中非常常用的元素了,使用 <el-button>
表示。
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
图标组件
Element UI 内置一套图标集合。你可以通过设置 el-icon-[icon-name]
类名的方式启用。
<i class="el-icon-edit"></i>
<i class="el-icon-delete"></i>
当然,某些组件(比如:按钮组件)还通过 icon
prop 内置了图标支持。
<el-button type="primary" icon="el-icon-search">搜索</el-button>
文字链接
这类组件通常用作跳转链接或是执行某些特定操作。
<!-- 链接跳转 -->
<el-link href="https://element.eleme.io" target="_blank">默认链接</el-link>
<!-- 执行特定操作 -->
<el-link icon="el-icon-edit">编辑</el-link>
<el-link>查看<i class="el-icon-view el-icon--right"></i> </el-link>
表单组件
这类组件很多,我们这里只挑一些常用地说。包括:<el-form>
& <el-form-item>
、<el-input>
、<el-select>
& <el-option>
、<el-date-picker>
、<el-checkbox-group>
& <el-checkbox>
、<el-radio-group>
& <el-radio>
、<el-switch>
。
表单容器组件
按照约定,所以表单组件都应该写在 <el-form>
中,它的作用类似 HTML 中的 <form>
标签,不过做了一些功能增强。同时,每个表单组件还要包装在 <el-form-item>
中,它的作用类似 HTMl 的 <label>
标签,不过做了一些功能增强。
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="活动名称">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="活动形式">
<el-input type="textarea" v-model="form.desc"></el-input>
</el-form-item>
</el-form>
常用表单组件
包含输入框 <el-input>
、选择框 <el-select>
、开关按钮 <el-switch>
、日期选择 <el-date-picker>
。
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="活动名称">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="活动区域">
<el-select v-model="form.region" placeholder="请选择活动区域">
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
<el-form-item label="活动时间">
<el-col :span="11">
<el-date-picker type="date" placeholder="选择日期" v-model="form.date" style="width: 100%;"></el-date-picker>
</el-col>
</el-form-item>
<el-form-item label="即时配送">
<el-switch v-model="form.delivery"></el-switch>
</el-form-item>
</el-form>
单选、多选元素
Element UI 中,<el-radio>
、<el-checkbox>
用来表示单选、多选元素。这些元素通常成组出现,因此会放在对应的包装组件中(<*-group>
)。
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="活动性质">
<el-checkbox-group v-model="form.type">
<el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
<el-checkbox label="地推活动" name="type"></el-checkbox>
<el-checkbox label="线下主题活动" name="type"></el-checkbox>
<el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="特殊资源">
<el-radio-group v-model="form.resource">
<el-radio label="线上品牌商赞助"></el-radio>
<el-radio label="线下场地免费"></el-radio>
</el-radio-group>
</el-form-item>
</el-form>
数据展示组件
顾名思义,这类组件通常跟数据展示相关。
状态占位元素
这类组件有 3 种。一种是骨架屏组件 <el-skeleton>
,一种是空状态组件 <el-empty>
,还有一个是加载组件。
<el-skeleton :rows="6" animated />
<el-empty description="描述文字"></el-empty>
加载组件比较特殊,你既可以通过 v-loading
指令(绑定 boolean
值)的方式创建,也可以通过调用组件实例上的 .$loading()
方法创建。
<!-- 区域加载效果 -->
<template>
<el-table
v-loading="loading"
:data="tableData"
...
>
...
</template>
<template>
<el-button
type="primary"
@click="openFullScreen">
服务方式
</el-button>
</template>
<script>
export default {
data() {
return {
fullscreenLoading: false
}
},
methods: {
openFullScreen2() {
const loading = this.$loading({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
setTimeout(() => {
loading.close();
}, 2000);
}
}
}
</script>
对 v-loading
指令来说,Loading 遮罩默认会覆盖当前绑定元素上。而对以 .$loading()
方法创建的 Loading 遮罩默认整页加载效果,你可以通过设置 target
选项实现局部加载效果。
结果数据展示组件
这类组件有 4 种:一种是用于统计数据展示的统计组件 <el-statistic>
,一种是用于展示列表描述数据的组件 <el-descriptions>
& <el-descriptions-item>
,一种是用于描述操作结果的 <el-result>
组件,最后是表格组件 <el-table>
。
先来看看前三个的使用:
<el-statistic
group-separator=","
:precision="2"
:value="1314"
:title="'增长人数'"
></el-statistic>
<el-descriptions title="用户信息">
<el-descriptions-item label="用户名">kooriookami</el-descriptions-item>
<el-descriptions-item label="手机号">18100000000</el-descriptions-item>
</el-descriptions>
<el-result icon="success" title="成功提示" subTitle="请根据提示进行操作">
<template slot="extra">
<el-button type="primary" size="medium">返回</el-button>
</template>
</el-result>
<el-table>
的使用相对复杂一些,不过能够实现的功能也更加多样。
<template>
<el-table
:data="tableData"
style="width: 100%">
</el-table-column>
<el-table-column
prop="name"
label="姓名"
width="180">
</el-table-column>
<el-table-column
prop="address"
label="地址">
</el-table-column>
</el-table>
</template>
<script>
export default {
data() {
return {
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}]
}
}
}
</script>
修饰性小组件
这类组件包括:标记组件 <el-badge>
、头像组件 <el-avatar>
以及标签组件 <el-tag>
。
<template>
<el-badge :value="12" class="item">
<el-button size="small">评论</el-button>
</el-badge>
<el-avatar :size="50" src="https://picsum.photos/160/160"></el-avatar>
<el-tag>标签一</el-tag>
<el-tag type="success">标签二</el-tag>
<el-tag type="info">标签三</el-tag>
</template>
导航类组件
这类组件一共有 3 种。一种是用于网站导航菜单栏组件 <el-menu>
& <el-menu-item>
,一种是用于分隔页面内不同内容类型的标签页组件 <el-tabs>
& <el-tab-pane>
,最后还有用于展示页面路径的组件 <el-page-header>
(适应于简单场景) 和 <el-breadcrumb>
& <el-breadcrumb-item>
(适应于复杂场景) 。
<template>
<el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal" @select="handleSelect">
<el-menu-item index="1">处理中心</el-menu-item>
<el-menu-item index="2" disabled>消息中心</el-menu-item>
<el-menu-item index="3"><a href="https://www.ele.me" target="_blank">订单管理</a></el-menu-item>
</el-menu>
<el-tabs v-model="activeTab" @tab-click="handleClick">
<el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane>
<el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane>
</el-tabs>
<!-- 简单场景 -->
<el-page-header @back="goBack" content="详情页面"></el-page-header>
<!-- 复杂场景 -->
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item><a href="/">活动管理</a></el-breadcrumb-item>
<el-breadcrumb-item>活动列表</el-breadcrumb-item>
</el-breadcrumb>
</template>
反馈组件
这类组件主要有 3 种。一种是弹出框类型的 MessageBox、<el-dialog>
和 <el-popconfirm>
,一种是消息通知类型的 Notification 和 Message,一种是文字提示类型的 <el-tooltip>
和 <el-popover>
。
弹出框组件
MessageBox 是美化系统自带的 alert()
、confirm()
和 prompt()
的,因此适合展示较为简单的内容,稍微复杂一些的就要使用 <el-dialog>
组件。
MessageBox 是在 Element UI 全局注册后,可以通过 this.$alert()
、this.$confirm()
、this.$prompt()
访问到。
<template>
<el-button type="text" @click="open">点击打开 Message Box</el-button>
</template>
<script>
export default {
methods: {
open() {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// confirm
}).catch(() => {
// cancel
});
}
}
}
</script>
<el-dialog>
组件上则能实现内容丰富、结构灵活的内容展示。
<el-button type="text" @click="dialogVisible = true">点击打开 Dialog</el-button>
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
<script>
export default {
data() {
return {
dialogVisible: false
};
},
methods: {
handleClose(done) {
this.$confirm('确认关闭?')
.then(_ => {
done();
})
.catch(_ => {});
}
}
};
</script>
<el-popconfirm>
则是带有跟随功能的小型确认框组件,俗称“气泡确认框”。通常用于行内局部展示的确认操作。
<template>
<el-popconfirm
title="这是一段内容确定删除吗?"
>
<el-button slot="reference">删除</el-button>
</el-popconfirm>
</template>
消息通知组件
一共包含 2 种:Notification 和 Message。Element UI 在全局注册后,会以组件实例上方法的形式提供。
Notification 通过 this.$notify()
调用,展示侧边栏消息提示。
<template>
<el-button
plain
@click="open">
可自动关闭
</el-button>
</template>
<script>
export default {
methods: {
open() {
const h = this.$createElement;
this.$notify({
title: '标题名称',
message: h('i', { style: 'color: teal'}, '这是提示文案')
});
}
}
}
</script>
Message 则通过 this.$message()
调用出现,展示页面顶部消息提示。
<template>
<el-button :plain="true" @click="open">打开消息提示</el-button>
<el-button :plain="true" @click="openVn">VNode</el-button>
</template>
<script>
export default {
methods: {
open() {
this.$message('这是一条消息提示');
},
openVn() {
const h = this.$createElement;
this.$message({
message: h('p', null, [
h('span', null, '内容可以是 '),
h('i', { style: 'color: teal' }, 'VNode')
])
});
}
}
}
</script>
文字提示组件
这类提示一般是作为页面内容的补充信息提供的。目前有 2 种:<el-tooltip>
和 <el-popover>
。
<el-tooltip>
用于简单场景的文字补充提示,<el-popover>
相当于是前者的增强版,能展示更多的提示信息。
<el-tooltip class="item" effect="dark" content="Top Left 提示文字" placement="top-start">
<el-button>上左</el-button>
</el-tooltip>
<el-popover
placement="top-start"
title="标题"
width="200"
trigger="hover"
content="这是一段内容">
<el-button slot="reference">hover 激活</el-button>
</el-popover>
以上差不过是 Element UI 中所有可用组件的一个概况性介绍。各组件的具体使用,大家可以参照官方组件文档进行学习。
讲完 Element UI 的基本概况,我们再来浅谈它的源码架构。
源码架构
由于 Element UI 源码库牵涉的知识点太多。本节我们只从使用的角度分析 Element UI 源码的组织和打包方式。学有余力的同学可以在观看本节内容后,亲自去仓库中进行学习。
注意:源码基于目前最新的 v2.15.14 进行分析。
总体来说,在阅读 Element UI 源码后,你会有这样一种感受:组件代码实现上简单,UI 库打包流程则相对繁琐。
我们先从组件代码以及组件样式上进行分析。
组件结构
Element UI 组件的源码都位于仓库的 packages 目录下。
文档中 <el-[component-name]>
组件对应仓库 packages
目录下的 component-name
目录。比如 <el-button>
元素,就对应 button
目录,里面包含按钮组件的实现源码。
组件目录中包含 2 部分。
组件入口文件 index.js
,以及组件实现目录 src
。src
中会有一个跟目录同名的 .vue
文件,对应组件实现代码。
index.js
的作用主要是给组件部署 install()
方法,以 packages/button/index.js
为例。
import ElButton from './src/button';
/* istanbul ignore next */
ElButton.install = function(Vue) {
Vue.component(ElButton.name, ElButton);
};
export default ElButton;
如此一来,我们的组件除了 Vue.component()
手动注册的方式,还能支持 Vue.use()
自动注册的方式。
import Vue from 'vue';
import { Button, Select } from 'element-ui';
Vue.component(Button.name, Button);
// 或者
Vue.use(Button)
相比较来说,Vue.use(Button)
的部署方式更加简洁。
下面,我们点开 button.vue
文件内容查看。
<template>
<button
class="el-button"
@click="handleClick"
...
>
<span v-if="$slots.default"><slot></slot></span>
<button>
<template>
<script>
export default {
name: 'ElButton',
inject: {
elForm: {
default: ''
},
...
},
props: {
type: {
type: String,
default: 'default'
},
size: String,
icon: {
type: String,
default: ''
},
...
},
computed: {
...
buttonDisabled() {
return this.$options.propsData.hasOwnProperty('disabled') ? this.disabled : (this.elForm || {}).disabled;
}
},
methods: {
handleClick(evt) {
this.$emit('click', evt);
}
}
</script>
你会发现 button.vue
中只包含 <template>
和 <script>
标签,并没有 <style>
标签——这是因为 Element UI 的组件样式是单独打包的。
样式打包
Element UI 的样式文件位于 packages/theme-chalk
目录下。theme-chalk 是 Element UI 的默认主题,样式文件位于 src
目录下,采用 SCSS 语法编写。
theme-chalk
中存储了一个个跟组件名对应的 .scss
文件。比如 <el-button>
对应的 scss 文件是 button.scss
。
当然,还有一个特殊的 index.scss
文件,包含所有组件样式。接入 Element UI 时,我们引入的样式文件就是由它构建出来的。
Element UI 的样式是在单独的打包流程中的,对应 package.json
文件中的 "build:theme" 命令。
{
"scripts": {
"build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
}
}
node build/bin/gen-cssfile
:确保packages
目录下每个组件在packages/theme-chalk/src
目录中都有对应的.scss
文件gulp build --gulpfile packages/theme-chalk/gulpfile.js
:核心样式打包指令。Element UI 的样式使用 gulp 以管道任务的形式打包,gulpfile.js 文件做两件事- 将
.scss
文件处理成.css
文件,并进行文件压缩,输出到./lib
目录(即packages/theme-chalk/lib
) - 将
theme-chalk/fonts
目录下的两个字体文件(icon.scss
中引用),输出到./lib/fonts
目录
- 将
cp-cli packages/theme-chalk/lib lib/theme-chalk
:表示将packages/theme-chalk/lib
目录下编译出来的样式文件,复制到仓库根目录的lib/theme-chalk
目录下
以上,就完成了所有组件样式文件的打包工作。
组件打包
Element UI 组件的打包采用 webpack,在执行 npm run dist
指令时执行打包。我们来看一下 dist
指令。
{
"scripts": {
"dist": "npm run clean && npm run build:file && npm run lint && webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run build:utils && npm run build:umd && npm run build:theme",
}
}
非常长。不过,我们只需要关注 webpack --config 对应的 3 个指令即可。
说明:webpack 之前的指令主要在做代码清理、Linter 工作以及确保打包能够顺利进行的安全工作;而 webpack 之后的指令,主要在做 Element UI 周边的一些琐碎工作,包括:工具类打包、多语言文件打包(Locale)以及前一节说得主题样式打包。
先来看看 webpack --config build/webpack.conf.js
命令。
umd 格式打包
webpack --config build/webpack.conf.js
的作用是将 src/index.js
文件打包成 umd 格式,然后输出到 lib/index.js
文件。
src/index.js
文件作用类似 index.scss
文件,汇总了所有组件的入口文件,并做了一些必要的 Element UI 库的初始化工作。
这一步打包出来的文件,是为了能在浏览器中直接使用的,也就是我们熟知的 CDN 引入方式。
<!-- 引入样式(对应 npm run build:theme 指令的产物) -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库(对应 webpack --config build/webpack.conf.js 命令的产物) -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
再来看看 webpack --config build/webpack.common.js
。
CommonJS 格式打包
webpack --config build/webpack.common.js
的作用是提供 CommonJS 入口文件。这个命令会将src/index.js
文件打包成 CommonJS
格式,然后输出成 lib/element-ui.common.js
文件。
element-ui.common.js
文件要求宿主环境具备打包能力。也就说,宿主项目在打包时,也会打包 Element UI 的文件。
注意:
element-ui.common.js
文件在输出时,会经过 Babel 工具转译,将文件语法降级到 IE9+ 能够兼容的程序(参见 .babelrc 配置)。因此宿主项目不需要对 Element UI 文件在做转译工作了,直接打包就行。
另外,element-ui.common.js
被设置成项目的入口文件(package.json
的 "main"
字段)。
{
"main": "lib/element-ui.common.js",
}
因此,当你在项目中使用 Element UI 时,实际上就是在引入这个 lib/element-ui.common.js
文件。
import ElementUI from 'element-ui';
// 等价于
import ElementUI from 'element-ui/lib/element-ui.common.js';
最后,再来看看 webpack --config build/webpack.component.js
。
CommonJS 格式打包(针对一个个组件)
上一步我们完成了 Element UI 入口文件的打包工作,这是一种将 Element UI 代码全部引入项目的做法。
当然了,有时候项目中并不会用到所有组件,为了考虑到尺寸原因,需要支持按需引入。webpack --config build/webpack.component.js
命令就是用来单独打包组件源码的,让你能够按需引入。
这里的 Components
是从维护的 component.json
文件中读取的,对应 packages
目录下的所有组件的入口文件 index.js
。
我们从所有组件的入口文件出发,分别打包。如此,我们就完成了每个组件代码的单独打包。
最终,组件代码会被输出项目根目录下的 lib
目录中,以 [component-name].js
形式存在,配合 lib/theme-chalk
目录下的 [component-name].css
文件,就能实现按需引入了。
以上,我们就讲完了 Element UI 的源码架构部分。
总结
本文我们对 Element UI 这个 Vue 2 UI 库,做了一下基本概况介绍和源码架构的分析工作。
在“基本概况”中,我们按照功能点对 Element UI 所有重点组件做了分类。包括基础组件、表单组件、数据展示组件、导航类组件、反馈组件。然后进入每个分类,进行概况性的介绍。当然,具体使用时,大家可以参照官方组件文档进行学习。
在“源码架构”中,我们从使用的角度分析了 Element UI 源码的源码组织和打包方式。Element UI 对外提供了 2 中输出格式——umd 和 CommonJS,前者让你能够在浏览器中直接使用,后者则是在通过 npm 包使用的引入的版本,需要宿主环境具备打包能力。当然,Element UI 还单独对各组件进行打包了,让你能够按需引入。
好了,到这里差不多该结束了。感谢你的阅读,再见。
转载自:https://juejin.cn/post/7343905368622268452