现代前端的发展涌现出很多出色的 JavaScript 框架,在性能和效率方面各有特色,每个前端团队都有自己使用得比较顺手的开发框架。本文介绍一个新的前端框架:​​Mithril.js​​​,是一个高性能 JavaScript 框架,它的大小和速度使其成为满足视图模型需求的不错选择。在 ​​Mithril.js​​​ 中的大部分工作都是纯 JavaScript。除此之外,​​Mithril.js​​ 是学习函数式编程的不错的教程。本文通过构建一个简单的应用程序,来展示帖子列表,并通过简单步骤实现 CRUD 操作:

  • 项目初始化
  • 设置应用组件
  • 设置页面之间的路由
  • 实施清单
  • 实现创建/更新表单
  • 添加删除按钮
  • Bootstrap美化UI

如果需要查看最终结果,可以在 GitHub 上找到它。

​Mithril.js​​ 官方网站:mithril.js.org/

项目初始化

前端项目框架引入的方式一般有通过引用公共 CDN 上的核心 Mithril.js 文件、通过 ​​npm install​​ 或者通过其它脚手架进行安装,本文将通过脚手架 Vite.js 来进行项目初始化,在这个应用程序中将使用 TypeScript 。

首先,创建基本应用程序并安装依赖项,这里将项目命名为 mithril-study ,然后使用 ​​Vite​​​ 模板 ​​vanilla-ts​​。

npm init vite@latest ./ --template复制代码

安装依赖:

npm install mithril -- save
npm install bootstrap --save
npm install @types/mithril -- save-dev
复制代码

设置应用组件

现在来创建应用程序组件,它将呈现应用程序的根元素(​​<div class="app">...</div>​​),并将成为应用程序中所有其他组件的容器。将 ​​src>main.ts​​ 的代码替换为:

import "./style.css";
import m from "mithril";
function App(): m.Component {
return {
view: () =>
m(
"div.app",
m("header", "Mithril Study"),
m("main", "[main content]"),
m(
"footer",
"Mithril is simple yet powerful,一个简单却很强大的前端框架"
)
),
};
}
m.mount(document.body, App);
复制代码

那么这里发生了什么?


  • 安装的依赖 ​​Mithril.js​​ 都在文件夹 ​​node_modules​​ 中,在应用程序中通过 ​​import​​ 导入,并给其取了一个简称 ​​m​​ ,方便后续调用。
  • 定义了一个函数 ​​App​​,它返回一个 ​​Mithril​​ 组件。 定义了一个返回 ​​Mithril​​ 组件的函数 ​​App​​ 。​​Mithril​​ 组件是任何具有视图方法的对象。由 ​​App​​ 函数返回的组件,将从此调用 ​​app​​ 组件。
  • 视图方法将 ​​m​​ 作为一个函数调用,它的第一个参数看起来就像CSS选择器 ​​div.app​​。这将呈现为 ​​<div class="app"></div>​​。对 ​​m​​ 的多个调用将作为参数添加到第一个调用中,这些调用将作为嵌套元素呈现。
  • 最后,告诉 ​​Mithril​​ 渲染 ​​App​​ 函数返回的任何内容并将其渲染到 ​​document.body​​ 元素中。

现在在终端中运行 ​​npm run dev​​ 来运行 ​​Vite​​,然后导航到该应用,效果如下:

Mithril.js:一个高性能 JavaScript MVC 框架_javascript

打开根目录下文件​​index.html​​ 并删除带有 ​​id app​​ 的 ​​div​​,因为项目渲染的根是 ​​document.body​​ 。

设置页面之间的路由

设置路由之前下面三个函数添加到 ​​src>main.ts​​ :

function GetList(): m.Component {
return {
view: () => "Get List",
};
}
function PostForm(): m.Component {
return {
view: () => "Post Form",
};
}
function setupRouting(root: Element): void {
m.route.prefix = "";

m.route(root, "/", {
"/": GetList, // 获取列表
"/posts": PostForm, // 发送数据
"/posts/:id": PostForm, // 更新数据
});
}
复制代码

前两个是组件函数,因此约定组件函数使用首字母大写的命名形式,就像 ​​App​​ 函数一样,需要渲染一些虚拟文本。

​setupRouting​​ 函数作用如下:


  • 首先,它从 DOM 中获取一个 HTML 元素作为输入。
  • 通过将 ​​m.route.prefix​​ 设置为空字符串,定义了应该如何从浏览器 URL 解析路由。如果忽略,​​Mithril​​ 会像其他前端框架一样使用描点hash ​​#!​​,这将提供类似 ​​http://localhost:3000/#!/posts/1​​ 的 URL。通过将其设置为空字符串,将 URL 格式改成如下形式 ​​http://localhost:3000/posts/1​​。
  • 调用 ​​m.route(...)​​ 函数,为其设置三个参数,第一个是充当路由容器的 HTML 元素,即所有路由内容都将显示在该元素中。第二个参数是当浏览器导航到没有定义路由的 URL 时要使用的回退路由。第三个参数是一个将路由(URL 路径)映射到组件函数的对象。
  • 最后,查看路由​​/posts/:id​​。此路由将 URL 的一部分映射到变量 ​​id​​。可以在组件中检查该路由参数。

需要从某个地方调用 ​​setupRouting​​,为其提供一个 HTML 元素。在 App 函数中,将 ​​m("main", ...)​​ 行更改为以下内容:

m("main", {
oncreate: (vnode: m.VnodeDOM) => setupRouting(vnode.dom),
})
复制代码

