分析Element-ui封装思想
在平时写业务或者是写玩具的时候为了方便,我们会使用各种各样的组件库。虽然说基本需求看文档就可以了,但是文档中提供的方法和业务需求相比肯定是有一定差距的,这时候就需要自己封装组件了;并且,在写了一些代码后感觉,其实在不同的项目中写过功能差不多相同的代码,那为什么不封装一下方便以后、或者是其他人使用呢?写这篇博客的时候参考了b站up主樱满空的视频。
文章内容会不断的更新,每一节内容分为
- props属性分析
- 样式分析
- 重新封装
目录结构分析
假设现在你已经在项目中安装了element-ui,此时打开node_modules目录往下翻,可以看到一个名为element-ui的文件夹。
- lib文件夹存放element-ui打包后的文件,也就是项目实际依赖了的文件
- packages文件夹存放组件相关的源代码,也是之后源码分析的主要目标。
- src文件夹存放了如指令、混入、工具方法等源代码
- types文件夹存放了ts的类型声明文件,方便引入 typescript 写的项目中,需要在
package.json
中指定 typing 字段的值为 声明的入口文件才能生效。
入口文件
在分析packegs文件夹中的各个组件源码之前,我们先看看src中的入口文件index.js。
// 部分引入import Pagination from'../packages/pagination/index.js';import Dialog from'../packages/dialog/index.js';import Autocomplete from'../packages/autocomplete/index.js';import Dropdown from'../packages/dropdown/index.js';import DropdownMenu from'../packages/dropdown-menu/index.js';// 将引入的文件名放在一个数组中const components =[
Pagination,
Dialog,
Autocomplete,
Dropdown,
DropdownMenu
]// Element暴露出去一个install函数,Element本身就是一个插件constinstall=function(Vue, opts ={}){
locale.use(opts.locale);
locale.i18n(opts.i18n);// 通过对组件使用forEach方法,将所有的组件进行注册
components.forEach(component=>{
Vue.component(component.name, component);});
Vue.use(InfiniteScroll);
Vue.use(Loading.directive);Vue.prototype.$ELEMENT={size: opts.size ||'',zIndex: opts.zIndex ||2000};Vue.prototype.$loading = Loading.service;Vue.prototype.$msgbox = MessageBox;Vue.prototype.$alert = MessageBox.alert;Vue.prototype.$confirm = MessageBox.confirm;Vue.prototype.$prompt = MessageBox.prompt;Vue.prototype.$notify = Notification;Vue.prototype.$message = Message;};
我们在调用
Vue.use(ElementUI)
注册时,本质上就是调用这个
install
函数。由于Vue.use接收一个对象,这个对象必须具有install方法,Vue.use函数内部会调用参数的install方法。如果插件没有被注册过,那么注册成功之后会给插件添加一个installed的属性值为true。Vue.use方法内部会检测插件的installed属性,从而避免重复注册插件。
插件的install方法将接收两个参数,第一个是参数是Vue,第二个参数是配置项options(就是这里的opts)对象。
从
Vue.prototype.$ELEMENT
这一句来看,传入的参数可以有
size
和
zIndex
属性,
size
用于改变组件的默认尺寸,
zIndex
设置弹框的初始 z-index(默认值:2000)。我们可以手动向options中传入size和zInde,保存到
Vue.prototype.$ELEMENT
全局配置中,这样在组件中我们就可以根据size和zIndex进行不同组件尺寸的展示。
import Element from'element-ui';
Vue.use(Element,{size:'small',zIndex:3000});
在入口文件中我们可以通过
forEach
循环遍历进行大部分组件的注册,小部分如
InfiniteScroll
和
Loading
在全局注册指令,通过
v-infinite-scroll
和
v-loading
等指令式来调用;也有如
msgbox
、
alert
等在全局Vue.prototype添加方法,可以通过函数进行调用。
Layout布局
先看看基础布局里面提供的代码,对于Layout布局的部分,我们需要使用到el-row和el-col的嵌套子组件。
<el-row><el-col:span="24"><divclass="grid-content bg-purple-dark"></div></el-col></el-row><el-row><el-col:span="12"><divclass="grid-content bg-purple"></div></el-col><el-col:span="12"><divclass="grid-content bg-purple-light"></div></el-col></el-row>
el-row
- 打开路径
node_modules -> element-ui -> package -> el-row -> src -> row.js
查看组件的逻辑和页面部分 - 打开路径
node_modules -> element-ui -> packages -> theme-chalk -> src -> row.scss
查看el-row的样式部分 - 查阅官方文档查看el-row有那些属性 和传统的vue文件中template模板不同的是,el-row组件是以渲染函数的方式编写的
export default {
// 组件名
name: 'ElRow',
// 这个选项并非Vue官方提供的API,而是Element团队自定义的属性
// 在查阅 Vue2 官方文档的时候可以看到,vm.$options的api可以用于当前Vue实例的初始化选项
// 所有我们写的Vue选项都会放到Vue实例属性$options中
// 比如之后可以通过this.$options.componentName获取到这里的属性值
componentName: 'ElRow',
props: {
tag: {
type: String,
default: 'div'
},
gutter: Number,
type: String,
justify: {
type: String,
default: 'start'
},
align: String
},
computed: {
style() {
const ret = {};
if (this.gutter) {
ret.marginLeft = `-${this.gutter / 2}px`;
ret.marginRight = ret.marginLeft;
}
return ret;
}
},
render(h) {
return h(this.tag, {
class: [
'el-row',
this.justify !== 'start' ? `is-justify-${this.justify}` : '',
this.align ? `is-align-${this.align}` : '',
{ 'el-row--flex': this.type === 'flex' }
],
style: this.style
}, this.$slots.default);
}
};
props属性分析
- tag:用来自定义元素标签,默认是div。我们可以看到tag属性用在了render函数中,render函数的参数h就是createElement函数的别名,也就是说默认情况下每个渲染出来的el-row是一个div。
- type、justify、align:这三个属性都与flex布局相关,type属性可选flex布局,后面两个属性用于垂直水平的布局,在render函数中查看class,可以通过垂直水平的属性判断元素所在的位置。顺便介绍下render函数的最后一个参数,
this.$slots.default
用的是default插槽的内容,也就是在el-row标签中写的内容。 - gutter:列间距。这个属性用在了computed中计算style,在我们手动传入了gutter的情况下,会给el-row左右两侧各添加一个gutter值除以2的负外边距。这么做是因为 el-col 的左右两侧都会添加一个gutter除以2的内边距。如果不追加这个负外边距的话会导致行的左右两侧也有间距,导致el-col无法和外层元素边缘对齐。至于为什么需要这样做,我们可以看看这个例子
<divclass="box"><divclass="son"></div>
test
<divclass="son"></div></div><style>body{background-color: coral;}.box{width: 100%;height: 300px;background-color: aquamarine;}.son{height: 100px;background-color: black;}</style>
现在为son设置一个margin-top:100px看看,可以明显的看到,父元素的box元素也被强制向下移动了100px
现在为父元素设置margin-top: -100px,整体元素就成功上移了。所以说在el-row中添加负外边距是为了保证子元素设置外边距时,不会影响整体行位置上的改变。
样式分析
打开row.scss文件
@import"common/var";@import"mixins/mixins";@import"mixins/utils";@includeb(row){position: relative;box-sizing: border-box;@include utils-clearfix;@includem(flex){display: flex;&:before,
&:after{display: none;}@includewhen(justify-center){justify-content: center;}@includewhen(justify-end){justify-content: flex-end;}@includewhen(justify-space-between){justify-content: space-between;}@includewhen(justify-space-around){justify-content: space-around;}@includewhen(align-top){align-items: flex-start;}@includewhen(align-middle){align-items: center;}@includewhen(align-bottom){align-items: flex-end;}}}
在第四行中我们可以看到使用了@include指令,这个指令是搭配mixin使用的,现在它混入了一个名为b的mixin,并且传递了一个参数值为row,接下来通过@import "mixins/mixins"点进去查看,这个名为b的mixin做了什么工作。
/* BEM
-------------------------- */
@mixin b($block) {
$B: $namespace+'-'+$block !global;
.#{$B} {
@content;
}
}
在这个混入中首先定义了一个$B的变量,值是namespace + ‘-’ + 传入的变量。这个namespace在config.scss中有定义
$namespace: 'el';
$element-separator: '__';
$modifier-separator: '--';
$state-prefix: 'is-';
对于这些定义需要了解一下采用BEM的Class命名风格
- 组件名放在最前面,比如el-row,el-col,el-input
- 如果说是组件的子元素样式,会用组件名加上两个下划线再加上元素名,比如el-input__inner
- 修饰符接在最后,用两个中横线与前面的隔开,比如el-button–primary
- 表示状态的前缀 is-,比如表示禁用状态的样式is-disabled
现在我们回到b的mixin中,#{}是使用变量定义的意思,这里用$B的值定义了一个class,其中使用了@content来将我们使用混入时写在大括号中间的内容放到这个class中。
@include b(row) {
position: relative;
box-sizing: border-box;
...
}
最终渲染到页面上名为el-row的class中。
接着row.scss往下看,又include了一个混入
@include utils-clearfix
,首部utils-名称表示我们需要到utils.scss中来看。
@mixin utils-clearfix {
$selector: &;
@at-root {
#{$selector}::before,
#{$selector}::after {
display: table;
content: "";
}
#{$selector}::after {
clear: both
}
}
}
从display:tabel和clear:both可以明显的看出来该方法用于清除浮动。
回到row.scss,发现存在一个名为m的混入@include m(flex) ,通过mixin.scss中我们可以看到,这个是生成修饰符class用的混入,使用@each遍历我们传入的修饰符,生成class名,然后拼接每个修饰符class,最后把给混入传递的内容放到这些class中。
@mixin m($modifier) {
$selector: &;
$currentSelector: "";
@each $unit in $modifier {
$currentSelector: #{$currentSelector + & + $modifier-separator + $unit + ","};
}
@at-root {
#{$currentSelector} {
@content;
}
}
}
这里的@at-root是让后面的样式跳出目前层级到顶层,由于我们之前在el-row的class下调用m混入,所以默认会把这些样式添加父类选择器el-row。使用@at-root后,这些修饰符选择器就可以和el-row class平级了。
最后还剩下一堆when的混入,其实就是用来生成is-开头表示状态用的class
@mixin when($state) {
@at-root {
&.#{$state-prefix + $state} {
@content;
}
}
}
el-col
el-col和el-row一样,都是通过渲染函数编写的。
props属性分析
- span表示列宽,默认情况下一列的宽度为24占一整行
- offset、pull、push表示栅格在一行中的便宜位置
- xs、sm、md、lg、xl用于表示响应式布局,他们可以接收Number或者是Object类型。接收Number类型的时候相当于对应画面大小时的span;接收Object类型时对象的键可以是span、offset、pull、push
在计算属性中有一个gutter属性,通过this.$parend获取到当前el-col的父组件,下面的while循环表示,在el-col的祖先结点中查找到离当前节点距离最近的el-row组件。如果找到了最近的el-col祖先组件,就返回父组件身上所绑定的gutter值,否则为0,Element经常使用这个方法去查找某个组件的最近祖先元素,比如el-from,el-form-item。
computed:{gutter(){let parent =this.$parent;while(parent && parent.$options.componentName !=='ElRow'){
parent = parent.$parent;}return parent ? parent.gutter :0;}}
通过props属性以及计算属性,可以在render函数中渲染结点
render(h){let classList =[];let style ={};if(this.gutter){
style.paddingLeft =this.gutter /2+'px';
style.paddingRight = style.paddingLeft;}['span','offset','pull','push'].forEach(prop=>{if(this[prop]||this[prop]===0){
classList.push(
prop !=='span'?`el-col-${prop}-${this[prop]}`:`el-col-${this[prop]}`);}});['xs','sm','md','lg','xl'].forEach(size=>{// 如果传递进来进行响应式处理的是具体的数字if(typeofthis[size]==='number'){
classList.push(`el-col-${size}-${this[size]}`);}// 如果传递进来的是一个数组elseif(typeofthis[size]==='object'){let props =this[size];// 通过Object.keys()拿到键值对,其实感觉用for...in...会更方便
Object.keys(props).forEach(prop=>{
classList.push(
prop !=='span'?`el-col-${size}-${prop}-${props[prop]}`:`el-col-${size}-${props[prop]}`);});}});returnh(this.tag,{class:['el-col', classList],
style
},this.$slots.default);}
样式分析
在theme-chalk文件夹下找到col.scss文件,在文件的起始部分为每一个el-col开头的元素设置了左浮动和border-box属性
[class*="el-col-"] {
float: left;
box-sizing: border-box;
}
接下来通过一个循环从0-24设置span、offset、pull、push的样式
// span为0的样式会额外设置一个display:none
.el-col-0 {
display: none;
}
@for $i from 0 through 24 {
.el-col-#{$i} {
width: (1 / 24 * $i * 100) * 1%;
}
.el-col-offset-#{$i} {
margin-left: (1 / 24 * $i * 100) * 1%;
}
.el-col-pull-#{$i} {
position: relative;
right: (1 / 24 * $i * 100) * 1%;
}
.el-col-push-#{$i} {
position: relative;
left: (1 / 24 * $i * 100) * 1%;
}
}
紧接着的会通过名为res的混入生成各个size的响应式布局样式
@include res(xs) {
.el-col-xs-0 {
display: none;
}
@for $i from 0 through 24 {
.el-col-xs-#{$i} {
width: (1 / 24 * $i * 100) * 1%;
}
...
}
}
我们来看一下这个名为res的混入,这个混入接收两个参数,$key表示传入响应式的key值(xs、sm等)
/* Break-points
-------------------------- */
@mixin res($key, $map: $--breakpoints) {
// 循环断点Map,如果存在则返回
@if map-has-key($map, $key) {
@media only screen and #{inspect(map-get($map, $key))} {
@content;
}
} @else {
@warn "Undefeined points: `#{$map}`";
}
}
如果没有传入第二个参数
m
a
p
,
会
使
用
默
认
值
map,会使用默认值
map,会使用默认值–breakpoints
/* Break-point
--------------------------*/
$--sm: 768px !default;
$--md: 992px !default;
$--lg: 1200px !default;
$--xl: 1920px !default;
$--breakpoints: (
'xs' : (max-width: $--sm - 1),
'sm' : (min-width: $--sm),
'md' : (min-width: $--md),
'lg' : (min-width: $--lg),
'xl' : (min-width: $--xl)
);
回到res混入中,通过
@if
和 scss内置的函数方法
map-has-key
判断key值是否在
m
a
p
中
,
如
果
k
e
y
值
在
map中,如果key值在
map中,如果key值在map中存在,就会生成一个媒体查询。这里的inspect也是scss的内置函数,用来将变量值转换为字符串形式;map-get函数用来获取map中key所对应的value值。
举个栗子,当我们使用
<el-col:xs="8":sm="6":md="4":lg="3":xl="1"><divclass="grid-content bg-purple-light"></div></el-col>
就会分别生成max-width: 765px,min-width: 766px、992px、1200px、1920px的媒体查询,根据画面的不同展示不同的样式,他们对应每一列的宽度也会不一样。
重新封装
官方文档给定el-row的最大宽度有24列,现在如果将其扩展为48列应该如何操作呢?
在前面分析样式文件中,每一列的宽度都是在一个从1至24的循环中设置,最小列宽为 (1 / 24 * 1 * 100) * 1% = (1 / 24)%,如果想要扩展列数,我们只需要将循环的上限扩大为48,以及每一列改为 (1 / 48 * i * 100)* 1%即可。
更改
这样做似乎不行,在页面上显示的时候仍按照24的宽度,期待大佬分享正确改法。
思路的确是这样的,还记得最开始分析目录结构的时候介绍的lib文件夹吗,通过
element-ui->lib->theme-chalk->col.css
可以看到打包完成的element col的样式。
可以明显的看到列的最大值为24,具体每一列的宽度也计算好了放在文件的后面。而我之前一直是在scss文件中去修改它的循环条件,计算结果最终却没有重新打包,即引用的是未经过打包的、没有修改的css文件,所以最终导致无法显示。也许你会想,那直接修改打包后的文件可以吗?答案是不行,vue项目中的
node-module->element-ui
文件夹中没有build文件夹。所以无法直接修改项目中的element-ui。
解决方法
首先将ElementUI的源码clone下来并安装依赖
git clone https://github.com/ElemeFE/element.git
cd element
npm install
然后在packages文件夹中去修改目标文件的源代码结构以及theme-chalk下的样式,修改完毕后执行npm run dist进行打包
使用
dist
打包原因来自官方文档:https://github.com/ElemeFE/element/blob/master/.github/CONTRIBUTING.zh-CN.md
打包结束会生成一个lib文件夹,将他替换掉项目中
node_modules->element-ui
下的lib文件夹即可(我之前使用的是element 12+,打包后lib文件夹中只有一个index.js,将版本回退到2.4.5的时候打包结果和项目中文件结构相同)
打包的过程中如果你的 node版本 ≥ 12.0 并且 gulp版本 < 4.0,会遇到下面的报错
面对版本冲突错误,我选择升级gulp的版本,方法可以看gulp官方文档:https://gulpjs.com/docs/en/getting-started/quick-start/
在stackoverflow上面看到了另外一种解决办法:https://stackoverflow.com/questions/55921442/how-to-fix-referenceerror-primordials-is-not-defined-in-node-js,通过在package.json文件里修改配置实现兼容
解决版本冲突后重新打包,将打包后的lib文件夹,替换掉项目中lib文件夹后重启项目,就可以正常使用了。
Container 布局容器
用于布局的容器组件,方便快速搭建页面的基本结构
el-container
打开pakages文件找到contianer组件所在的位置,可以看到这个组件是通过模板编写的
<template>
<section class="el-container" :class="{ 'is-vertical': isVertical }">
<slot></slot>
</section>
</template>
<script>
export default {
name: 'ElContainer',
componentName: 'ElContainer',
props: {
direction: String
},
computed: {
isVertical() {
if (this.direction === 'vertical') {
return true;
} else if (this.direction === 'horizontal') {
return false;
}
return this.$slots && this.$slots.default
? this.$slots.default.some(vnode => {
const tag = vnode.componentOptions && vnode.componentOptions.tag;
return tag === 'el-header' || tag === 'el-footer';
})
: false;
}
}
};
</script>
props属性分析
- direction:用于定义主题的排列顺序,可以直接传入horizontal或vertical;也可以使用之前介绍过的方法,每个组件都有一个特殊的componentName属性,默认情况下通过插槽获取到子元素的数组,利用该属性的值进行判断。
// 如果存在插槽 && 存在默认插槽
return this.$slots && this.$slots.default
? this.$slots.default.some(vnode => {
// 1 && function, 会执行后面的函数
// 0 && function, 不会执行
const tag = vnode.componentOptions && vnode.componentOptions.tag;
// 对结点进行判断是否是el-header || el-footer
return tag === 'el-header' || tag === 'el-footer';
})
: false;
样式分析
@import "mixins/mixins";
@include b(container) {
display: flex;
flex-direction: row;
flex: 1;
flex-basis: auto;
box-sizing: border-box;
min-width: 0;
@include when(vertical) {
flex-direction: column;
}
}
首先看到的还是首先将contianer传入混入b,最终生成el-container的类名以及属性。整体来看属性值就是border-box盒子模型以及flex布局,需要多介绍的是
min-width
属性,使用了这个属性可以让外部盒子元素缩短到比元素中的内容还短。
之后是when混入,负责生成
is-vertical
的状态判断属性,这里是将元素设置为列排序。
el-main
在container布局里面剩下的几个组件(el-header/aside/footer/main)内容相仿,所以单独挑出main进行举例介绍。
props属性分析
el-header、aside、footer都会接收height或者是width的prop用来设置内联样式。如果不想设置这些属性,你也可以显示的将这个属性设置为null,这样每一个组件的大小就可以由子内容撑开。
样式分析
@import "mixins/mixins";
@import "common/var";
@include b(main) {
// IE11 supports the <main> element partially https://caniuse.com/#search=main
display: block;
flex: 1;
flex-basis: auto;
overflow: auto;
box-sizing: border-box;
padding: $--main-padding;
}
el-aside、main将overflow设置为auto,使内容超出容器的情况下显示滚动条而不是溢出。
Button按钮
Button是常用的操作按钮,我们经常会对Button组件进行封装
props属性分析
在进行props属性分析之前我们先看看 script 部分。
这里复习一下inject和provide的使用方法。首先看看官网API中的定义
单纯看概念可能还是会有些生疏,所以借助代码实例来理解:
// 父级组件提供 'foo'
// 父组件通过provide提供一个对象,这个对象的key是foo,value是'bar'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子组件注入 'foo'
// inject注入字符串数组,数组项中存放的就是provide提供的key,之后再子组件中通过this.key可以拿到父组件中的value
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
// inject注入一个对象,本地的key名是foo,value的默认值是待注入的 'foo'
// 需要注意的是通过inject注入属性的时候,default的value值也需要加上单引号来表示,引用的是某个provide提供的key
const Child2 = {
inject: {
foo: { default: 'foo' }
}
// ...
}
// 如果在不同的组件中提供了相同名字的 provide,
// 在子组件注册的时候可以使用 from 来表示其源 property
const Child3 = {
inject: {
foo: {
from: 'bar',
default: 'foo'
}
}
// ...
}
所以在这里,最开始的时候通过
inject
注入了el-form和el-form-item的组件实例本身(因为他们provide都是自身的this),如果之后需要使用他们身上属性的话,直接通过 this.elForm.xxx | this.elFormItem.xxx就可以直接使用了,仿佛是在button组件中直接操作form-item中的数据,也的确如此!
element的表单控件组件基本上都会使用这种方式来访问form或者from-item身上的实例属性或者是方法。
// button.vue
inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
}
// el-form-item.vue
provide() {
return {
elFormItem: this
};
}
props里面接收到的参数:
- size:用于定义按钮的大小,对应template中的buttonSize,之后会在computed中介绍
- type:用于定义按钮的类型,在class的三目表达式中添加上对应的
el-button-${type}
属性 - plain、round、circle:用于定义按钮的显示样式,在class中通过传入的true/false来添加对应的
'is-'
属性 - 复习一下动态绑定class的方法,:class="[ 普通的css | 三目表达式的css , { 通过bool值判断是否添加的css }]"
- 举个例子,:class="[‘home-container’, isShow ? ‘show’ : ’ ', { ‘is-active’ : isActive}]" - loading、disabled:disabled对应template中的buttonDisabled,之后会在computed中介绍;通过loading判断按钮的加载状态,需要注意的是,如果在加载的状态中,按钮的disabled由disabled和loading是否为真来决定
- native-type:负责决定原生的button事件
<button
class="el-button"
@click="handleClick"
:disabled="buttonDisabled || loading"
:autofocus="autofocus"
:type="nativeType"
:class="[
type ? 'el-button--' + type : '',
buttonSize ? 'el-button--' + buttonSize : '',
{
'is-disabled': buttonDisabled,
'is-loading': loading,
'is-plain': plain,
'is-round': round,
'is-circle': circle
}
]"
>
<i class="el-icon-loading" v-if="loading"></i>
<i :class="icon" v-if="icon && !loading"></i>
<span v-if="$slots.default"><slot></slot></span>
</button>
接下来分析computed中的计算属性
computed: {
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
buttonSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
buttonDisabled() {
return this.disabled || (this.elForm || {}).disabled;
}
},
- _elFormItemSize:这个计算属性使用的是从el-form-item里面注入的elFormItemSize属性,这个属性在el-form-item.vue中也是一个计算属性,值是在item中传入size时返回的,如果在item里面没有传入size则使用el-form实例身上的size。
// el-form-item.vue 里的 computed
_formSize() {
return this.elForm.size;
},
elFormItemSize() {
return this.size || this._formSize;
},
- buttonSize:- 这个属性首先会应用当前组件中传入的size值- 没传入的情况下会使用之前计算的_elFormItemSize- 如果仍未false值,则会使用 E l e m e n t 身 上 的 值 。 Element身上的值。 Element身上的值。Element在介绍入口文件时已经分析过,我们在注册Element插件的时候可以手动的传入一个数组(元素有size和zIndex)
Vue.use(Element, { size: 'small', zIndex: 3000 })
,这也是为什么最后写的是(this.$ELEMENT || {}).size- 如果都没设定的话结果就为空值 - buttonDisabled:现在我们看到的是2.4.5版本的Element-ui,该属性会首先获取button->props身上的disabled属性,如果为false,向上查找注入的elForm身上的disabled属性。- 这样会导致一个问题:当我们给按钮明确传入false的时候,按钮按照想法来说应该不会被禁用,可是当他发现this.disabled为空时就找elForm身上的disabled,也就是说如果同时为elForm身上设置disabled:true,离el-Form最近的那个按钮就会导致失效。- 官方认为这是一个bug,所以在2.15.7版本时修复为:
buttonDisabled() { return this.$options.propsData.hasOwnProperty('disabled') ? this.disabled : (this.elForm || {}).disabled}
这样修改的话会首先判断实例button身上的$options.propsData是否有自有属性disabled,来判断组件是否被传入了disabled属性,如果传入了就使用elForm身上的disabled属性。
样式分析
版权归原作者 黑猫几绛 所有, 如有侵权,请联系我们删除。