在读完 Muut 上 Frameworkless JavaScript 这篇博文后,我遇上了 Riot,请一定先阅读该博文!Muut 的程序员拿出实际行动编写了 Riot,一个 类似React 的用来构建响应式UI组件的微型库。
阅读Riot的文档时,令我感触最深的是 Riot 竟如此容易理解——相比 React 来说,在 Riot 里需要学习的术语和概念极少(说实话,和 Polymer 和 Angular 等比起来,Riot 也是十分简单易懂的)。
为了有助于学习 Riot,我把自己用 React 编写的 flux-backbone-todo 搬运到了用 Riot 编写的 Riot Todo app 上。这篇博文就是我记录这次重构经历的笔记的合集。
如果觉得文章太长,这里是精简版:
如果你对 Riot 还不熟悉,可以先去浏览一下 Riot官网——开发文档是第一课。我将列出一些我所学到的但是阅读文档时不一定很明显易懂的内容,而不会去讨论 Riot 是如何工作的。
示例的应用采用 ES6 编写,我使用 6to5 转译器将其转换为 ES5 代码,使用 Webpack 将编译后的代码以及需要的库一起打包。这种方式使得联结 JavaScript 模块成为必要——当你理解最新的 ES6 中 import
和 export
表述的优势时(看这个示例),你就会知道使用 ES6 编写代码是非常棒的。
Webpack通过配置可以使用 6to5 loader
将 ES6 源码自动转换成 CommonJS 格式的 ES5 模块,再将其打包至一个单独的 bundle.js
文件。
.tag
文件Riot 标签文件是指包含 HTML 标记以及 JavaScript UI 逻辑的 HTML 模板。如果你已经浏览过了上面提到的 Todo应用,你可能会疑惑标签文件在哪里——答案是我已经不再使用它们,并且更喜欢用 JavaScript 来替代之。去除 .tag
文件简化了我的编码、加工和工作流程。对我来说,标签文件的复杂性和局限性大于它任何可以感知到的优点。
这并不是对 Riot 的一种批判。对标签文件来说,灵活的地方在于它完全可选而非强制使用,在此记录我不使用它的原因。
当你审视编译后的 JavaScript 代码时,你会看到 Riot 标签文件其实是一层轻微的语法糖.
this
时,脱离上下文意味着代码不是合法的 JavaScript 并且在编辑器/IDE 中会报错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 工具的支持。
最重要的区别在于 UI 标记模板是如何声明的:
这种反转的结果是 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 方式的威力和灵活性就明显更优越了。
不要用/>
来关闭标签,因为它不总是立刻就能关闭标签。当以 HTML5 元素对待时,<foo />
表示<foo>
(然而在 XHTML 中<foo />
表示<foo></foo>
),换言之,HTML5 会忽略/
字符。有关这个话题可以在 Stackoverflow 上查看更多讨论。还可以查看下面两处内容:
绑定标签事件处理器到 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:edit
,admin:delete
,admin:new
等。
自定义标签最终会被渲染生成到 DOM 中,因此它们可以使用在 CSS 选择器和 DOM 审查中使用,这里是一个例子。
当用 Webpack 打包时你需要使用开发工具 source-map 配置选项来为你打包后的应用生成 source map 文件。这使得你可以在 ES6 的源码文件中进行调试。
当需要浏览和调试源码时,打开浏览器的 Sources 窗口然后定位到webpack:///.
文件夹:
我不是很喜欢调试程序和设置断点——大多数情况下我仅仅会有策略地在代码中放置暂时性的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 %}