在读完 Muut 上 Frameworkless JavaScript 这篇博文后,我遇上了 Riot,请一定先阅读该博文!Muut 的程序员拿出实际行动编写了 Riot,一个 类似React 的用来构建响应式UI组件的微型库。

阅读Riot的文档时,令我感触最深的是 Riot 竟如此容易理解——相比 React 来说,在 Riot 里需要学习的术语和概念极少(说实话,和 Polymer 和 Angular 等比起来,Riot 也是十分简单易懂的)。

为了有助于学习 Riot,我把自己用 React 编写的 flux-backbone-todo 搬运到了用 Riot 编写的 Riot Todo app 上。这篇博文就是我记录这次重构经历的笔记的合集。

如果觉得文章太长,这里是精简版:

  1. Riot 确实践行了它最小化的座右铭。Todo 应用的 Riot 版本(未压缩)只有很小的 32 KB。作为对比,React 版本则达到了 964 KB 之多(即使减去在 Riot 版本中未使用到的 Backbone 和 jQuery,React 版本仍然有 600 KB)。不管你怎么看,这都是一个巨大的差异。

  2. Riot 有那种很少见的“刚刚好”的感觉,使用起来非常愉悦。

  3. Riot 是一个相对较新的方案,因此我没有找到其在大一些的项目中的性能方面的统计数据。在大型项目中 Riot 表现究竟怎样尚未有定论,我希望它能够做的很好。

如果你对 Riot 还不熟悉,可以先去浏览一下 Riot官网——开发文档是第一课。我将列出一些我所学到的但是阅读文档时不一定很明显易懂的内容,而不会去讨论 Riot 是如何工作的。

在 Riot 中使用 ES6

示例的应用采用 ES6 编写,我使用 6to5 转译器将其转换为 ES5 代码,使用 Webpack 将编译后的代码以及需要的库一起打包。这种方式使得联结 JavaScript 模块成为必要——当你理解最新的 ES6 中 importexport 表述的优势时(看这个示例),你就会知道使用 ES6 编写代码是非常棒的。

Webpack通过配置可以使用 6to5 loader 将 ES6 源码自动转换成 CommonJS 格式的 ES5 模块,再将其打包至一个单独的 bundle.js 文件。

为什么我不使用 Riot 的 .tag 文件

Riot 标签文件是指包含 HTML 标记以及 JavaScript UI 逻辑的 HTML 模板。如果你已经浏览过了上面提到的 Todo应用,你可能会疑惑标签文件在哪里——答案是我已经不再使用它们,并且更喜欢用 JavaScript 来替代之。去除 .tag 文件简化了我的编码、加工和工作流程。对我来说,标签文件的复杂性和局限性大于它任何可以感知到的优点。

这并不是对 Riot 的一种批判。对标签文件来说,灵活的地方在于它完全可选而非强制使用,在此记录我不使用它的原因。

当你审视编译后的 JavaScript 代码时,你会看到 Riot 标签文件其实是一层轻微的语法糖.

  • 它添加了额外的概念层——新的或者比较新的语法和语义需要学习。

  • 它添加了额外的编译步骤。

  • 标签文件编译器指定了你可以使用的语言和模板(CoffeeScript、ES6和Jade),这有悖于“使用你最喜欢的工具”的理念。

  • 标签构造主体的逻辑脱离上下文:

    • 这使得标签文件无法被编辑器/IDE 的代码检测以及其他的工具处理
    • 涉及 this 时,脱离上下文意味着代码不是合法的 JavaScript 并且在编辑器/IDE 中会报错
  • 目前没有对模块化(CommonJS,AMD)标签文件编译为普通 JavaScript 的支持。

  • 标签文件需要构建工具(比如 Webpack 和 Browserify)直接使用标签转换器来进行转换。

  • 类似 ES6 的构造方法很棒但它们不是合法的 JavaScript 并且很可能总是成为持续混乱(语法和语义上)的来源。你可以使用 ES6 的箭头函数以几乎相同的简洁程度来获得相同的语义(拥有词法作用域的 this),举例如下:

            this.add = (e) => {
            	var input = e.target[0]
            	this.items.push(input.value)
            	input.value = ''
            }; 
    

这里有一个使用 ES6 模板字符串和箭头函数的 ES6 形式的 JavaScript 标签文件的例子

ES6 模板字符串提高了标签 HTML 模板的可读性。相似的,如果你使用的是 CoffeeScript,那么你可以使用 CoffeeScript 块字符串。JSX 是另一种可选项——React 的 JSX 转换器可以经过修改然后生成字符串文本,这样你就可以获得现有的 JSX 工具的支持。

Riot和React的基本区别

