受 Mithril.js 启发的零学习成本极易用框架
- 框架名称: mj.js (https://github.com/ahui2016/mj.js)
- 在介绍本框架之前,我不得不恳求:请先别带着偏见小瞧本框架的能力。
- 本框架是很容易被小瞧、被误解的,因为基于 jQuery, 第一版一共由两个函数共约 10 行代码构成。
真组件
本框架中的组件是真组件,因为:
- 生成组件不需要写 HTML 或 JSX, 而是完全使用 JavaScript
- 组件可以有自己的方法,可调用组件的方法使组件发生变化
- 组件可以套娃,即组件内可以嵌套另一个组件
- 组件与组件之间可以交流,互相调用或互相传递数据
第一版
以下两个函数就是本框架的全部代码了。(这是第一版,后来做了一点扩展,但第一版更简洁易懂,有利于讲解)
// 函数名 m 来源于 Mithril.js, 也可以理解为 make 的简称,用来创建一个元素。
function m(obj) {
if (typeof obj == 'string') {
return $(document.createElement(obj));
}
return obj.view();
}
// 函数名 cc 意思是 create a component, 用来创建一个简单的组件。
function cc(name, id, elements) {
if (!id) id = '' + Math.round(Math.random() * 100000000);
const vnode = m(name).attr('id', id);
if (elements) vnode.append(elements);
return {id: '#'+id, raw_id: id, view: () => vnode};
}
简单示例 (与 Mithril 对比)
Mithril 创建一个 component 是这样:
const Hello = {
view: () => m("div", [
m("h1", {class: "title"}, "My first app"),
m("button", "A button"),
])
}
使用 mj.js 创建一个 component 是这样:
const Hello = {
view: () => m('div').append([
m('h1').attr({class: 'title'}).text('My first app'),
m('button').text('A button'),
])
}
也可以这样:
const Hello = cc('div', 'id-123', [
m('h1').attr({class: 'title'}).text('My first app'),
m('button').text('A button'),
]);
可见,与 Mithril 的写法非常相似,同时又完全是 jQuery 的基本操作,对于已经会用 jq 的人来说,学习成本接近零。
新版
不久前我写了《超简单易用的 Java Web 框架 - Javalin》及其续篇《Javalin网站框架介绍之二 - 数据库》。
这两篇文章主要讲解了后端的程序,而前端我选择 mj.js, 完整的(包括前后端)可以运行的代码在这里 => github.com/ahui2016/monostich/tree/c34e88afa0 ... 这是 mj.js 的一个真实使用事例。
其中前端部分,重点关注的文件是:
- mj.js 框架本身,比第一版多加了几行代码,但仍然非常简单。
- mj-util.js 用 mj.js 制作的一些常用组件
- add-entry.js 一个用 mj.js 制作的页面,含有表单,可向后端发送 post 请求。
组件之间的相互嵌套与交流
在 mj-util.js 文件中有这样的函数:
function createInput(type = "text") {
return cc("input", { attr: { type: type } });
}
function createFormItem(inputComp, name) { /* ... */ }
在 add-entry.js 里是这样使用 createInput 函数的:
const NotesInput = createInput();
const CmdInput = createInput();
const SubmitBtn = cc('button', {text: 'Submit'});
const Form = cc('form', { children: [
createFormItem(NotesInput, 'Notes'),
createFormItem(CmdInput, 'Command'),
m(SubmitBtn).on('click', event => {
const body = {
notes: valOf(NotesInput),
cmd: valOf(CmdInput),
};
axios.post('/api/add-entry', body).then(() => { /* ... */ });
}),
]});
可见, SubmitBtn
是一个组件, Form
也是一个组件, SubmitBtn 可以嵌套进 Form 里,例如 Form = cc('form', {children: [ m(SubmitBtn) ]})
效果相当于:
<form>
<button>Submit</button>
</form>
另外 NotesInput
等组件也同样嵌套到 Form
里,然后在 SubmitBtn 的 click 事件中用第三方库 axios
向后端发送请求即可。
(同时也可以看到,在 SubmitBtn 的 click 事件中可以直接获取 NotesInput 的数据。在该例子中我使用了函数 valOf
, 另外也可以给 NotesInput 添加实现类似功能的方法,然后用类似 NotesInput.val()
的方式获取数据。)
在后端,用类似这样的代码接收该表单的请求 (参考 github.com/ahui2016/monostich/.../Handle.java):
app.post("/api/add-entry", ctx -> {
var data = ctx.bodyAsClass(EntryForm.class);
var entry = new Entry(
db.getNextId(),
data.notes(),
data.cmd(),
Util.now());
db.insertEntry(entry);
ctx.status(200);
});
其中 db.insertEntry()
详见 《Javalin网站框架介绍之二 - 数据库》中的 DB.java 部分内容。
如上所述,前端用 mj.js 不写 HTML, 完全用 JS 写组件,组件可以嵌套、可以有自己的方法、可以互相交流。
写好前后端代码后,用 mvn package
命令生成 jar 文件,用 java -jar XXX.jar
命令启动程序,通过浏览器访问 http://127.0.0.1:7070/add-entry.html
即可看到效果,可通过网页表单向后端发送请求,向数据库写入数据。(其中 add-entry.html 的源码见这里: github.com/ahui2016/monostich/.../public/index.html)
TypeScript
另外我也做过 mj.js 的 TypeScript 版本,如有兴趣可参考这个项目的源码 (前端 mj.js, 后端 Go) https://github.com/ahui2016/dictplus
总结
- mj.js 极致简单,非常直观,对于已经用过 jQuery 的人来说学习成本约等于零。
- 完全不使用 npm 或类似工具链,用
<script src="jquery.js"></script>
引用 jQuery 就可以直接写代码了。 - 完全用 JavaScript 写组件,不需要写 HTML 或 JSX
- 组件可以复用、可以嵌套、可以有自己的方法、可以相互交流
- 可以配合 Bootstrap 之类的前端框架使用
- 不适合用来做有很多即时交互的 web app, 但如果用来做博客、论坛之类的前端是完全够用的,而且页面是由一个个 "真组件" 搭建起来的,学习成本低、条理很清晰、可维护性强。