avalon2 が最も誇りに思っていることは、Web コンポーネントに似た強力なコンポーネント システムをついに備えたことです。このコンポーネント システムは React の JSX に相当し、サブコンポーネントのパラメータ受け渡しをより適切に制御できます。
Avalon は誕生以来、コンポーネントをエレガントに定義して使用する方法を模索してきました。 avalon1.4のms-widgetからavalon1.5のカスタムラベルまで。現在のバージョンはそれらを組み合わせたもので、スロット挿入ポイントのメカニズムとライフサイクル管理を Web コンポーネントから借用し、レンダリング文字列テンプレートを React からコピーします。
avalon1.5 では、キャリアとしてのカスタム ラベルに変更され、設定object 属性を使用します。widgetOption として、$id または identifier 属性を使用してコンポーネント VM の $id を指定し、タグ名を使用してコンポーネントの種類を指定します。
今振り返ると、重要な設定項目はコンポーネント ID とコンポーネント タイプの 2 つだけです。他の構成項目は、より洗練された方法で追加する必要があります。幸いなことに、新しいコンポーネントの説明を書き始める前に問題を解決できました。戻って見てください、ms-attr、ms-css 命令の属性値がオブジェクトまたはオブジェクト配列の形式で存在すれば、多くのものを格納できるのではないでしょうか。
avalon1.4 | avalon1.5 | avalon2 | Webコンポーネント | xtag | react |
$init | $init | onInit | createdCallback | created | componentWillMount |
| $childReady | onReady | attachedCallback | inserted | componentDidMount |
| $ready | onViewChange | attributeChangedCallback | attributeChanged | componentWillReceiveProps |
$remove | $dispose | onDispose | detachedCallback | removed | componentWillUpdate |
| | | | | componentDidUpdate |
| | | | | componentWillUnmount |
从上表可以看到,avalon2与Web Component的生命周期很相近了。
-
onInit,这是组件的vm创建完毕就立即调用时,这时它对应的元素节点或虚拟DOM都不存在。只有当这个组件里面不存在子组件或子组件的构造器都加载回来,那么它才开始创建其虚拟DOM。否则原位置上被一个 注释节点 占着。
-
onReady,当其虚拟DOM构建完毕,它就生成其真实DOM,并用它插入到DOM树,替换掉那个注释节点。相当于其他框架的attachedCallback, inserted, componentDidMount.
-
onViewChange,当这个组件或其子孙节点的某些属性值或文本内容发生变化,就会触发它。它是比Web Component的attributeChangedCallback更加给力。
-
onDispose,当这个组件的元素被移出DOM树,就会执行此回调,它会移除相应的事件,数据与vmodel。
我们再来看一下如何定义组件。上面只是说如何添加配置项。onInit, onReady, onViewChagne, onDispose只是其中的四个配置项。
avalon2 的默认配置项比avalon1.5 少许多。
-
is, 字符串, 指定组件的类型。如果你使用了自定义标签,这个还可以省去。
-
$id, 字符串, 指定组件vm的$id,这是可选项。
-
define, 函数, 自己决定如何创建vm,这是可选项。
-
diff, 函数, 比较组件的前后两个虚拟DOM树,返回true同步到真实DOM中,可选。
-
onInit, onReady , onViewChange , onDispose 四大生命周期钩子。
然后就没有了, 没有$replace, $slot, $template, $extend, $container, $construct, $$template 这些怪怪的东西。
说起自定义标签。之前1.5为了兼容IE6-8,是使用旧式的带命名空间的标签作为容器,而Web Component则是使用中间带杠的标签,如,风格大相径庭。显然后者是主流,是未来!
经过一番研究,发掘出三大标签作为组件定义时的容器。
xmp, wbr, template
xmp是闭合标签,与div一样,需要写开标签与闭标签。但它里面的内容全部作为文本存在,因此在它里面写带杠的自定义标签完全没问题。并且有一个好处时,它是能减少真实DOM的生成(内部就只有一个文本节点)。
<xmp ms-widget="@config"><ms-button ms-widget="@btn1"><ms-button><div></div><ms-tab ms-widget="@tab"><ms-tab></xmp>
ログイン後にコピー
wbr与xmp一样,是一个很古老的标签。它是一个空标签,或者说是半闭合标签,像br, area, hr, map, col都是空标签。我们知道,自定义标签都是闭合标签,后面部分根本不没有携带更多有用的信息,因此对我们来说,没多大用处。
<wbr ms-widget="@config" />
ログイン後にコピー
template是HTML5添加的标签,它在IE9-11中不认,但也能正确解析得出来。它与xmp, wbr都有一个共同特点,能节省我们定义组件时页面上的节点规模。xmp只有一个文本节点作为孩子,wbr没有孩子,template也没有孩子,并且用content属性将内容转换为文档碎片藏起来。
<template ms-widget="@config" ><ms-dialog ms-widget="@config"></ms-dialog></template>
ログイン後にコピー
当然如果你不打算兼容IE6-8,可以直接上ms-button这样标签。自定义标签比起上面三大容器标签,只是让你少写了is配置项而已,但多写了一个无用的闭标签。
<ms-dialog ms-widget="@config" ><ms-panel ms-widget="@config2"></ms-panel></ms-dialog><!--比对下面的写法--><xmp ms-widget="@config" ><wbr ms-widget="@config2"/></xmp>
ログイン後にコピー
如果你想在页面上使用ms-button组件,只能用于以下四种方式
<!--在自定义标签中,ms-widget不是必须的--><ms-button></ms-button><!--下面三种方式,ms-widget才是存在,其中的is也是必须的--><xmp ms-widget='{is:"ms-button"}'></xmp><wbr ms-widget='{is:"ms-button"}'/><template ms-widget='{is:"ms-button"}'></template>
ログイン後にコピー
在JS中,我们是这样使用它
<!DOCTYPE html><html> <head> <title>ms-validate</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <script src="../dist/avalon.js"></script> <script> var vm = avalon.define({ $id: 'test', button: {//注意这里不能以 $开头 buttonText: "VM内容" } }) avalon.component('ms-button', { template: '<button type="button"><span><slot name="buttonText"></slot></span></button>', defaults: { buttonText: "默认内容" }, soleSlot: 'buttonText' }) </script> </head> <body ms-controller="test"> <!--在自定义标签中,ms-widget不是必须的--> <ms-button ></ms-button> <!--下面三种方式,ms-widget才是存在,其中的is也是必须的--> <xmp ms-widget='{is:"ms-button"}'></xmp> <wbr ms-widget='{is:"ms-button"}'/> <template ms-widget='{is:"ms-button"}'></template></body></html>
ログイン後にコピー
但这样我们就不好控制组件的更新。我们改一改。
<!DOCTYPE html><html> <head> <title>ms-validate</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <script src="../dist/avalon.js"></script> <script> var vm = avalon.define({ $id: 'test', button: {//注意这里不能以 $开头 buttonText: "按钮内容" } }) avalon.component('ms-button', { template: '<button type="button"><span><slot name="buttonText"></slot></span></button>', defaults: { buttonText: "button" }, soleSlot: 'buttonText' }) </script> </head> <body ms-controller="test"> <!--在自定义标签中,ms-widget不是必须的--> <ms-button ms-widget="@button"></ms-button> <!--下面三种方式,ms-widget才是存在,其中的is也是必须的--> <xmp ms-widget='[{is:"ms-button"},@button]'></xmp> <wbr ms-widget='[{is:"ms-button"},@button]'/> <template ms-widget='[{is:"ms-button"},@button]'></template></body></html>
ログイン後にコピー
这样我们直接操作 vm中的button对象中对应属性就能更新组件了。这比原来avalon1.*好用一万倍。
此外,avalon2还支持Web Components规范中所说的slot插入点机制,它是用来配置
一些字符串长度很长的属性。比如说ms-tabs组件,通常有一个数组属性,
而数组的每个元素都是一个很长的文本,用于以应一个面板。这时我们可以在自定义标签的
innerHTML内,添加一些slot元素,并且指定其name就行了。
当我们不使用slot,又不愿意写面板内部放进vm时,你的页面会是这样的:
<ms-tabs ms-widget='{panels:["第一个面板的内部dfsdfsdfsdfdsfdsf","第二个面板的内部dfsdfsdfsdfdsfdsf""第三个面板的内部dfsdfsdfsdfdsfdsf"] }'></ms-tabs>
ログイン後にコピー
使用了slot后
<ms-tabs><div slot='panels'>第一个面板的内部dfsdfsdfsdfdsfdsf</div><div slot='panels'>第二个面板的内部dfsdfsdfsdfdsfdsf</div><div slot='panels'>第三个面板的内部dfsdfsdfsdfdsfdsf</div></ms-tabs>
ログイン後にコピー
而你的组件是这样定义
<ms-tabs><slot name='panels'></solt><slot name='panels'></solt><slot name='panels'></solt></ms-tabs>
ログイン後にコピー
上面的div会依次替代slot元素。
此外,如果我们只有一个插槽,不想在页面上slot属性,那么可以在组件里使用soleSlot。
注意avalon.component的第二个参数,是一个对象,它里面有三个配置项,template是必须的, defaults、 soleSlot是可选的。
组件属性的寻找顺序,会优先找配置对象,然后是innerHTML,然后是defaults中的默认值.我们可以看一下测试
div.innerHTML = heredoc(function () { /* <div ms-controller='widget0' > <xmp ms-widget="{is:'ms-button'}">{{@btn}}</xmp> <ms-button>这是标签里面的TEXT</ms-button> <ms-button ms-widget='{buttonText:"这是属性中的TEXT"}'></ms-button> <ms-button></ms-button> </div> */ }) vm = avalon.define({ $id: 'widget0', btn: '这是VM中的TEXT' }) avalon.scan(div) setTimeout(function () { var span = div.getElementsByTagName('span') expect(span[0].innerHTML).to.equal('这是VM中的TEXT') expect(span[1].innerHTML).to.equal('这是标签里面的TEXT') expect(span[2].innerHTML).to.equal('这是属性中的TEXT') expect(span[3].innerHTML).to.equal('button') vm.btn = '改动' setTimeout(function () { expect(span[0].innerHTML).to.equal('改动') done() }) })
ログイン後にコピー
生命周期回调的例子.avalon是使用多种策略来监听元素是否移除,确保onDispose回调会触发!
<!DOCTYPE html><html> <head> <title>TODO supply a title</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="./dist/avalon.js"></script> <script> var vm = avalon.define({ $id: 'widget0', config: { buttonText: '按钮', onInit: function (a) { console.log("onInit!!") }, onReady: function (a) { console.log("onReady!!") }, onViewChange: function () { console.log("onViewChange!!") }, onDispose: function () { console.log("onDispose!!") } } }) setTimeout(function () { vm.config.buttonText = 'change' setTimeout(function () { document.body.innerHTML = "" }, 1000) }, 1000) </script> </head> <body> <div ms-controller='widget0' > <div><wbr ms-widget="[{is:'ms-button'},@config]"/></div> </div> </body></html>
ログイン後にコピー
在 avalon仓库 中有两个简单的例子,大家可以下回来研究研究。
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
-
2024-10-22 09:46:29
-
2024-10-13 13:53:41
-
2024-10-12 12:15:51
-
2024-10-11 22:47:31
-
2024-10-11 19:36:51
-
2024-10-11 15:50:41
-
2024-10-11 15:07:41
-
2024-10-11 14:21:21
-
2024-10-11 12:59:11
-
2024-10-11 12:17:31