zweizhao.github.io

项目,文章,随笔,博客,Markdown,个人网站,人生感悟。

View on GitHub

先看框架使用的成果:Koojee,看不了网页的请看截图:

首页

分享

这是工程项目,链接那个是Vue的,截图是MithrilJS的,效果是一样一样的。


先介绍一下MithrilJS,一张图说明问题:

MithrilJS

有经验的小伙伴应该很清楚关于Vue或者React以及Angular打包后包的大小,最小的Vue也要上100K以上。虽然有按需加载这种解决方案,但是,还是很大。。。

MithrilJS的优势之一就是小,就是轻,加载快,响应快,如右侧的对比测试。

关于MithrilJS的优势,主要是两个(快的这个需要测试,请相信官网):

  1. 非常轻巧

  2. 相当简单

轻巧非常容易理解了,就是压缩包只有10K不到,这个大小在当前的网络环境中,几乎就是秒加载,可以忽略不计的大小了。

所谓的简单,本文将使用上述的工程例子来简单说明,请小伙伴们跟紧了。


一般来说,一个MVx的JS框架到手,首先需要考虑的是数据驱动,向Vue的data绑定,React的setState等,而MithrilJS相对这二者来说,更加简单,就是普通的变量,恩,就是这种:var a = 1。然后在MithrilJS的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组件渲染,效果如下:

count

好了,数据驱动理解了,提一下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生成的会多次渲染,这是首选,但是我们也不用,原因是我们的页面是单页面牵扯到页面内路由,而MithrilJS提供了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 src="https://cdn.bootcss.com/less.js/3.0.4/less.min.js"></script> -->
    <!-- <script src="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

大头来了,我这里会直接贴出代码,然后在路由那里说一下,为什么我不多说呢,因为MithrilJS真的太简单了(我会告诉你我懒得说么)。。。

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。

说到这里,刚好顺带过一下MithrilJS的声明周期,如下:

用法与view一样,放在对象的属性上,对应值是函数,都可以获取vnode。

  1. oninit

    初始化时候,方便放一些准备用的数据,或者用来网络请求。此时可以拿到vnode,但是不一定拿得到真实DOM,所以这里不推荐进行相关的DOM操作,比如:vnode.dom

  2. oncreate

    创建成功,此时可以拿到真实DOM了。

  3. onupdate

    DOM渲染刷新后。业务有刷新变动数据时候使用。

  4. onbeforeremove

    DOM销毁前。常用,比如我们的离开动画。

  5. onremove

    DOM销毁后。一样不建议进行真实DOM操作,用来销毁垃圾数据可以使用。

  6. 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路径等。

第二:路由的简单配置。

注意,MithrilJS没有类似Vue的router-view这种组件,它还是建议把组件配到大组件里面,这个缺点就是如果业务的路由组件非常深入,就相对麻烦,但是优点还是简单!

这里配置根路径地址就是Homepage组件,当路由切换到share时候,就是Share组件显示了。

同样的,MithrilJS的路由标记像这样:#!/share,默认是使用#!,当然,你可以通过类似m.route.prefix("#")来修改路由标记,但是个人觉得意义不大。起码别人逛你网站的时候,懂行的一眼就看出来你用的是MithrilJS写的页面,恩,厉害!


到最后,index.html解析所有的内容后,就渲染出文章开头的页面了,点击切换时候还有淡入淡出的效果(上面的链接看不到,因为Vue版本我还没加路……)。


OK,简单,快速,小巧的一个牛逼纯JS框架MithrilJS,带你们走了一遍简单的。

MithrilJS当然也是支持ES6,以及JSX的,本人不是很喜欢JSX的写法,所以采用了原生的,而要用以上两个,都需要去配置下webpack,这里不做过多介绍了。

如果想看源码,请到下面的GitHub源码里面,找到mi分支即可,默认master分支是Vue版本哦。


项目源码:Github


喜欢我的文章,请关注一波啊,定期更新技术文章,满满的都是干货!