当 ​​m​​ 的第二个参数是非组件对象时,它用于配置由第一个参数创建的元素。在这种情况下,就将使用 ​​Mithril​​ 的生命周期钩子之一:​​oncreate​​。当 ​​Mithril​​ 在 DOM 中创建元素并公开一个附加了 DOM 元素的虚拟节点(元素的内部表示)时,会调用此钩子。将该 DOM 元素提供给 ​​setupRouting​​。

现在在浏览器输入 ​​http://localhost:3000​​ 看到主元素中的内容为 ​​Get List​​,然后输入 ​​http://localhost:3000/posts​​ 可以看到主内容变成了 ​​Post Form​​ 。

实现真实列表

前面的列表只是一个文本内容,现在开始通过 Ajax 来获取列表数据。即实现 ​​GetList​​ 组件,添加以下接口并更新 ​​GetList​​ 函数:

interface IPost {
userId: number;
id: number;
title: string;
body: string;
}
function GetList(): m.Component {
const posts: IPost[] = [
{
userId: 1,
id: 1,
title: "JavaScript数据结构之 Array",
body: "JavaScript 中,数组是经过改进的对象,和其他语言不同的是,数组中每个槽位可以存储任意类型的数据,这意味着可以创建一个数组,它的第一个元素是字符串、第二个元素是数字、第三个是对象。",
},
{
userId: 1,
id: 2,
title: "JavaScript数据结构之Object",
body: "Object 是 ECMAScript 中最常用的数据类型之一,很适合存储和在应用程序之间交互数据。Object 定义一组属性的无序集合,可以将其想象成一张散列表,其中的内容就是一组名/值对,值可以是数据或者函数。",
},
];
return {
view: () =>
m(
"section.post-list",
m(
"ul",
posts.map((post) =>
m(
"li",
m(
m.route.Link,
{
href: `/posts/${post.id}`,
},
post.title
)
)
)
),
m(
m.route.Link,
{
href: "/posts",
className: "button",
},
"新增"
)
),
};
}
复制代码

上面的代码主要做的事情如下:


  • 首先定义了文章的数据结构,即接口 ​​IPost​​,并定义文章数据 ​​posts​​ ;
  • 在 ​​view​​ 方法中,创建了一个列表 (​​ul​​) 的部分,并为其设置了一个 CSS 类;
  • 增加了一个 ​​新增​​ 的连接按钮

查看运行效果,如下所示:

Mithril.js:一个高性能 JavaScript MVC 框架_应用程序_02

接下来,回到 ​​GetList​​ 函数增加真实接口调用数据,在 ​​return​​ 前面插入一下代码:

m.request<IPost[]>({
method: "GET",
url: "https://jsonplaceholder.typicode.com/posts",
}).then((data) => posts.push(...data));
复制代码

调用 ​​m.request​​,并将响应数据插入到变量 ​​posts​​ 中,在使用 TypeScript 时,向 ​​m.request<IPost[]>​​ 添加一个类型参数,由此可以预测到响应数据是什么类型,运行效果如下:

Mithril.js:一个高性能 JavaScript MVC 框架_javascript_03

实施创建/编辑页面

将更新 ​​PostForm​​ 函数,并添加一个函数来保存文章数据:

function savePost(post: IPost, callback: Function): void {
const isCreate = post.id === -1;
const url =
"https://jsonplaceholder.typicode.com/posts" +
(isCreate ? "" : `/${post.id}`);
m.request<IPost>({
method: isCreate ? "POST" : "PUT",
url,
body: post,
}).then((data) => callback(data));
}

function PostForm(): m.Component {
let post: IPost = {
userId: -1,
id: -1,
title: "",
body: "",
};
const id = m.route.param("id");
if (id) {
m.request<IPost>({
method: "GET",
url: `https://jsonplaceholder.typicode.com/posts/${id}`,
}).then((data) => (post = data));
}
return {
view: () =>
m(
"form",
m(
"div",
m("label", "标题:"),
m('input[type="text"]', {
value: post.title,
onchange: (e: Event) =>
(post.title = (e.target as HTMLInputElement).value),
})
),
m(
"div",
m("label", "内容:"),
m('textarea[rows="5"]', {
value: post.body,
onchange: (e: Event) =>
(post.body = (
e.target as HTMLTextAreaElement
).value),
})
),
m(
"div",
m("label", ""),
m(
'button.button[type="button"]',
{
onclick: () => savePost(post, () => {
console.log("保存操作完成");
}),
},
"立即保存"
)
)
),
};
}
复制代码

然后点击文章,查看如下效果:

Mithril.js:一个高性能 JavaScript MVC 框架_TypeScript_04

添加删除按钮

作为最后一个 CRUD 操作,这里将要实现删除功能。回到 ​​GetList​​ 函数并在每个列表项中添加一个删除按钮,并为该按钮绑定单击事件,用于向 API 发送删除请求。

function deletePost(postId: number): void {
m.request({
method: "DELETE",
url: `https://jsonplaceholder.typicode.com/posts/${postId}`,
});
}


Bootstrap

现在使用 Bootstrap 对界面进行简单的调整,具体的代码这里就不展示,可以在 GitHub 上查看,最终效果如下:

Mithril.js:一个高性能 JavaScript MVC 框架_应用程序_05

总结

至此就构建了一个简单的 CRUD 应用程序。 ​​Mithril​​​ 负责渲染、路由和 HTTP 请求,而纯 JavaScript 则负责其余的工作。到目前为止,可以看到 ​​Mithril​​ 构建应用的简单。由于是一个学习性质的,一些交互并非完整,如保存或者删除成功后的操作没有实现,包括后续逻辑的更新。如有兴趣可以参阅官网文档:mithril.js.org/