【前端骚操作】易、轻、快!超牛逼纯JS前端框架Mithril
先看框架使用的成果:Koojee,看不了网页的请看截图:
这是工程项目,链接那个是 Vue 的,截图是 Mithril 的,效果是一样一样的。
先介绍一下 Mithril,一张图说明问题:
有经验的小伙伴应该很清楚关于 Vue 或者 React 以及 Angular 打包后包的大小,最小的 Vue 也要上 100K 以上。虽然有按需加载这种解决方案,但是,还是很大。。。
Mithril 的优势之一就是小,就是轻,加载快,响应快,如上方的对比测试。
关于 Mithril 的优势,主要是两个(快的这个需要测试,请相信官网):
-
非常轻巧
-
相当简单
轻巧非常容易理解了,就是压缩包只有 10K 不到,这个大小在当前的网络环境中,几乎就是秒加载,可以忽略不计的大小了。
所谓的简单,本文将使用上述的工程例子来简单说明,请小伙伴们跟紧了。
一般来说,一个 MVx 的 JS 框架到手,首先需要考虑的是数据驱动,向 Vue 的 data绑 定,React 的 setState 等,而 Mithril 相对这二者来说,更加简单,就是普通的变量,恩,就是这种:var a = 1
。然后在 Mithril 的 m 函数里面使用,变化就会直接使跟数据有关的 DOM 渲染了。
以下与工程无关的代码将直接使用官网的例子。
var count = 0 // added a variable
var Hello = {
view: function() {
return m("main", [
m("h1", {class: "title"}, "My first app"),
// changed the next line
m("button", {: function() {count++}}, count + " clicks"),
])
}
}
m.mount(root, Hello)
上述代码可以看到,count 只是普通的变量,然后 count 被用在一个 Hello 的组件里面,然后点击 button 就会造成 count + 1,从而使 Hello 组件渲染,效果如下:
好了,数据驱动理解了,提一下 m 函数与 m.render 还有 m.mount 即可进入我们的项目了。
m函数
m 函数就是 JS 生成 DOM 的函数,与h函数非常相似,h函数介绍 。
m 函数有三个参数:标签名称(支持 emmet),标签属性对象(一个 object 对象),标签内容。
m 函数还有种参数:组件,组件的参数对象。
如:
m('div#main', {class: 'normal'}, '我是div')
// <div id='main' class='normal'>我是div</div>
m('div#main', {class: 'normal'}, [m('button', '我是button')])
// <div id='main' class='normal'>
// <button>我是button</button>
// </div>
m([组件], [组件参数]) // 组件参数建议使用object
除了第一个参数,其余两个都可以为空,就是不写,然后第三个参数可以是单个 m 函数返回值也可以是数组。
m.render 函数
插入渲染结果的 DOM,插入的对象。
如:
m.render(document.body, "My first app")
就是 body 里面插入一段字符。
这里就是为什么我对MithrilJS念念不忘的原因,它不用强制生成个类似:
<div id='app'></div>
根结构的东西就可以直接渲染了。看什么,说的就是你,Vue。
m.render 只渲染一次,后续如果有数据变动,也不会渲染,所以我们不用。
m.mount 函数
这个与 m.render 参数差不多。
m.mount(document.body, [生成的组件])
m.mount 生成的会多次渲染,这是首选,但是我们也不用,原因是我们的页面是单页面牵扯到页面内路由,而 Mithril 提供了 m.route 这个路由启动渲染,我们使用的是这个,具体会在项目代码里面说。
进入项目
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>WBRoom</title>
<link rel="stylesheet/less" href="less/app.less">
</head>
<body>
<script src="https://cdn.bootcss.com/less.js/3.0.4/less.js"></script>
<script src="https://cdn.bootcss.com/mithril/1.1.6/mithril.js"></script>
<!-- <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.bootcss.com/less.js/3.0.4/less.min.js"></script> -->
<!-- <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.bootcss.com/mithril/1.1.6/mithril.min.js"></script> -->
<script src="js/components/header.js"></script>
<script src="js/components/footer.js"></script>
<script src="js/pages/homepage.js"></script>
<script src="js/pages/share.js"></script>
<script src="js/app.js"></script>
</body>
</html>
这里面是纯原生,没有使用 webpack 之类的打包,所以引用了很多组件 js,当然你也可以写到一个里面,但是那实在不好看也不好拓展。
使用了 less,为了偷懒,你让我在工程里面使用原生 CSS,臣妾真的做不到了。
less
less 的内容就不做过多展示,这个有兴趣在下方去源码里面看吧,这里略过了。
js
大头来了,我这里会直接贴出代码,然后在路由那里说一下,为什么我不多说呢,因为 Mithril 真的太简单了(我会告诉你我懒得说么)。。。
header.js
const Header = (function() {
return {
view(v) {
const titles = ['首页', '分享', '设计', '绘画', '摄影']
const hrefs = ['#!/', '#!/share']
const selectedIndex = v.attrs.selectedIndex
return m("header", [
m("h1", "KOOJEE"),
m('section.titles',
titles.map((title, index) => m('a', {class: 'title' + (selectedIndex === index ? ' selected' : ''), href: hrefs[index] || '#'}, titles[index])
)
),
])
}
}
})()
这里使用了自运行函数,为了是避免变量名污染。
组件就是个对象,组件 DOM 结构实在 view 这个属性值下的一个函数返回,函数可携带 vnode 对象,用来传参等等,获取参数使用v.attrs
。
其他就是常规内容,渲染了一个header标签,里面包了一个 h1 标签,包了一个 section.titles 标签, section.titles 里面根据titles数组生成了 5 个 a.title 标签。
这就是 header 了。
footer.js
const Footer = (function() {
return {
view() {
return m(
'footer'
)
}
}
})()
目前没东西,所以渲染了一个空的 footer 标签用来占位。
homepage.js
const Homepage = (function() {
const h2 = '最终选择了生活……'
const h3 = '宁愿向着远方哭泣,不愿望着当下诧异。'
let contentImgs = ['Bitmap1.png', 'Bitmap2.png', 'Bitmap3.png', 'Bitmap4.png']
let arts = [
{
img: 'Bitmap5.png',
main: '经历',
desc: '我在东方等你,不济那远去的夕阳。',
},
{
img: 'Bitmap6.png',
main: '兴趣',
desc: '给个游戏,能躺半年。',
},
{
img: 'Bitmap7.png',
main: '性格',
desc: '热辣似火,妖娆弄人?不不不,就是呆萌。',
},
{
img: 'Bitmap8.png',
main: '态度',
desc: '你到底准备用什么态度和姑奶奶说话?',
},
]
const Content = {
onbeforeremove(v) {
v.dom.classList.add("exit")
return new Promise(function(resolve) {
setTimeout(resolve, 500)
})
},
view() {
return m('main#homepage.fancy',
m('section.content-center', [
m('h2', h2),
m('h3', h3),
contentImgs.map((contentImg, index) => m('img', {class: 'img' + index, src: assetsPath + contentImg, alt: 'content-img'}))
])
)
}
}
return {
view(v) {
return [
m(Header, {selectedIndex: 0}),
m(Content),
m(Footer),
]
}
}
})()
因为将来要使用数据驱动,所以把数据提出去,方便后续服务器请求数据操作。
这里使用了 m 函数的组件模式,如上例子中的:m(Header, {selectedIndex: 0})
,就是 homepage 组件里面包含了一个 Header 组件,并且给 Header 组件传参了,这个参数的使用在上面 Header 组件那里可以看到。
注意这里有个内部的 Content 组件,这个是用来做动画的,比如你页面内路由切换时,为了看起来更舒服,可以做个过渡动画,Homepage 组件自带 fancy 类的 CSS,然后 Homepage 的 Content 组件在声明周期 onbeforeremove 即将要消失的时候添加了一个 exit 类的 CSS,具体两个 CSS 如下:
.fancy {animation:fade-in 0.5s;}
@keyframes fade-in {
from {opacity:0;}
to {opacity:1;}
}
.exit {animation:fade-out 0.5s;}
@keyframes fade-out {
from {opacity:1;}
to {opacity:0;}
}
了解 CSS 的同学就知道是什么动画了,对,就是淡入与淡出,分别 0.5s。
说到这里,刚好顺带过一下 Mithril 的声明周期,如下:
用法与 view 一样,放在对象的属性上,对应值是函数,都可以获取 vnode。
-
oninit
初始化时候,方便放一些准备用的数据,或者用来网络请求。此时可以拿到 vnode,但是不一定拿得到真实 DOM,所以这里不推荐进行相关的 DOM 操作,比如:
vnode.dom
。 -
oncreate
创建成功,此时可以拿到真实 DOM 了。
-
onupdate
DOM 渲染刷新后。业务有刷新变动数据时候使用。
-
onbeforeremove
DOM 销毁前。常用,比如我们的离开动画。
-
onremove
DOM 销毁后。一样不建议进行真实 DOM 操作,用来销毁垃圾数据可以使用。
-
onbeforeupdate
DOM 渲染刷新前。业务有刷新变动数据时候使用。
share.js
const Share = (function() {
let bannerSrc = 'https://wbroom-blog.oss-cn-hangzhou.aliyuncs.com/public/assets/share-bannre.png'
const Content = {
onbeforeremove(v) {
v.dom.classList.add("exit")
return new Promise(function(resolve) {
setTimeout(resolve, 500)
})
},
view() {
return m('main#share.fancy', [
m(`img.share-banner[src=${bannerSrc}]`),
m('section.arts',
[1,1,1,1,1,1].map((item, index) => m('figure.art', [
m(`img.head[alt=${index}]`, {src: 'https://wbroom-blog.oss-cn-hangzhou.aliyuncs.com/public/assets/Bitmap1.png'}),
m('figcaption.main', '别看,看也没博文。'),
m('span.time', '耶稣生日的那天'),
m('section.ctrl', [
m('span', '点赞(1000)'),
m('span', '评论(1000)'),
m('span', '浏览(100000)')
])
]))
),
m('section.pagination', '页码(待处理)'),
])
}
}
return {
view(v) {
return[
m(Header, {selectedIndex: 1}),
m(Content),
m(Footer),
]
}
}
})()
与 homepage 大同小异,只是堆页面,放置一些图片,页码也还没做~
注意,一样要设置进场动画与离场动画。
app.js
const rootPath = 'https://wbroom-blog.oss-cn-hangzhou.aliyuncs.com/'
const publicPath = rootPath + "public/"
const assetsPath = publicPath + "assets/"
m.route(document.body, '/', {
"/": Homepage,
'/share': Share,
})
这个就是常说的入口 js 了,由于存在 JS 对象的依赖关系,所以上面的组件不得不先加载,然后最后加载入口js。
第一:做一些全局的数据,比如本项目里面需要用到的图片资源 basic 路径等。
第二:路由的简单配置。
注意,Mithril 没有类似 Vue 的 router-view 这种组件,它还是建议把组件配到大组件里面,这个缺点就是如果业务的路由组件非常深入,就相对麻烦,但是优点还是简单!
这里配置根路径地址就是 Homepage 组件,当路由切换到 share 时候,就是 Share 组件显示了。
同样的,MithrilJS的路由标记像这样:#!/share
,默认是使用#!
,当然,你可以通过类似m.route.prefix("#")
来修改路由标记,但是个人觉得意义不大。起码别人逛你网站的时候,懂行的一眼就看出来你用的是 Mithril 写的页面,恩,厉害!
到最后,index.html 解析所有的内容后,就渲染出文章开头的页面了,点击切换时候还有淡入淡出的效果(上面的链接看不到,因为 Vue 版本我还没加路……)。
OK,简单,快速,小巧的一个牛逼纯 JS 框架 Mithril,带你们走了一遍简单的。
Mithril 当然也是支持 ES6,以及 JSX 的,本人不是很喜欢 JSX 的写法,所以采用了原生的,而要用以上两个,都需要去配置下 webpack,这里不做过多介绍了。
如果想看源码,请到下面的 GitHub 源码里面,找到 mi 分支即可,默认 master 分支是 Vue 版本哦。
项目源码:Github
欢迎关注,如有需要 Web,App,小程序,请留言联系。
共同学习,写下你的评论
评论加载中...
作者其他优质文章