
ElementUI:Form 组件拆解
先来看看 ElementUI 的 Form 组件是如何使用的
1 | <el-form :model="form" label-width="auto" style="max-width: 600px"> |
el-form 组件作为最外层包裹的标签,model 属性是整个 form 组件传入的对象,内部的 form-item 包裹了具体组件内容,作为每个 form 组件的具体表现形式,在 form-item 标签上的属性 props 则是接收 form 传入的 model 对象的属性值,不需要在 input 上指定 v-model,即可更新 form.name 的值,那么这里面是怎么做的呢。
1 | <--! Form组件 --> |
上面的 Form 组件能看到,ElementUI 的 Form 组件是用的 html 原声的 form 标签实现的,传入一个 formClasses 覆盖原声的 class 样式
Form 组件用了 provide/inject 依赖注入的方式传递深层次属性,首先进行 provide 依赖收集
1 | provide( |
上面的 emit 是 form 组件导出的方法:validate,以及 resetField 之类的都是 form 全局方法,通过 form 全局依赖注入到 formContextKey 上,具体使用场景举个例子:
1 | const formRef = ref(); // 赋值给Form组件的ref属性变量 |
Form 组件完成了一些全局方法的注册,外层 props 的定义,以及顶层 form 标签的构建,那么 formItem 是如何接收这些属性的呢:inject
1 | const formContext = inject(formContextKey, undefined); |
这里有个问题,如何将 formItem 下的组件的值赋值给 model.prop 的属性上
1 | // 这里model.prop会将值作为fieldValue的默认值 |
1 | <input :id="inputId" :form="form" /> |
ElementUI:Table 组件
Table 组件导出了两个组件模块:Table 和 TableColumn,先来看看 Table 组件的设计
整个 Table 组件的 template 部分分为两个区域,整个组件由外层的 tableWrapper 包裹,tableWrapper 外层布局融合了传入的 style 和 class 属性,规定了 table 整体的布局,这些 style 和 class 以命名空间的形式动态的加载到 class 中
table 的核心分为:原生 table 包裹,table-header 组件,table-body,table-footer
其中,bodyWrapper 下使用了 scrollbar 组件包裹,目的是解决表格超出当前页面高度时,出现滚动条
1 | <div ref="bodyWrapper" :class="ns.e('body-wrapper')"> |
table-footer 是为了显示什么内容呢,上图能看到 showSummary,当需要显示表格总计的情况时,就在 table-footer 上显示
一个一个分析 Table 组件,从 table-header 开始
table-header 是用的 h()方法渲染的 UI,导出的是一个含有 setup 方法并且返回用 h 函数渲染的 dom 结构,具体设计如下
1 | return h( |
table-body 和 table-header 的思路相似,也是利用 tr、td、tbody 这些原生标签实现 dom 的构建
1 | // tbody的render实现 |
1 | // tfoot标签的render实现 |
可以借鉴的一些做法
- Style:和样式相关的内容,放在同一个文件内,如 styles-helper.ts,避免样式失效,要注意 style 行内样式顺序,classnames 的合并注意同名 class
- Events:emit 事件放在 events-hepler.ts
- 将不同的渲染内容区分开:如 table 的 footer、header、column 等等,都是作为单独 render 的组件聚合到一起
- Provider 和 Inject 的 key 用 symbol 修饰,并且使用常量命名,注意,使用 ts 类型断言的 as const
- 是否需要使用 store,看组件的复杂程度,element-UI 中的 Table 和 Form 均使用了 store,store 会增加组件的复杂度,谨慎使用
- Vue3 版本中推荐使用 hooks 抽象逻辑
- 单元测试:开发组件一定要加入单元测试,目的是为了发现组件设计的盲点和不合理的地方,甚至是内存管理问题。