Riot.js 的自定义标签可以用来构造用户界面(UI)。他们是“View”部分,下面是几个例子:
<todo> <h3>{ opts.title }</h3> <ul> <li each={ items }> <label class={ completed: done }> <input type="checkbox" checked={ done } onclick={ parent.toggle }> { title } </label> </li> </ul> <form onsubmit={ add }> <input name="input" onkeyup={ edit }> <button disabled={ !text }>Add #{ items.length + 1 }</button> </form> <script> this.disabled = true this.items = opts.items edit(e) { this.text = e.target.value } add(e) { if (this.text) { this.items.push({ title: this.text }) this.text = this.input.value = '' } } toggle(e) { var item = e.item item.done = !item.done return true } </script></todo>
这些自定义标签会被编译成JS。可以在Riot.js 官网看到关于编译的说明。
一个Riot.js 的自定义标签是HTML(布局)和JS(逻辑)的组合。
首先定义HTML,JS跟在后面,<script>标签包裹JS代码是可选的(注意,在外部的.tag里是不能用的)
如果不写<script>,那JS只能跟在最后一个HTML标签结束后(以后的版本可能会完全支持混写)
自定义标签可以是空的,也可以只用HTML或者只有JS
<foo bar={ baz }>或者 <foo bar="{ baz }">都可以,引号是可选的
支持ES6的方法语法,比如 methodName() 会变成 this.methodName = function(),这个变量总是指向当前自定义标签的实例
类名的简写语法可用了:如果那个done是true的话, class={ completed: done } 会被渲染成 class="completed"
当表达式值是false时,逻辑属性(checked, selected 什么的)都将被忽略 <input checked={ undefined }> 会变成<input>
所有的属性名称必须小写,这是W3C规范
支持自结束标签:<div/> 等于 <div></div>,众所周知的"打开标签"如 <br>,<hr>,<img>或 <input>在编译之后永远不会关闭。
自定义标签必须被关闭,不管是自结束标签还是普通标签
标准的HTML标签也可以被自定义,不过不建议这么用
标签的定义要放在.tag文件中每一行的开头,不能有缩进
<!-- works --> <my-tag></my-tag> <!-- also works --> <my-tag> </my-tag> <!-- this fails, because of indentation --> <my-tag> </my-tag>
放在HTML文档里的tag定义(即内联JS里)必须正确的缩进,即所有的自定义标签保持一致的缩进等级,可以混用tab和空格
你可以不要<script>标签的
<todo> <!-- layout --> <h3>{ opts.title }</h3> // logic comes here this.items = [1, 2, 3] </todo>
目前的版本中,这种不要<script>的形式只能是这样:JS只能跟在最后一个HTML标签结束后(不包括那个todo)。接下来的例子中将全部采用这种语法。
你可以指定一个预处理器,即在<script>上加一个type属性,目前支持的有“coffee”“typescript”“es6”或者不写,当然你也可以在前面加个text/,比如text/coffee
你可以在自定义标签里加个<style>标签,Riot会自动把它放到一个外部文件并在head中引入
<todo> <!-- layout --> <h3>{ opts.title }</h3> <style> todo { display: block } todo h3 { font-size: 120% } /** other tag specific styles **/ </style> </todo>
Scoped CSS可用啦,Scoped CSS
<todo> <!-- layout --> <h3>{ opts.title }</h3> <style scoped> :scope { display: block } h3 { font-size: 120% } /** other tag specific styles **/ </style> </todo>
这只会发生一次,不管标签被初始化多少次
为了更方便的重载CSS,你可以在<head>中指定Riot应注入的标签的样式
<style type="riot"></style>
demo用了个在normalize.css之后但在你样式之前的组件库中插入标签的样式,以便允许你覆盖库之前的样式
(笔者注:意为为装载、编译)
一旦定义了个自定义标签你就可以在页面上mount它
<body> <!-- place the custom tag anywhere inside the body --> <todo></todo> <!-- include riot.js --> <script src="riot.min.js"></script> <!-- include the tag --> <script src="todo.js" type="riot/tag"></script> <!-- mount the tag --> <script>riot.mount('todo')</script> </body>
在页面里的(笔者注,即内联的)自定义标签需要需要被正常关闭,但暂不支持自关闭的标签<todo/>
这是mount的集中用法:
// mount all custom tags on the page riot.mount('*') // mount an element with a specific id riot.mount('#my-element') // mount selected elements riot.mount('todo, forum, comments')
一个页面中可以包含同种标签的多个实例
在第二个参数里面你可以传递options
<script> riot.mount('todo', { title: 'My TODO app', items: [ ... ] }) </script>
不管是一个简单的对象还是某网站API,或者作为Flux架构的存储,这取决与你设计的体系结构
在标签里面,options能和ops变量一起被引用:
<my-tag> <!-- Options in HTML --> <h3>{ opts.title }</h3> // Options in JavaScript var title = opts.title </my-tag>
Mixins提供了一种很简单的方案用来在标签间共享函数。但一个标签被Riot编译完成后,所有定义的Mixins会被添加到那个标签中:
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>
在这个demo中,你给了<my-tag>所有实例以OptsMixin提供的getOpts 和setOpts 方法。init 方法是特殊的能在当他加载标签时初始化Mixin的方法。init方法在它混合进的方法中是不可访问的。
var my_tag_instance = riot.mount('my-tag') [0]console.log(my_tag_instance.getOpts()) //will log out any opts that the tag has
标签将接受任何对象,{'key': 'val'} var mix = new function(...)——当传递给他其他类型就会报错
现在除了init方法之外,<my-tag>已经有了一个getId 方法以及其他被OpsMixins一起定义进去的东西
function IdMixin() { this.getId = function() { return this._id } } var id_mixin_instance = new IdMixin() <my-tag> <h1>{ opts.title }</h1> this.mixin(OptsMixin, id_mixin_instance) </my-tag>
通过在标签级别上被定义,Mixin不仅能扩展你自定义标签的功能,还允许用在一个可重复的界面里。每次mount一个标签,甚至是指标签,每个实例将拥有混进去的代码
如果要在不同文件和项目中共享Mixin,Riot提供了个叫 riot.mixin 的API。你可以用下面这种方式在全局范围内注册你的Mixin:
riot.mixin('mixinName', mixinObject)
如果要把Mixin加载到标签里去,有个叫 mixin() 的方法:
<my-tag> <h1>{ opts.title }</h1> this.mixin('mixinName') </my-tag>
每个标签都是按下面的顺序创建的:
标签被构造
执行标签的JS逻辑
计算HTML的表达式再触发 ”update“ 事件
标签被mount到页面上,然后触发mount事件
标签被mount后按照这些步骤更新:
自动调用事件处理器(除非你在事件处理器里单独设置 e.preventUpdate = true)比如在前文中说到的toggle
在当前标签的实例上调用 this.update()
在父级和父级以上的标签实例上调用 this.update(),再更新从父到子的单向数据流
当 riot.update() 被调用时,就会在全局范围内更新所有的表达式
当标签更新时,这个”更新“事件也会被触发
所有的变量都会在mount除了 <img src={ src }>这种会失败的调用之前被计算
你可以像下面这样监听各种生命周期事件:
<todo> this.on('mount', function() { // right after tag is mounted on the page }) this.on('update', function() { // allows recalculation of context data before the update }) this.on('unmount', function() { // when the tag is removed from the page }) // curious about all events ? this.on('mount update unmount', function(eventName) { console.info(eventName) }) </todo>
对同一个事件你有很多个监听器可以用,后面会详细讲解
HTML中可以混入用花括号包裹起来的表达式:
{ /* my_expression goes here */ }
表达式可被设置属性或嵌套文本节点:
<h3 id={ /* attribute_expression */ }> { /* nested_expression */ } </h3>
所谓表达式就是JS,这是最常见的用法:
{ title || 'Untitled' } { results ? 'ready' : 'loading' } { new Date() } { message.length > 140 && 'Message is too long' } { Math.round(rating) }
Riot的目标是让表达式尽可能迷你,也希望你的HTML更干净,如果以后这些表达式越来越复杂庞大,你可以试试把一部分逻辑代码放到”更新“事件上:
<my-tag> <!-- the `val` is calculated below .. --> <p>{ val }</p> // ..on every update this.on('update', function() { this.val = some / complex * expression ^ here }) </my-tag>
像checked、selected这类逻辑属性的值是false时会被忽略的
W3C 说过如果存在该属性那么就该是true— 就算变量为false的空
下面这个就是无效的:
<input type="checkbox" { true ? 'checked' : ''}>
因为只有属性和嵌套的文本表达式是有效的。Riot能检测44中不同的逻辑属性
对于写CSS的Class,Riot提供了种简洁的语法:
<p class={ foo: true, bar: 0, baz: new Date(), zorro: 'a value' }></p>
这段{}的计算结果为 "foo baz zorro" ,值是true的属性名会被追加到class里。当然如果你有其他需要用的地方,比如id和你自定义的属性什么的,也是可以直接用的
你可以通过转义符号输入无需计算的表达式:
\{ this is not evaluated \} //输出如下,但里面未被计算过 { this is not evaluated }
你可以自由地把括号改成你要的:
riot.settings.brackets = '${ }' riot.settings.brackets = '{{ }}'
开头结尾之间要加个空格
你要用预编译器的话必须要设计括号选项
注意,在 <style> 标签里的表达式会被无视的
Riot表达式可以只渲染文本值而无视HTML格式。不过呢,你可以自定义标签来完成这项工作
<raw> <span></span> this.root.innerHTML = opts.content </raw>
这步之后就可以在其他标签里用了
<my-tag> <p>Here is some raw content: <raw content="{ html }"/> </p> this.html = 'Hello, <strong>world!</strong>' </my-tag>
这里有个demo:demo
不过这可能会被贱人用作XSS攻击,所以请确保你不会从不受信任的来源加载数据