Vuetify
在使用一个组件之前,我们要先知道这个组件库做了什么事情,例如组件是如何封装,有哪些组价是我们可以使用的。然后大致了解之后,我们在分析有没有适合我们业务的组件。
Vuetify 目录结构分的也很清楚:
- compnents 组件,实际的组件代码实现部分
- directives 指令部分
- locale 本地化部分
- mixins 混入部分
- presets 预设套餐
- server 服务部分
- styles 样式部分
- utils 工具函数部分
Vuetify 目录结构
- /vuetify/packages/vuetify/src/components
Vuetify 组件输入和引入全部在这个文件里面
export * from './VApp'
export * from './VAppBar'
export * from './VAlert'
// ...
指令
- vuetify/packages/vuetify/src/directives
Vuetify 的全部自定指令
export { ClickOutside } from './click-outside'
export { Intersect } from './intersect'
export { Mutate } from './mutate'
export { Resize } from './resize'
export { Ripple } from './ripple'
export { Scroll } from './scroll'
export { Touch } from './touch'
国际化
Vuetify 实现的国际化
export { default as af } from './af'
export { default as ar } from './ar'
export { default as ca } from './ca'
//...
perset
- /vuetify/packages/vuetify/src/presets
vuetify 的一些预设信息,类似于 Vuetify 的组件配置信息
// Styles
import '../../styles/main.sass'
// Locale
import { en } from '../../locale'
// Types
import { VuetifyPreset } from 'vuetify/types/services/presets'
export const preset: VuetifyPreset = {
breakpoint: {
// ...
},
icons: {
// ...
},
lang: {
// ...
},
rtl: false,
theme: {
// ...
},
themes: {
light: {
// ...
},
dark: {
// ...
},
},
},
}
server
export * from './application'
export * from './breakpoint'
export * from './goto'
export * from './icons'
export * from './lang'
export * from './presets'
export * from './theme'
Vuetify 采用了切面的思想,将一些配置形成切面,类似于 Ioc 的 Service 切面。管理所有的组件所需要的服务。
styles
- /vuetify/packages/vuetify/src/styles
Vuetify 使用 sass 创建css样式,一下是css管理
@import './tools/_index'
@import './settings/_index'
@import './generic/_index'
@import './elements/_index'
@import './utilities/_index'
一些基本准备工作就是这些,下面我们具体的源码
主入口
- /vuetify/packages/vuetify/src/index.ts
import * as components from './components'
import * as directives from './directives'
import Vuetify from './framework'
export default Vuetify
const install = Vuetify.install
Vuetify.install = (Vue, args) => {
install.call(Vuetify, Vue, {
components,
directives,
...args,
})
}
if (typeof window !== 'undefined' && window.Vue) {
window.Vue.use(Vuetify)
}
我们看到 Vuetify 上具有 install 方法,得知使用是 Vue 插件方式实现的组件注册。所以我们在使用 Vuetify 组件时,可直接使用 use 方法全部注册。
- vuetify/packages/vuetify/src/install.ts
import OurVue, { VueConstructor } from 'vue'
import { VuetifyUseOptions } from 'types'
import { consoleError } from './util/console'
export function install (Vue: VueConstructor, args: VuetifyUseOptions = {}) {
if ((install as any).installed) return
(install as any).installed = true
const components = args.components || {}
const directives = args.directives || {}
// 全局注册 vuetify 组件
for (const name in directives) {
const directive = directives[name]
Vue.directive(name, directive)
}
// 注册组件
(function registerComponents (components: any) {
if (components) {
for (const key in components) {
const component = components[key]
if (component && !registerComponents(component.$_vuetify_subcomponents)) {
Vue.component(key, component as typeof Vue)
}
}
return true
}
return false
})(components)
if (Vue.$_vuetify_installed) return
Vue.$_vuetify_installed = true
// 混入生命周期 beforeCreate,在每个组件上挂载 $vuetify。
Vue.mixin({
beforeCreate () {
const options = this.$options as any
if (options.vuetify) {
options.vuetify.init(this, options.ssrContext)
this.$vuetify = Vue.observable(options.vuetify.framework)
} else {
this.$vuetify = (options.parent && options.parent.$vuetify) || this
}
},
})
}
- /vuetify/packages/vuetify/src/framework.ts
Vuetify 的框架,从下面我们可以看出 Vuetify 的服务就是框架的一个属性。
import { install } from './install'
// Types
import Vue from 'vue'
import {
UserVuetifyPreset,
VuetifyPreset,
} from 'vuetify/types/services/presets'
import {
VuetifyService,
VuetifyServiceContract,
} from 'vuetify/types/services'
// Services
import * as services from './services'
// 输出 Vuetify 类,在 Vuetify 上挂载属性方法
export default class Vuetify {
static install = install
static installed = false
static version = __VUETIFY_VERSION__
public framework: Dictionary<VuetifyServiceContract> = {}
public installed: string[] = []
public preset = {} as VuetifyPreset
public userPreset: UserVuetifyPreset = {}
constructor (userPreset: UserVuetifyPreset = {}) {
this.userPreset = userPreset
this.use(services.Presets)
this.use(services.Application)
this.use(services.Breakpoint)
this.use(services.Goto)
this.use(services.Icons)
this.use(services.Lang)
this.use(services.Theme)
}
init (root: Vue, ssrContext?: object) {
this.installed.forEach(property => {
const service = this.framework[property]
service.framework = this.framework
service.init(root, ssrContext)
})
this.framework.rtl = Boolean(this.preset.rtl) as any
}
use (Service: VuetifyService) {
const property = Service.property
if (this.installed.includes(property)) return
this.framework[property] = new Service(this.preset, this as any)
this.installed.push(property)
}
}
组件 components
全局混入 mixins 创建组件
混入 mixins 是一个函数,有很多的函数重载,它的实现也很简单。
export default function mixins (...args: VueConstructor[]): VueConstructor {
return Vue.extend({ mixins: args })
}
其实就是通过 Vue.extend 来传入 options 创建组件构造器,来创建组件构造器。经过尝试 Vue.extend 的 extend 方法是可以连续调用的。其实在 Vuetify 中,本质就是联系的调用 extend 方法来创建 Vue 组件,这样处理的好好处是,明确了我们需要哪些 mixins。 将 mixins 直接凸显出来。
组件结构
封装在一新的方法中进行。我们整体的看 Alert 组件,组件结构也是特别的清晰的:
- Styles 组件需要的样式
- Extensions 组件需要的扩展
- Components 组件需要的依赖组件
- Mixins 组件依赖的 mixins
- Utilities 组件依赖的工具函数
- Types 组件依赖的类型
- Vue/Component vue 组件。
与 TS 结合
Vuetify 选择了 TypeScript 进行编写。没有使用 class TypeScript,而是使用 Vue.extend, Vue.extend 有良好的 Vue 的 TS 类型检测
组件: transition
首先要搞明白,Vuetify 中的组件没有使用 template,使用了 render 函数。如果你还没有书写 render 函数的形式来组件,快速的熟悉 render 函数写组件的方法。在 transition 是使用函数的方式创建 transition 组件的。源码中提供了创建 transition-group 和 transition 两个函数:
- createSimpleTransition 创建简单的过渡,没有transitioin-group
- createJavascriptTransition 创建过渡,可以是 transition-group
在 Vuetify 中间过渡分为了三大类
- Component specific transitions 组件特定的过渡
- VCarouselTransition 轮播过渡
- VCarouselReverseTransition 轮播反向过渡
- VTabTransition Tab过渡
- VTabReverseTransition Tab 反向过渡
- VMenuTransition Menu 过渡
- VFabTransition Fab 过渡
- Generic transitions 通用过渡
- VDialogTransition 对话过渡
- VDialogBottomTransition 对话底部过渡
- VFadeTransition 渐隐过渡
- VScaleTransition 缩放过渡
- VScrollXTransition X滚动过渡
- VScrollXReverseTransition X滚动反向过渡
- VScrollYTransition Y滚动过渡
- VScrollYReverseTransition Y滚动反向过渡
- VSlideXTransition X滑动滚动
- VSlideXReverseTransition X滑动反向过渡
- VSlideYTransition Y滑动过渡
- VSlideYReverseTransition Y滑动方向过渡
- Javascript transitions JavaScript 过渡
- VExpandTransition 扩展过渡
- VExpandXTransition 扩展X过渡
组件: VAlert
提示组件,提示组件提供了丰富的颜色配置, 能够配置出各种颜色丰富多彩的 alert 提示内容。
- 缓存
__cachedBorder缓存 border__cachedDismissible缓存 dismissible__cachedIcon缓存 icon
- 混入
ToggleableThemeableTransitionableVSheet
- 依赖组件
VBtn、VIcon
- 生成元素方法
- genWrapper
- genContent
- genAlert
组件: VApp
VApp 是 Vuetify 的入口组件,使用 Vuetify 组件的顶层,必须使用 VApp 组件进行包裹才能正常使用。
mixins: Themeable classs: BEM
{
attrs: { 'data-app': true },
domProps: { id: this.id },
}
本质上是渲染了一个带有 slot 的 div 元素。那么他是如何控制整个 Vuetify 的样式的呢?
- themeClasses
class: {
'v-application--is-rtl': this.$vuetify.rtl,
'v-application--is-ltr': !this.$vuetify.rtl,
...this.themeClasses,
},
- 检查 this.$vuetify, 是否进行了初始化。没有初始化,就抛出一个错误:
Error: Vuetify is not properly initialized。
组件: VAppBar
- 混入
- VToolbar
- Applicationable
- Scrollable
- SSRBootable
- Toggleable
- 单位转换
- convertToUnit
- 指令
- Scroll
- 特殊的 render
const render = VToolbar.options.render.call(this, h)
render.data = render.data || {}
特殊的 render,不是直接的 render 而是调用了 VToolbar options 的 render 方法。VToolbar 组件是 Vue.extend 生成的一个 Vue 组件构造器。需要的注意的是 data 不是Vue 中的响应式数据 data。
这里其实给有一个很好的扩展方式,Vue.extend 方法暴露的构造器,在别的组件内扩展的时候使用 calll 方法调用,这个实例的扩展方法。例如,我们也可以用来扩展 classes, style 等内容。
VSheet
VSheet 旨在为 Vuetify 中的其他 paper 组件提供支持。 它旨在用作低级组件。
- 混入
- BindsAttrs
- Colorable 包含了方法 setBackgroundColor
- Elevatable
- Measurable
- Themeable
VToolbar
它通常是站点导航的主要来源。工具栏是一个灵活的容器,可以通过多种方式使用。
- 扩展
- VSheet 的基础上进行扩展, 相当于链式的调用 extends 方法进行 options 合并
- 组件依赖
- VImg
- 工具
- convertToUnit 单位
- getSlot,获取插槽
- breaking
export function getSlot (vm: Vue, name = 'default', data?: object | (() => object), optional = false) {
if (vm.$scopedSlots[name]) {
return vm.$scopedSlots[name]!(data instanceof Function ? data() : data)
} else if (vm.$slots[name] && (!data || optional)) {
return vm.$slots[name]
}
return undefined
}
VSheet
Todo
- trantions
- VApp
- VAppBar
- VSheet
- VToolbar
- VAutocomplete
- VAvatar
- VBadge
- VBanner
- VBottomNavigation
- VBottomSheet
参考
...