元编程快速入门

什么是元编程(Metaprogramming)

一言以蔽之,元编程就是通过代码实现代码的一种方式。

在一般情况下,我们写的代码是直接对应的业务逻辑的。在一定程度上可以说是”所见即所得”的。这种代码是最符合直觉的。但是在某些情况下,正常的代码会显得不是非常高效。因此我们需要通过用代码来生成代码的方式来开发,这就是我们所说的元编程。

为了更好的理解元编程,我们来用一个非常简单的例子来说明一下元编程:

我们需要生成一些假的电话号码,用一般的代码可能会是这样实现的:

1
2
3
4
5
6
const mobilePool = ["18012345678", "18112345678", "18812345678"];

function getRandomMobileNum() {
const len = mobilePool.length;
return mobilePool[Math.floor(Math.random() * len)];
}

不论这个函数如何调用,都是从一个有限池(代码)中获取的某一项内容。但是实际上这种方式是比较受限的,因此以元编程的思想来说,我们可能会写出这样的代码:

1
2
3
4
5
6
7
8
9
const mobilePrefix = ["180", "181", "188"];

function getRandomMobileNum() {
const len = mobilePrefix.length;
return (
mobilePrefix[Math.floor(Math.random() * len)] +
Math.random().toString().substr(2, 8)
);
}

即我们设定一个规则: 一个手机号是以 180,181,188 开始的, 然后后面跟上 8 位任意数字的,均视为一个合法的手机号。

其元信息为: 手机号是一个以固定3位开头加8位任意数字组合而成的11位数字

那么我们就可以根据这种固定的规则生成无限(相对手写)种可能的手机号码。

当然,相信大家都能写出代码。元编程也不是什么比较新鲜的东西,大家可能都写过类似的东西。元编程的思想对于我们来说也是一种必须掌握的编程能力。

元编程能做什么

当然上面的 case 过于简单,甚至可能都不能算得上元编程。元编程的概念在很多比较底层、基础的场景是非常常见的概念。

比较常见的场景是:

  • 编程语言(比如 typescript, coffee)
  • 框架语言(比如 jsx, vue-template 等)
  • 低代码/无代码平台
  • 后端 CRUD 框架(比如 graphql)
  • 各类 linter, formatter, parser, transformer, generator.

在平时开发中,我们常用的 antdTable/Column 这种形式的代码也是一种元编程方式, 而 Form 则是普通的编程方式

如何实现元编程

我们通过来写一个元编程的表单来简单说一下元编程的实现。

在普通的表单中,我们的组件可能是以下这样的:

1
2
3
4
5
6
7
8
9
10
11
function App() {
return (
<form>
<input name="username" required />
<input name="password" type="password" required />
<textarea name="desc" />

<button type="submit">Submit</button>
</form>
);
}

这是一个简单的用户注册表单, 为了多样性我还增加了一个desc属性用于描述用户的记录。

好的,那么如果用元编程的思想去看待这个问题呢?我们把 “渲染” 和 “数据” 分开考虑:

  • 这个表单有以下字段:
    • username
    • password
    • desc
  • 一个表单可能有以下类型:
    • input
    • password
    • textarea

然后通过组装可以变成一个数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const meta = [
{
name: "username",
type: "input",
required: true,
},
{
name: "password",
type: "password",
required: true,
},
{
name: "desc",
type: "textarea",
required: false,
},
];

这个就是一个元数据,用于表达想要去渲染的界面是怎么样的一个结构。

当然,仅仅有数据还是不够的。因此我们还需要一个通用的组件来将这个元数据转换成实际的渲染视图。

那么这个组件的接口应该是这样的

1
<MetaForm meta={meta} onSubmit={handleSubmit} />

这个组件接受一个meta对象用于”填充”内容,返回一个点击提交按钮的回调来告知外部

我们来实现一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const MetaForm = ({ meta, onSubmit }) => {
const formRef = useRef();

return (
<form
ref={formRef}
onSubmit={(e) => {
e.preventDefault();

onSubmit(
meta.reduce(
(obj, item: any) => ({
...obj,
[item.name]: e.currentTarget.elements[item.name].value,
}),
{}
)
);
}}
>
{meta.map((item) => {
if (item.type === "input") {
return <input name={item.name} required={item.required} />;
}
if (item.type === "password") {
return (
<input name={item.name} type="password" required={item.required} />
);
}
if (item.type === "textarea") {
return <textarea name={item.name} required={item.required} />;
}

return <div>Unknown Type</div>;
})}

<button type="submit">Submit</button>
</form>
);
};

以上就是一个非常简单的元编程的表单组件,后面想要的话还可以自定义type实现, 增加更多的属性, 增加校验规则等等。。而在一个完善的元编程的架构下,开发是非常迅速的。

作为开发,我们只要能够掌握元编程的思想,对我们对于开发的架构思想是非常有帮助的。

文章目录
  1. 1. 什么是元编程(Metaprogramming)
  2. 2. 元编程能做什么
  3. 3. 如何实现元编程