在C/C++的时代我们所用的代码信息只有代码内容本身。编译器很少可以告诉我们代码运行在哪里以及运行的文件是哪些。
而在语言逐渐发展之后,一些特殊的信息能够在运行时被我们获取,如运行文件路径。通过这些信息和元编程的概念,我们实现一些特殊的程序来减少代码中重复的部分。
以vue
为例:
Vue的相同特性组件的一种优化思路
定义
相同特性组件是指,一组相同props以及listeners等特性,并完成类似功能的组件。
比如在我的项目中,表格的每个单元格可能由不同的类型组成:输入都为实际的数据,但展示位不同的样式。我的部分目录结构如下
# .../table/cell/
# Text.vue
# Color.vue
# Number.vue
# Image.vue
# ...
每个组件接收的参数都是一个JS的标准值,我们需要把不同的值转换成不同的显示方式。比如"#ffffff"
在Text
组件下显示为单纯的字符串,而在Color
组件下则应当展示为一个白色的颜色块。
问题
当组件数量不多时我们可以引用每一个组件到父组件中,但是当表格内容足够复杂,可能需要引用十几个基础组件时,import语句看起来就不那么“美观”了。尤其是页面越来越多时,每个页面都需要引用大量的基础组件,对import/components的管理以及日后可能的重构都是一种极大的负担。
解决方案
使用一个组件大致可以分为三步:
- 引用组件
import 组件 from ‘组件.vue’
- 声明组件
components: {..., 组件, ...}
- 调用组件
<组件 ...props/>
我们可以针对每一步来进行优化。
引用组件
我们使用的import语句会在webpack的转换下变成webpack的require,一般情况下我们需要显示的引用所有需要的组件,webpack才会将代码打包至生成文件中。
实际上webpack提供了一个require.context方法来实现整个目录(一定条件)的打包加载。我们可以使用这个特性来加载我们需要的任何组件:
// cell.js
const requireContext = require.context('./cell', true, /vue$/)
const components = {}
requireContext.keys().forEach(key => {
const nKey = /* 把实际的文件名转化成我想要的组件名 */;
components[nKey] = requireContext(key).default
})
这样我们就得到了一个map,包含了一系列特性相似的组件。
注:由于webpack不确定运行时你会使用什么组件,所以它会将所有匹配到的组件全部打包至目标代码。
声明组件 & 调用组件
上一部中我们已经得到了nKey => 组件
的map,所以可以直接使用到组件的components选项中使用,然而这样还是让组件产生了大量的components依赖,组件特性类似的性质也让我们有一种更统一的声明以及调用组件的方法:Proxy组件
。
声明 Proxy 组件
代理组件的作用就是通过组件名称,生成对应组件并传递组件需要的props以及listeners等信息。在这里我提供一个简单的实现:
// Proxy.js
export default {
name: 'table-cell-proxy',
functional: true,
inheritAttrs: false,
props: {
name: {
type: String,
required: true
}
},
render (createElement, {props, data}) {
return createElement(components[props.name], data)
}
}
由于表格渲染会大量使用单元格中的组件,所以proxy组件最好是functional的以提供最高的效率。proxy组件接受一个name参数用于找到对应的组件,并将所有其他的属性传递至实际渲染的组件中。其中需要的components变量即为上一步生成的map。
调用 Proxy 组件
iView中table的所有数据都使用render渲染:
columns () {
return this.getColumnsInfo()
.map(({title, key, type, meta}) => {
return {
title,
key,
render (h, params) {
return h(Proxy, {
props: {
name: type,
value: params.row[key],
meta
}
})
}
}
})
}
getColumnsInfo() {
return [
{
title: 'ID',
key: 'id',
type: 'id'
},
{
title: 'Name',
key: 'name',
type: 'text'
},
{
title: 'Theme'
key: 'theme',
type: 'color',
meta: {
format: '#rgba'
}
},
...
}
其中this.getColumnsInfo()
返回了一个列表,其中包含的type
代表了我们要渲染的组件类型,meta代表这个类型相关的一些额外的数据。
小结
通过以上的努力,我们把组件的引用以及声明组织到了一个目录下:
# .../table/
# cells/
# Text.vue
# Color.vue
# Number.vue
# Image.vue
# ...
# cell.js
# Proxy.js
调用时也只需要引用Proxy组件,这样使得我们有更多的精力去关注真正的业务代码,并更方便的去组织相同特性组件。
总结
本文主要介绍了如何通过Proxy组件
以及require.context
特性来组织相同特性组件的代码。用途不仅包括表格页面的单元格组件,也可以包括表单页面的输入组件等等,具体业务具体分析。也可以把Proxy组件看成一种抽象组件,不提供具体实现但提供了相同的接口。
本文提供的代码不为实际项目代码,仅供参考。
以上就是通过目录来组织相同特性组件的一个思路,利用了webpack的一些特性,我们利用这些目录信息,来简化了大量的代码。
在Java
里操作这些信息则更加方便,因为Java
自带反射的机制、包名也是用路径来划分。实际上spring-boot
的自动配置就用了这种类似的思想,引用了pom后自动扫目录并加载应该自动加载的配置和类,使得需要人工培植的地方大大减少。
这种实现虽然非常方便,但是要求使用者理解哪些元信息会影响到运行时,否则可能出现一些奇怪的BUG。也如我在之前的文章里提到了,以人为本,不要过度设计。