文章目录
一、简介
饿了么开源组件库
github 4w+ star
访问官网、github
Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库
二、基本用法
- 初始化项目
vue create element-test -d // 默认创建方式,这步可能会出现vue-cli版本需要更新,或是网络问题
- 运行一下看看效果
cd element-test
npm run serve
- 安装
cnpm i element-ui -S
- 使用 ElementUI 插件(main.js)
import ElementUI from 'element-ui'
Vue.use(ElementUI)
- 引用样式(main.js)
import 'element-ui/lib/theme-chalk/index.css'
- element-ui 案例
<template>
<div id="app">
<el-button @click="show">点我</el-button>
</div>
</template>
<script>
export default {
name: 'app',
methods: {
show() {
this.$message.success('Toast from element-ui')
}
}
}
</script>
三、按需加载
- 对项目进行打包:
npm run build
- 发现 vendors 库高达 近800KB
-rw-r--r-- 1 onc-virn 197121 801539 4月 10 11:51 chunk-vendors.bf819743.js
- 这是由于我们未使用按需加载,所以对 element-ui 进行全量打包的结果,按需加载的用法如下:
- 安装 babel-plugin-component
cnpm i -D babel-plugin-component
- 修改 babel.config.js:
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
- 按需引入 Button 和 Message
import { Button, Message } from 'element-ui'
Vue.component(Button.name, Button)
Vue.prototype.$message = Message
- 来代替如下内容
import ElementUI from ‘element-ui’
import ‘element-ui/lib/theme-chalk/index.css’
Vue.use(ElementUI)
重新构建,vendors 体积减小到 113KB
-rw-r--r-- 1 onc-virn 197121 114301 4月 10 12:22 chunk-vendors.c4537f82.js
四、插件引用
- 创建一个新项目
vue create element-test2 -d
- 可以通过 element 插件快速集成 element-ui
vue add element
- 安装过程中的选择如下:
? How do you want to import Element? Fully import
? Do you wish to overwrite Element's SCSS variables? No
? Choose the locale you want to load zh-CN
- 运行完毕可见新增或修改如下:
- 新增
src\plugins\element.js
import Vue from 'vue'
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(Element)
- 在main.js中新增如下:
import './plugins/element.js'
- App.vue新增如下:
<div>
<p>
If Element is successfully added to this project, you'll see an
<code v-text="'<el-button>'"></code>
below
</p>
<el-button>el-button</el-button>
</div>
package.json新增如下:
"dependencies": {
"element-ui": "^2.4.5",
},
"devDependencies": {
"vue-cli-plugin-element": "~1.0.1",
},
然后就是yarn.lock中的一些改变,暂时不用理会。
五、表单基本用法
- el-form 容器,通过 model 绑定数据(inline保证控件在一行)
- el-form-item 容器,通过 label 绑定标签
- 表单组件通过 v-model 绑定 model 中的数据
<template>
<div id="app">
<el-form inline :model="data">
<el-form-item label="审批人">
<el-input v-model="data.user" placeholder="审批人"></el-input>
</el-form-item>
<el-form-item label="活动区域">
<el-select v-model="data.region" placeholder="活动区域">
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
data: {
user: 'sam',
region: '区域二'
}
}
},
methods: {
onSubmit() {
console.log(this.data)
}
}
}
</script>
六、表单校验基本用法
首先修改data部分:
data() {
const userValidator = (rule, value, callback) => {
if (value.length > 3) {
callback()
} else {
callback(new Error('用户名长度必须大于3'))
}
}
return {
data: {
user: '',
region: ''
},
rules: {
user: [
{ required: true, trigger: 'change', message: '用户名必须录入' },
{ validator: userValidator, trigger: 'change' }
]
}
}
}
然后绑定校验规则,使用:rules="rules"
绑定到 el-form 或 el-form-item
<el-form inline :model="data" :rules="rules">
指定 el-form-item 的 prop 属性:prop="user"
<el-form-item label="审批人" prop="user">
<el-input v-model="data.user" placeholder="审批人" clearable></el-input>
</el-form-item>
rules
- required:必填项
- trigger:触发条件(例如:change)
- message:错误提示内容
- validator:绑定其他详细验证规则
在表单提交时也进行验证,需要在表单后增加ref="form"
<el-form inline :model="data" :rules="rules" ref="form">
然后修改提交表单的方法为如下:
onSubmit() {
console.log(this.data)
this.$refs.form.validate((isValid,errors)=>{
console.log(isValid,errors)
})
}
表单校验相关属性
- hide-required-asterisk:隐藏必录标识
- inline-message:验证消息是否在一行显示
七、表单校验高级用法
用法一:动态改变校验规则
使rules
只包含一个校验规则
{
rules: {
user: [
{ required: true, trigger: 'change', message: '用户名必须录入' },
]
}
}
新增按钮触发动态添加 rules
:
<el-button type="primary" @click="addRule">添加校验规则</el-button>
addRule() {
const userValidator = (rule, value, callback) => {
if (value.length > 3) {
this.inputError = ''
this.inputValidateStatus = ''
callback()
} else {
callback(new Error('用户名长度必须大于3'))
}
}
const newRule = [
...this.rules.user,
{ validator: userValidator, trigger: 'change' }
]
this.rules = Object.assign({}, this.rules, { user: newRule }) // 会将新旧两个rule合并为一个
}
去掉之前的校验规则userValidator
表单中添加属性:
validate-on-rule-change
就会在新加规则后立即校验(默认为true
,不添加也可)
- 使用:this.rules = Object.assign({}, this.rules, { user: newRule })
- 不使用:
this.rules.user.push(newRule)新增新规则时必须重新生成一个新的
rules
对象,因为watch
只能监听到rules
,而监听不到它内部的变化
<el-form inline :model="data" :rules="rules" ref="form" validate-on-rule-change>
用法二:手动控制校验状态
validate-status
:验证状态,枚举值,共四种:
success
:验证成功error
:验证失败validating
:验证中(空)
:未验证error
:自定义错误提示
在data()
中新增error
和status
元素:
return {
error: '',
status: '',
data: {
user: '',
region: ''
},
...
设置 el-form-item
属性
<el-form-item
label="用户名"
prop="user"
:error="error"
:validate-status="status"
>
在el-form
标签添加status-icon
属性
<el-form inline :model="data" :rules="rules" ref="form" validate-on-rule-change status-icon>
新增按钮:
<el-button type="success" @click="showSuccess">校验成功</el-button>
<el-button type="danger" @click="showError">校验失败</el-button>
<el-button type="warning" @click="showValidating">校验中</el-button>
在methods
中新增自定义 status
和 error
的方法
showError() {
this.status = 'error'
this.error = '用户名输入有误'
},
showSuccess() {
this.status = 'success'
this.error = ''
},
showValidating() {
this.status = 'validating'
this.error = ''
}
八、表单属性解析
label-position
:标签位置,枚举值,left 和 toplabel-width
:标签宽度label-suffix
:标签后缀inline
:行内表单disabled
: 设置整个 form 中的表单组件全部 disabled,优先级低于表单组件自身的
disabled
在node_modules\element-ui\packages\form\src
下找到form.vue
可以看到,里面并没有对disabled
进行处理,而是使用provide() {return {elForm: this};}
将自身传递给了后代,间接控制,让它们进行处理。
比如:下面el-input
中,先判断自身是否含有disabled
属性,再判断祖先是否含有。
/* el-input 源码 */
inputDisabled() {
return this.disabled || (this.elForm || {}).disabled;
}
size:设置表单组件尺寸
size
属性也是通过provide()
和inject:[]
实现的
/* el-input 源码 */
inputSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
}
/* el-form-item 源码 */
elFormItemSize() {
return this.size || this._formSize;
},
_formSize() {
return this.elForm.size;
}
可以说,el-form-item
在这一体系统起到承上启下的作用,而provide()
和inject:[]
功能的强大也是有目共睹的
/* el-form 源码 */
provide() {
return {
elForm: this
};
},
/* el-form-item 源码 */
provide() {
return {
elFormItem: this
};
},
inject: ['elForm'],
/* el-input 源码 */
inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
},
九、分析element-ui源码
1.入口文件分析
node_modules\element-ui\src\index.js
是element-ui
的入口文件
Vue.use(Element)
调用element-ui
入口文件的install
方法- 在
install
方法中,通过如下方法加载全部组件:
components.forEach(component => {
Vue.component(component.name, component);
});
- 而所有的组件都存在于
components
数组中
因此按需加载使用
Vue.component(Button.name, Button)
2.form.vue
- 容器功能是通过插槽来实现的,它的自带属性只有两个
labelPosition
和el-form--inline
:
<template>
<form class="el-form" :class="[
labelPosition ? 'el-form--label-' + labelPosition : '',
{ 'el-form--inline': inline }
]">
<slot></slot>
</form>
</template>
- form组件的入口文件是上级目录的index.js,很简单,完成如下操作:
ElForm.install = function(Vue) {
Vue.component(ElForm.name, ElForm);
};
- 在props下面定义了一系列属性;
- watch主要监听了rules的变化:
watch: {
rules() {
// remove then add event listeners on form-item after form rules change
this.fields.forEach(field => {
field.removeValidateEvents();
field.addValidateEvents();
});
if (this.validateOnRuleChange) {
this.validate(() => {});
}
}
},
- computed主要计算label的自动宽度:
computed: {
autoLabelWidth() {
if (!this.potentialLabelWidthArr.length) return 0;
const max = Math.max(...this.potentialLabelWidthArr);
return max ? `${max}px` : '';
}
},
- methods(这里就可以体现出关联组件,解耦代码的思想,每个组件仅仅做自己事,form只做了统一的管理与调度,真正干活的还是小弟):
// 重置所有字段
resetFields() {},
// 清除验证,如果不传prop就清除所有
clearValidate(props = []) {},
// 验证方法
validate(callback) {},
// 指定字段进行验证
validateField(props, cb) {},
// 获取label的下标
getLabelWidthIndex(width) {},
// 重置labelwidth
registerLabelWidth(val, oldVal) {},
// 移除此宽度
deregisterLabelWidth(val) {}
- 验证方法
// 对整个表单进行验证
validate(callback) {
// 没有表单数据 抛警告跳出
if (!this.model) {
console.warn('[Element Warn][Form]model is required for validate to work!');
return;
}
let promise;
// 没有callback并且浏览器支持Promise return promise
if (typeof callback !== 'function' && window.Promise) {
promise = new window.Promise((resolve, reject) => {
callback = function(valid) {
valid ? resolve(valid) : reject(valid);
};
});
}
let valid = true;
let count = 0;
// 如果需要验证的fields为空,调用验证时立刻返回callback
if (this.fields.length === 0 && callback) {
callback(true);
}
let invalidFields = {};
// 遍历所有实例,一个个验证
this.fields.forEach(field => {
// 这里的validate是form-item的方法,实际操作在form-item中执行
field.validate('', (message, field) => {
// 如果有返回信息, 则说明验证失败
if (message) {
valid = false;
}
// 将错误对象复制到invalidFields
invalidFields = objectAssign({}, invalidFields, field);
// 调callback
if (typeof callback === 'function' && ++count === this.fields.length) {
callback(valid, invalidFields);
}
});
});
if (promise) {
return promise;
}
}
3.form-item.vue
- 模板结构:
- 可看出验证提示效果可以自定义
<div class="el-form-item" :class="[{
'el-form-item--feedback': elForm && elForm.statusIcon,
'is-error': validateState === 'error',
'is-validating': validateState === 'validating',
'is-success': validateState === 'success',
'is-required': isRequired || required
},
sizeClass ? 'el-form-item--' + sizeClass : ''
]">
<!-- 表单域标签文本 -->
<label :for="labelFor" class="el-form-item__label" :style="labelStyle" v-if="label || $slots.label">
<slot name="label">{{label + form.labelSuffix}}</slot>
</label>
<div class="el-form-item__content" :style="contentStyle">
<!-- 插槽接收表单验证的元素,input框单选框多选框之类的 -->
<slot></slot>
<!-- 验证不通过时的message -->
<transition name="el-zoom-in-top">
<div
v-if="validateState === 'error' && showMessage && form.showMessage"
class="el-form-item__error"
:class="{
'el-form-item__error--inline': typeof inlineMessage === 'boolean'
? inlineMessage
: (elForm && elForm.inlineMessage || false)
}"
>
{{validateMessage}}
</div>
</transition>
</div>
</div>
- 监听验证状态和信息
watch: {
error: {
immediate: true, // 立即显示
handler(value) {
// 获取验证信息
this.validateMessage = value;
// 判断验证状态
this.validateState = value ? 'error' : '';
}
},
// 验证状态
validateStatus(value) {
this.validateState = value;
}
},
- 验证方法
validate(trigger, callback = noop) {
this.validateDisabled = false;
// 符合规则的trigger
const rules = this.getFilteredRule(trigger);
// 没有规则也不是必填 返回true
if ((!rules || rules.length === 0) && this.required === undefined) {
// 执行回调
callback();
return true;
}
// 状态改为验证中
this.validateState = 'validating';
const descriptor = {};
// 为了匹配AsyncValidator插件所需要的格式,需要做规则数据做一些操作
if (rules && rules.length > 0) {
rules.forEach(rule => {
delete rule.trigger;
});
}
// AsyncValidator需要的验证规则
descriptor[this.prop] = rules;
// 验证规则AsyncValidator实例对象(element校验使用的第三方库)
const validator = new AsyncValidator(descriptor);
const model = {};
// AsyncValidator需要的验证数据
model[this.prop] = this.fieldValue;
// 验证
validator.validate(model, { firstFields: true }, (errors, invalidFields) => {
// 验证后的状态(结果)
this.validateState = !errors ? 'success' : 'error';
// 验证提示
this.validateMessage = errors ? errors[0].message : '';
// 执行回调
callback(this.validateMessage, invalidFields);
// form组件发布validate事件
this.elForm && this.elForm.$emit('validate', this.prop, !errors);
});
}
拓展:
- 案例全部代码(App.vue):
<template>
<div id="app">
<el-form
:model="data"
style="width: 500px"
label-position="left"
label-width="100px"
label-suffix=":"
:inline="false"
:rules="rules"
:disabled="false"
status-icon
validate-on-rule-change
hide-required-asterisk
:inline-message="false"
>
<el-form-item
label="用户名"
prop="user"
:error="error"
:validate-status="status"
>
<el-input v-model="data.user" placeholder="用户名" clearable></el-input>
</el-form-item>
<el-form-item label="活动区域" prop="region">
<el-select v-model="data.region" placeholder="活动区域" style="width:100%">
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
<el-button type="primary" @click="addRule">添加校验规则</el-button>
<el-button @click="showError">错误状态</el-button>
<el-button @click="showSuccess">正确状态</el-button>
<el-button @click="showValidating">验证状态</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
data: {
user: 'sam',
region: '区域二'
},
error: '',
status: '',
rules: {
user: [
{ required: true, trigger: 'change', message: '用户名必须录入' }
]
}
}
},
methods: {
/* eslint-disable */
onSubmit() {
console.log(this.data)
},
addRule() {
const userValidator = (rule, value, callback) => {
if (value.length > 3) {
this.inputError = ''
this.inputValidateStatus = ''
callback()
} else {
callback(new Error('用户名长度必须大于3'))
}
}
const newRule = [
...this.rules.user,
{ validator: userValidator, trigger: 'change' }
]
this.rules = Object.assign({}, this.rules, { user: newRule })
},
showError() {
this.status = 'error'
this.error = '用户名输入有误'
},
showSuccess() {
this.status = 'success'
this.error = ''
},
showValidating() {
this.status = 'validating'
this.error = ''
}
}
}
</script>