注:本文基于Riot.js v2.5.0版本

Riot.js简介

  • 类似 React 的 微型 UI 库

  • 自定义标签/虚拟 DOM

  • 适合编写独立组件

  • MVP 架构

MVP 架构图

  • 支持 IE9+ ??

1.自定义标签

  1. 布局与逻辑耦合,可重用组件

  2. 实际上的语法糖—>编译为 JS

  3. 虚拟 DOM

    • 单向的数据传输: update 或 unmount 都是从 父亲->孩子
    • 预编译和缓存表达式,解析更加高效
    • 预处理器
    • 可用于服务器端
  4. 语法友好

    • 强大的属性缩写: class={enable: true, hidden: false}
    • 不需要额外的手动绑定,无需记忆 render,state,constructor 等
    • 可插值使用: Add #{items.length + 1} 或 class="item {selectd: true}"
    • 逻辑代码,可不放在 script 标签内
    • 可使用部分 ES6(完全使用需结合 Babel)

2.mixin

Mixin 可以将公共代码在不同标签之间方便地共享,可以混入 Object 和 new function(){}。

var OptsMixin = {
    init: function() {
      this.on('updated', function() { console.log('Updated!') })
    },

    getOpts: function() {
        return this.opts
    },

    setOpts: function(opts, update) {
        this.opts = opts

        if(!update) {
            this.update()
        }

        return this
    }
}

<my-tag>
    <h1>{ opts.title }</h1>

    this.mixin(OptsMixin)
</my-tag>

声明式 mixin 可在文件之间和项目之间共享 mixin:

riot.mixin("defaultData", {
    author: "ddfe",
    email: "shield@didichuxing.com"
});

// in custom tag
this.mixin("defaultData"); 

3.事件

  • 自定义标签创建过程:

    1. 创建标签实例
    2. 标签定义中的 JavaScript 被执行
    3. HTML 中的表达式被首次计算并首次触发 “update” 事件
    4. 标签被加载 (mount) 到页面上,触发 “mount” 事件
  • 监听生命周期事件

    • before-mount
    • mount
    • update(改写上下文数据)
    • updated(操作 DOM)
    • before-mount
    • unmount
  • 表达式更新方式:

    1. 当一个事件处理器被调用后自动更新。可以在事件处理器中设置 e.preventUpdate = true 来禁止这种行为。
    2. 当前标签实例的 this.update() 方法被调用时
    3. 当前标签的任何一个祖先的 this.update() 被调用时. 更新从父亲到儿子单向传播。
    4. 当 riot.update() 方法被调用时, 会更新页面上所有的表达式。

4.表达式

100%纯 JavaScript:

{ title || 'Untitled' }
{ results ? 'ready' : 'loading' }
{ new Date() }
{ message.length > 140 && 'Message is too long' }
{ Math.round(rating) }

可放在 html 节点中,也可作为文本节点嵌入:

<h3 id={ /* 属性表达式 */ }>
  { /* 嵌入表达式 */ }
</h3>

注:可通过riot.settings.brackets自定义花括号

5.杂烩

  • 嵌套标签

    <account>
      <subscription  plan={ opts.plan } show_details="true" />
    </account>
    
    <subscription>
      <h3>{ opts.plan.name }</h3>
      // 取得标签选项
      var plan = opts.plan,
          show_details = opts.show_details
      // 访问父标签实例
      var parent = this.parent
    </subscription>
      
      
      
    

    父标签的参数通过 riot.mount 方法的参数设置,而子标签的选项通过标签属性来传递。

    <script>
    riot.mount('account', { plan: { name: 'ddfe', age: '4' } })
    </script>
    

  • 嵌套HTML

    <!--definition-->
    <my-tag>
      <p>Hello <yield/></p>
      this.text = 'world'
    </my-tag>
    
    <!--apply-->
    <my-tag>
      <b>{ text }</b>
    </my-tag>
    
    <!--result-->
    <my-tag>
      <p>Hello <b>world</b><p>
    </my-tag>
    
  • 带有 name 或 id 属性的 DOM 元素将自动被绑定到上下文中,可直接访问

  • 事件处理器

    <login>
      <form onsubmit={ submit }>
      </form>
    
      // 上面的表单提交时调用此方法
      submit(e) {
      }
    </login>
    
    //e.currentTarget 事件处理器的所属元素
    //e.target 发起事件的元素
    //e.which 键盘事件中的键值 
    //e.item 循环中的当前元素
    

  • 渲染条件:if = {expression} show hide

  • 循环:each = {items}(对象数组)or each = {name, i in items}(非对象数组) or each = {name,value in items}(对象)(不建议使用)

    循环中的每一项将建立一个新的上下文,子标签通过 parent 访问父标签定义的方法和属性。

  • 使用标准 HTML 元素作为标签

    <ul riot-tag="my-tag"></ul>
    
    riot.mount('my-tag')
    
  • 服务端渲染

    var riot = require('riot')
    var timer = require('timer.tag')
    
    var html = riot.render(timer, { start: 42 })
    
    console.log(html) // <timer><p>Seconds Elapsed: 42</p></timer>
    

6. 编译