最重要的区别在于 UI 标记模板是如何声明的:

  • 在 React 中 UI 标记模板是在你的 JavaScript 源码中生成的(使用 JSX 语言的扩展)。
  • Riot 则反转了 React 的模型,将标记和逻辑都放在 HTML(标签)文件中。

这种反转的结果是 React 模板 DSL(领域特定语言)就是 JavaScript,而 Riot 依赖自定义的模板 DSL(采用自定义标签实现)。下面是两个简化的从一个 todo 事项的数组中生成一个列表的例子,第一个采用 React JavaScript编写,第二个是等价的 Riot 标签标记形式:

<ul>
  todos.map(todo =>
    <li><TodoItemComponent todo={todo} /></li>)
</ul>
<ul>
  <li each="{todo in todos}">
    <todo-item todo="{todo}">
  </li>
</ul>

第一个例子中使用了 JavaScript 的 map 函数来生成一个 <li> 元素的列表;第二个例子则使用了 Riot 自定义的 each 模板属性。

Steve Luscher 在这个视频的结尾解释了为何他认为 JavaScript 比自定义模板 DSL 更优秀——你不仅需要学习一门自定义的 DSL,而且还要拘泥于这套 DSL 提供的特性的束缚。对于像上面这样较小的普通用例来说,两种方式其实没太多选择余地,但是在编写更大的高度动态化的 UI 组件时,React 的 JavaScript 方式的威力和灵活性就明显更优越了。

小贴士

避免自关闭的XHTML风格的标签

不要用/>来关闭标签,因为它不总是立刻就能关闭标签。当以 HTML5 元素对待时,<foo /> 表示<foo>(然而在 XHTML 中<foo />表示<foo></foo>),换言之,HTML5 会忽略/字符。有关这个话题可以在 Stackoverflow 上查看更多讨论。还可以查看下面两处内容:

绑定标签事件处理器到 this 上

绑定标签事件处理器到 this 上,以确保这些处理器总是和标签文本一起清除(可选的方式是使用约定俗成的var self = this)。比如:

this.clear = function(e) {
  dispatcher.trigger(dispatcher.CLEAR_TODOS);
}.bind(this);

使用 ES6 中的词法作用域绑定的箭头函数也可以获得相同的效果:

this.clear = (e) => {
  dispatcher.trigger(dispatcher.CLEAR_TODOS);
};

关于循环项

使用each={item in items}结构将当前的循环项目传递给自定义的子标签。在下面的例子中,自定义的todo-item标签内的代码可以使用opts.todo来获取当前的 todo 项:

<ul>
 <li each="{todo in opts.store.todos}">
   <todo-item store="{parent.opts.store}" todo="{todo}">
 </li>
</ul>

命名空间事件名称

使用命名空间加冒号的约定来组织应用的事件名称,比如admin:editadmin:deleteadmin:new等。

可以在 CSS 文件中使用自定义标签

自定义标签最终会被渲染生成到 DOM 中,因此它们可以使用在 CSS 选择器和 DOM 审查中使用,这里是一个例子

调试

当用 Webpack 打包时你需要使用开发工具 source-map 配置选项来为你打包后的应用生成 source map 文件。这使得你可以在 ES6 的源码文件中进行调试。

当需要浏览和调试源码时,打开浏览器的 Sources 窗口然后定位到webpack:///.文件夹:

  • 在 Firefox 中:打开 Debugger(Ctrl+Shift+S)。
  • 在 Google Chrome 中:打开 Console(Ctrl+Shift+J)然后点击 Sources 标签来查看源码面板。

我不是很喜欢调试程序和设置断点——大多数情况下我仅仅会有策略地在代码中放置暂时性的console.log()

未来蓝图

Riot(类似 React)是一个 UI 库而不是一个框架。这非常棒(相对于大包大揽的框架来说,我更喜欢小而精的库的集合),但是对于具有一定复杂度的应用来说就需要条理清晰的高级的结构(一个体系结构)来提高可伸缩性、可发展性和可维护性。Flux 为类似React的应用增加了体系结构的选择。我喜欢 Flux,因为它很容易理解并且它能给人一种很直观的感觉(并非出于任何理论上的信仰)。Riot Todo app 使用了名为 RiotControl 的 Flux 风格的 dispatcher(经过轻微修改)来实现 Flux 体系结构。

{% blockquote srackham http://blog.srackham.com/posts/riot-es6-webpack-apps/ Building Apps with Riot, ES6 and Webpack %}
原文出处:
{% endblockquote %}

支付宝扫码打赏 微信打赏

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

扫描二维码,分享此文章

逆葵's Picture
逆葵

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