现代化 JavaScript 框架 Mithril 的简单介绍及用法
本文转载于 SegmentFault 社区
一、Mithril 介绍
1. 是什么
最新版本 (2.0.4) 支持 IE11 以上的浏览器,v1版本支持 IE9 以上。
2. 与其他框架的对比
至于 Mithril 和 Angular 以及 Vue 之间,差不多可以类比 React 与这两者之间的区别。
3. 特点
压缩后体积小,无依赖
api 少,上手简单
提供了一个模板引擎与一个虚拟的 DOM diff 实现,实现高性能渲染
自动重绘
• mvc
层次化的 mvc 组件,耦合性低可维护性高
二、用法示例
1. 安装
<!-- Development: whichever you prefer -->
<script src="https://unpkg.com/mithril/mithril.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mithril/mithril.js"></script>
<!-- Production: whichever you prefer -->
<script src="https://unpkg.com/mithril/mithril.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mithril/mithril.min.js"></script>
npm install mithril --save
如果项目是使用 ts 开发的,需要安装类型声明文件。
npm install @types/mithril --save-dev
2. 基本使用示例
const vnode = m('div.container', [
m("li", "hello"),
m("li", "mithril")
]);
m.render(document.body, vnode);
//
// 上面代码会生成如下的 HTML 结构
// <div class="container">
// <span>hello</span>
// <span>mithril</span>
// </div>
3. JSX
const MyComponent = {
view(vnode) {
return (
<div>Hello Mithril</div>
)
}
}
m.mount(document.body, <MyComponent />);
如果项目使用了 Webpack 的话,直接在 Webpac k的配置中添加 Babel 配置。 首先创建 .babelrc 文件
{
"presets": ["@babel/preset-env"],
"plugins": [
["@babel/plugin-transform-react-jsx", {
"pragma": "m",
"pragmaFrag": "'['"
}]
]
}
npm install @babel/core babel-loader @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
最后在 Webpack 的配置文件中添加 Babel 相关的配置信息
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './bin'),
filename: 'app.js',
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}]
}
}
一样先要创建 .babelrc 文件,文件具体内容和上面 Webpack 中的一样。
npm install @babel/core @babel/cli @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
同样的,在 Mithril 中, JSX 最终也会被转换成 Hyperscript 语法,文章中的例子一般都是用的 Hyperscript (m("span", "hello mithril"))。
上面 JSX 代码转换后:
三、核心概念
1. vnodes
虚拟 DOM 节点 (vnode) 是一个 JavaScript 对象,具有以下属性:
2. 组件
const component = {
view() {
return m('div', 'mithril component')
}
}
组件和虚拟 DOM 节点都有生命周期方法,包括 oninit 、 oncreate 、 onupdate 、 onbeforeremove 、 onremove 和 onbeforeupdate。
把一个 attrs 对象传入到 m() 函数的第二个参数,即可把参数传入到组件实例中,然后在组件的视图和生命周期方法中可以通过 vnode.attrs 来访问数据:
const component = {
view(vnode) {
return m("div", "Hello, " + vnode.attrs.name);
}
};
m(component, { name: 'Component' });
3. 生命周期
4. 自动重绘
需要注意的是调用 m.mount() 或 m.route() 才会开启自动重绘,通过 m.render() 渲染的 vnode 不会进行自动重绘。
下面 3 种方法可以触发自动重绘:
-
DOM 事件之后触发 -
调用请求 (m.request()) 方法之后 -
改变路由时 (m.route())
const componemt = {
text: 'initial state',
view() {
return m('div', [
m('h1', `${this.text}`),
m('button', {onclick: e => {
this.text = 'state change';
e.redraw = false; // 禁用重绘
}}, 'click')
])
}
}
m.mount(document.body, componemt);
const componemt = {
text: 'initial state',
oninit() {
setTimeout(() => {
this.text = 'state change';
m.redraw(); // 手动进行重绘
}, 1000);
},
view() {
return m('h2', this.text);
}
}
m.mount(document.body, componemt);
5. keys
有 key 意味着,如果数据数组被打乱,且视图被重新渲染,DOM 元素将按照和以前一致的排序方式进行排序,以便保持正确的焦点和 DOM 状态。
const userInputs1 = {
users: [
{ id: 1, name: 'John' },
{ id: 2, name: 'Mary' },
],
view() {
return m('div', this.users.map(user => m('input',
{
value: user.name,
onclick: () => {
setTimeout(() => {
this.users[0] = this.users.pop();
m.redraw();
}, 3000);
}
}
)));
}
}
const userInputs2 = {
users: [
{ id: 1, name: 'Andi' },
{ id: 2, name: 'Sam' },
],
view() {
return m('div', this.users.map(user => m('input',
{
key: user.id,
value: user.name,
onclick: () => {
setTimeout(() => {
this.users[0] = this.users.pop();
m.redraw();
}, 3000);
}
}
)));
}
}
m.mount(document.querySelector('.div-1'), userInputs1);
m.mount(document.querySelector('.div-2'), userInputs2);