自定义标签会被编译为 JavaScript

  • 浏览器内编译

    <script src="todo.tag" type="riot/tag"></script>
    

  • 预编译:riot 命令

    npm install riot -g
    
    # 编译到当前目录
    riot some.tag
    
    # 编译到目标目录
    riot some.tag some_folder
    
    # 编译到目标路径
    riot some.tag some_folder/some.js
    
    # 将源目录下的所有文件编译至目的目录
    riot some/folder path/to/dist
    
    # 将源目录下的所有文件编译(合并)到单个js文件
    riot some/folder all-my-tags.js
    
    参数:
    -w watch 目录 有变化自动编译
    -ext html 指定后缀名
    --config config 使用config.js作配置文件
    --type 指定 js 处理器
    --template 指定 HTML 模板
    

  • 预处理器

    <script type="coffee"></script>
    

7.观察者 Observable(事件触发器)

Riot 提供 Observable 以便组件间通信,实现模块化。

// 方法1,创建一个观察者,返回一个实例,之后该对象便可以触发和监听事件
var ddfe = riot.observable({
});

//方法2,使 ddfe 成为观察者
riot.observable(ddfe);


// 监听事件
ddfe.on("event1", function(data1, data2){
    // 监听event1事件
    // data1 和 data2 是trigger传入的参数
    // data1 = 1, data2 = 2
    console.log(data1, data2);
});

// 发布一个事件
// 该事件带有 1 和 2 作为参数
// 上面的on("event1")的回调fn将会执行
ddfe.trigger("event1", 1, 2);

// 解除 event1 的所有监听,第二个参数可选
// 如果有第二个参数 [function],则只解绑该函数
ddfe.off("event1");

// one 与 on 类似,只是 one 如果执行过一次,就自动解除绑定
ddfe.one("event1", function(data1){
    console.log(data1);
});
ddfe.trigger("event1", 1, 2);

//删除所有事件的所有监听器
ddfe.off('*')

//对所有的事件删除指定的回调函数
ddfe.off('*',fn)

8.路由

一个最小化的路由器实现

功能:

  1. 修改 URL 的 hash 部分
  2. hash 变化时进行通知
  3. 查看当前 hash

API:

  1. riot.route(callback)

    riot.route(function(collection, id, action) {
    
    })
    
    //如果 url 变为 customers/987987/edit,则
    
    //collection = 'customers'
    
    //id = '987987'
    
    //action = 'edit'
    
  2. riot.route(filter, callback)

       // 精确匹配 `/fruit`
       riot.route('/fruit', function(name) {
         console.log('The list of fruits')
       })
    
       // 如果 url 变成 `/blog/2015-09/01`,
       // 回调的参数将被捕捉成 '2015', '09' 和 '01'
       riot.route('/blog/*-*/*', function(year, month, date) {
         console.log('The page of ' + year + '-' + month + '-' date)
       })
    
  3. riot.route.create()

    返回一个新的路由上下文

       <first-tag>
         <p>First tag</p>
         <script>
           var subRoute = riot.route.create() // 创建新的路由上下文
           subRoute('/fruit/*', function(name) {
             /* 公用的部分 */
           })
         </script>
       </first-tag>
    
       <second-tag>
         <p>Second tag</p>
         <script>
           var subRoute = riot.route.create() // 创建新的路由上下文
           subRoute('/fruit/apple', function(name) {
             /* 个别的部分 */
           })
         </script>
       </second-tag>
    
  4. riot.route(to[, title, shouldReplace])

    在内部实现中,
    
    • 如果没有 shouldReplace, 将使用 history.pushState().
    • 如果有 shouldReplace, 将使用history.replaceState().
  5. riot.route.start()

    开始监听路由变化,需要手动调用

  6. riot.route.start(autoExec)

    riot.route.start(true) <=> riot.route.start()+riot.route.exec()

  7. riot.route.stop()

  8. riot.route.query()

       // 如果 url 变成 `/search?keyword=Apple&limit=30` 将会匹配
       riot.route('/search..', function() {
         var q = riot.route.query()
         console.log('Search keyword: ' + q.keyword)
         console.log('Search limit: ' + q.limit)
       })
    
  9. riot.route.base(base)修改基础路径

  10. riot.route.parser(parser[, secondParser])

  11. 其他,路由优先级等

   riot.route('/fruit/apple', function() { /* */ }) // 路由-B (1)
   riot.route('/fruit/orange', function() { /* */ }) // 路由-C (2)
   riot.route('/fruit/*', function(name) { /* */ }) // 路由-A (3)

   riot.route(function() { /* */ }) // 路由-X (3)
   riot.route('/fruit/*', function() { /* */ }) // 路由-Y (1)
   riot.route('/sweet/*', function() { /* */ }) // 路由-Z (2)

支付宝扫码打赏 微信打赏

坚持原创技术分享,您的支持将鼓励我继续创作!

扫描二维码,分享此文章

逆葵's Picture
逆葵

网名逆葵。北邮土著,CS 硕士在读。《Vue.js 权威指南》作者之一。学习、思考、沉淀中, 向成为顶级 JavaScript 技术栈开发者努力。