最近有个朋友在公司里边遇到一个难题,要求他把easyUI用vue重构一遍,这个确实很有挑战性。所以我这边也仿照ElementUI开始写一套自己的UI框架,相当于重构了吧。
首先当然是阅读源码了,我也是从网上找了很多的资料,查看其他人是怎么阅读这块的源码。结合别人的经验我先是看了树选择控件的实现,感觉有那么点意思,之后开始从alert开始一点一点的重构我的UI框架的,希望在不久的将来能推出自己的UI库。
首先是看源码结构
lib: 这块代码都是经过webpack打包之后的代码,没必要看,我们了解就行。
packages: 我们学习主要的代码都在这里,里边包含各个组件的实现,还有样式,里边有一个文件夹叫theme-chalk文件夹,里边都是一些样式文件。
src: 里边是一些js文件,我们刚开始只要看index.js就可以了,这个很明显是入口文件。
所以我们也是创建一个类似于这样的目录,但是因为我们处于开发阶段所以没有是用webpack,这块具体怎么打包,稍后会给大家说明,这里先跳过。 下面是我创建的文件夹:
是不是很简单,嘿嘿,只有组件和src。
我们看源码中src中的index文件:
/* Automatically generated by './build/bin/build-entry.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';
import DropdownItem from '../packages/dropdown-item/index.js';
import Menu from '../packages/menu/index.js';
import Submenu from '../packages/submenu/index.js';
import MenuItem from '../packages/menu-item/index.js';
import MenuItemGroup from '../packages/menu-item-group/index.js';
import Input from '../packages/input/index.js';
import InputNumber from '../packages/input-number/index.js';
import Radio from '../packages/radio/index.js';
import RadioGroup from '../packages/radio-group/index.js';
import RadioButton from '../packages/radio-button/index.js';
import Checkbox from '../packages/checkbox/index.js';
import CheckboxButton from '../packages/checkbox-button/index.js';
import CheckboxGroup from '../packages/checkbox-group/index.js';
import Switch from '../packages/switch/index.js';
import Select from '../packages/select/index.js';
import Option from '../packages/option/index.js';
import OptionGroup from '../packages/option-group/index.js';
import Button from '../packages/button/index.js';
import ButtonGroup from '../packages/button-group/index.js';
import Table from '../packages/table/index.js';
import TableColumn from '../packages/table-column/index.js';
import DatePicker from '../packages/date-picker/index.js';
import TimeSelect from '../packages/time-select/index.js';
import TimePicker from '../packages/time-picker/index.js';
import Popover from '../packages/popover/index.js';
import Tooltip from '../packages/tooltip/index.js';
import MessageBox from '../packages/message-box/index.js';
import Breadcrumb from '../packages/breadcrumb/index.js';
import BreadcrumbItem from '../packages/breadcrumb-item/index.js';
import Form from '../packages/form/index.js';
import FormItem from '../packages/form-item/index.js';
import Tabs from '../packages/tabs/index.js';
import TabPane from '../packages/tab-pane/index.js';
import Tag from '../packages/tag/index.js';
import Tree from '../packages/tree/index.js';
import Alert from '../packages/alert/index.js';
import Notification from '../packages/notification/index.js';
import Slider from '../packages/slider/index.js';
import Loading from '../packages/loading/index.js';
import Icon from '../packages/icon/index.js';
import Row from '../packages/row/index.js';
import Col from '../packages/col/index.js';
import Upload from '../packages/upload/index.js';
import Progress from '../packages/progress/index.js';
import Spinner from '../packages/spinner/index.js';
import Message from '../packages/message/index.js';
import Badge from '../packages/badge/index.js';
import Card from '../packages/card/index.js';
import Rate from '../packages/rate/index.js';
import Steps from '../packages/steps/index.js';
import Step from '../packages/step/index.js';
import Carousel from '../packages/carousel/index.js';
import Scrollbar from '../packages/scrollbar/index.js';
import CarouselItem from '../packages/carousel-item/index.js';
import Collapse from '../packages/collapse/index.js';
import CollapseItem from '../packages/collapse-item/index.js';
import Cascader from '../packages/cascader/index.js';
import ColorPicker from '../packages/color-picker/index.js';
import Transfer from '../packages/transfer/index.js';
import Container from '../packages/container/index.js';
import Header from '../packages/header/index.js';
import Aside from '../packages/aside/index.js';
import Main from '../packages/main/index.js';
import Footer from '../packages/footer/index.js';
import Timeline from '../packages/timeline/index.js';
import TimelineItem from '../packages/timeline-item/index.js';
import locale from 'element-ui/src/locale';
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';
const components = [
Pagination,
Dialog,
Autocomplete,
Dropdown,
DropdownMenu,
DropdownItem,
Menu,
Submenu,
MenuItem,
MenuItemGroup,
Input,
InputNumber,
Radio,
RadioGroup,
RadioButton,
Checkbox,
CheckboxButton,
CheckboxGroup,
Switch,
Select,
Option,
OptionGroup,
Button,
ButtonGroup,
Table,
TableColumn,
DatePicker,
TimeSelect,
TimePicker,
Popover,
Tooltip,
Breadcrumb,
BreadcrumbItem,
Form,
FormItem,
Tabs,
TabPane,
Tag,
Tree,
Alert,
Slider,
Icon,
Row,
Col,
Upload,
Progress,
Spinner,
Badge,
Card,
Rate,
Steps,
Step,
Carousel,
Scrollbar,
CarouselItem,
Collapse,
CollapseItem,
Cascader,
ColorPicker,
Transfer,
Container,
Header,
Aside,
Main,
Footer,
Timeline,
TimelineItem,
CollapseTransition
];
const install = function(Vue, opts = {}) {
// 国际化
locale.use(opts.locale);
locale.i18n(opts.i18n);
// 组件注册
components.forEach(component => {
Vue.component(component.name, component);
});
//
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;
};
/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
export default {
version: '2.6.3',
locale: locale.use,
i18n: locale.i18n,
install,
CollapseTransition,
Loading,
Pagination,
Dialog,
Autocomplete,
Dropdown,
DropdownMenu,
DropdownItem,
Menu,
Submenu,
MenuItem,
MenuItemGroup,
Input,
InputNumber,
Radio,
RadioGroup,
RadioButton,
Checkbox,
CheckboxButton,
CheckboxGroup,
Switch,
Select,
Option,
OptionGroup,
Button,
ButtonGroup,
Table,
TableColumn,
DatePicker,
TimeSelect,
TimePicker,
Popover,
Tooltip,
MessageBox,
Breadcrumb,
BreadcrumbItem,
Form,
FormItem,
Tabs,
TabPane,
Tag,
Tree,
Alert,
Notification,
Slider,
Icon,
Row,
Col,
Upload,
Progress,
Spinner,
Message,
Badge,
Card,
Rate,
Steps,
Step,
Carousel,
Scrollbar,
CarouselItem,
Collapse,
CollapseItem,
Cascader,
ColorPicker,
Transfer,
Container,
Header,
Aside,
Main,
Footer,
Timeline,
TimelineItem
};
看到上边的代码相信搭建应该了解了ElementUI的组件都是通过这个来整合到一起的,还有就是我们用到的一些方法。我们现在还不需要写这么多,我们先从alert组件开始写,所以我们只需要这么写就可以。
所以我的index.js文件如下:
import Alert from '../packages/alert/index'
const components = [
Alert,
]
const install = function(Vue, opts = {}) {
components.forEach(component => {
Vue.component(component.name, component)
})
Vue.prototype.$SHANZHOUO = {
size: opts.size || '',
zIndex: opts.zIndex || 2000
};
// Vue.prototype.$alert =
}
/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
export default {
install,
Alert
}
是不是很简单,虽然很简单,但是我们得学习这种写法,以后对于我们自己封装组件非常有用。
好了,现在开始写alert组件。
首先我们肯定是先创建一个文件夹,在packages文件夹下,名为alert。
index.js中的内容:
import Alert from './src/main';
/* istanbul ignore next */
Alert.install = function(Vue) {
Vue.component(Alert.name, Alert);
};
export default Alert;
这种写法相信很多同学都很熟,这种就是我们日常写组件的方式,到这一步终于发现原来也没想象中的那么难。嘿嘿。
然后我们打开main.vue文件:
<template>
<transition name="el-alert-fade">
<div
class="el-alert"
:class="[typeClass, center ? 'is-center' : '']"
v-show="visible"
role="alert"
>
<i class="el-alert__icon" :class="[ iconClass, isBigIcon ]" v-if="showIcon"></i>
<div class="el-alert__content">
<span class="el-alert__title" :class="[ isBoldTitle ]" v-if="title || $slots.title">
<slot name="title">{{ title }}</slot>
</span>
<p class="el-alert__description" v-if="$slots.default && !description"><slot></slot></p>
<p class="el-alert__description" v-if="description && !$slots.default">{{ description }}</p>
<i class="el-alert__closebtn" :class="{ 'is-customed': closeText !== '', 'el-icon-close': closeText === '' }" v-show="closable" @click="close()">{{closeText}}</i>
</div>
</div>
</transition>
</template>
<script type="text/babel">
const TYPE_CLASSES_MAP = {
'success': 'el-icon-success',
'warning': 'el-icon-warning',
'error': 'el-icon-error'
};
export default {
name: 'ElAlert',
props: {
title: {
type: String,
default: ''
},
description: {
type: String,
default: ''
},
type: {
type: String,
default: 'info'
},
closable: {
type: Boolean,
default: true
},
closeText: {
type: String,
default: ''
},
showIcon: Boolean,
center: Boolean
},
data() {
return {
visible: true
};
},
methods: {
close() {
this.visible = false;
this.$emit('close');
}
},
computed: {
typeClass() {
return `el-alert--${ this.type }`;
},
iconClass() {
return TYPE_CLASSES_MAP[this.type] || 'el-icon-info';
},
isBigIcon() {
return this.description || this.$slots.default ? 'is-big' : '';
},
isBoldTitle() {
return this.description || this.$slots.default ? 'is-bold' : '';
}
}
};
</script>
这个就是一个很简单很普通的一个alert组件,到了这里我们写第一个组件基本就完成了,因为我的组件和ElementUI相近,所以我的代码和这个代码差不多是一样的,只是部分地方结合公司的具体需求稍微做了点改动,这里就不给大家详细写了。希望大家通过这篇文章能了解ElementUI是怎么整合我们的组件到一起的,写组件的一些基本的流程,稍后具体的每一个组件我会在接下来的文档中详细介绍。 待续......