在Amaple单页应用中,一个页面其实存在两种模块化单位,分别是javascript
am.Module
类),它是以web单页应用跳转更新为最小单位所拆分的独立块;am.Component
类),它的定位是拥有特定功能的封装块,就像由一堆代码封装成的具备特定功能的函数同样,一个组件也有独立的视图、状态数据对象、组件行为以及生命周期。经常使用的组件有Dialog
、Bubble
、Navigator
和Menubar
等。使用am.class
类构造器继承am.Component
类定义一个组件,而继承am.Component
建立的类被称为 组件衍生类 ,你能够这样定义一个组件衍生类:css
// 在am.class函数的参数中指定该组件衍生类的类名,它返回指定名称的组件衍生类 // 类名须遵循首字母大写的驼峰式命名规范,如"BubbleDemo",不然将会报错。但接收变量名没有限制 var BubbleDemo = am.class ( "BubbleDemo" ).extends ( am.Component ) ( { // 在init函数返回该组件的状态数据对象 init : function () { return { bubbleText: "this is a component bubble" }; }, // 组件中必须定义render函数,在该函数中指定组件的template模板和样式 render : function () { this.template ( "<span>{{ bubbleText }}</span>" ) .style ( { span: { background: "red", fontSize: 20, padding: "10px 16px" } // !注意:当元素选择器为符合变量命名规则时可不用引号,如上面选择span元素时。当选择器不符合变量名规则时需使用引号,如: // ".class-name": { fontSize: 15 } // "span #id": { margin-top: 24 } } ); // this.template ( templateHTML )函数中传入html字符串来定义该组件的视图 // this.style ( styleObj )函数为该组件的视图定义样式,这些样式也只做用于组件视图 // 需注意的是该函数传入一个对象,对象属性名为css选择器语法,值为css样式对象,样式名也是使用驼峰式表示,样式值为量值时可直接写为数字 } } );
在一个模块中使用 组件衍生类 渲染组件视图也是很是简单的,首先在am.startRouter
函数中配置组件加载的baseURL
:html
am.startRouter ( { baseURL : { // ... // 为组件文件设置base路径,全部的组件文件请求路径都将基于“/component”目录,不设置时默认“/” component: "/component" }, // ... } );
而后在须要使用的模块或组件中经过import
函数引入,并在<template>
中经过自动以标签名来使用组件java
<template> <!-- 自定义标签名为该组件衍生类的类名以所有小写的中划线式规范转换而来,而不是接收的变量名的转换 --> <bubble-demo></bubble-demo> </template> <script> // 当你将上面的组件衍生类单独编写在src/component文件里时,你须要使用“import ( componentFilePath )”来引入此组件, // 这样在template模板中的<bubble-demo>元素就会解析为组件模板“<span>this is a component bubble</span>”了。 // 引入时可省略“.js”文件后缀 var BubbleDemo = import ( "BubbleDemo" ); // 固然你也能够直接在模块中编写使用一个组件,像这样: // var BubbleDemo = am.class ( "BubbleDemo" ).extends ( am.Component ) ( ... ); new am.Module ( { init : function () { ... } } ); </script>
与模块生命周期阶段数同样,一个组件从建立到卸载也分为5个阶段的生命周期,具体以下:web
init
:组件初始化时触发,它返回组件<template>
模板解析与挂载所使用的状态数据。init
函数内可调用this.propsType
函数进行props
参数验证。props相关知识将在本章节的后面部分介绍
render
:渲染组件视图时触发,该生命周期函数内可分别调用this.template
函数定义视图标签的字符串,和this.style
函数为组件视图添加样式mounted
:解析并挂载状态数据到组件视图后触发,你能够在此函数中处理一些视图解析完成后的操做,如为此组件请求网络数据并更新到模板等updated
:当组件在页面中的位置改变时触发,在组件上使用:for
指令时将会渲染多个组件,此时改变:for
指令所绑定的状态数组时将可能改变组件的位置unmount
:组件卸载时触发,有两种状况将会卸载组件::for
指令渲染多个组件视图后,调用绑定的状态数组的变异函数均可能在页面上卸载一个或多个组件;咱们已经知道组件是一个拥有特定功能的封装块,因此它会有本身特定的 组件行为 ,如Dialog
组件有打开和关闭行为,轮播图组件有翻页行为等。你能够这样定义 组件行为 :正则表达式
var Dialog = am.class ( "Dialog" ).extends ( am.Component ) ( { init : function () { return { open: false, text: "" }; }, render : function () { this.template ( [ '<div :if="open">', '<span>{{ text }}</span>', '</div>' ].join ( "" ) ); }, // 添加action成员函数,该函数返回组件行为的函数集合对象,该对象被称为组件行为对象 // action函数的this指针也是指向该组件对象自己 action : function () { var _this = this; return { // 组件行为函数的this指针不会指向任何值 // 经过state.open来控制Dialog视图的隐藏与显示 open: function ( text ) { _this.state.text = text; _this.state.open = true; }, close: function () { _this.state.open = false; } }; } } );
mounted
、update
和unmount
中可经过this.action
使用组件行为对象;:ref
指令,调用module.refs
函数获取组件引用时将返回该组件的组件行为对象。组件与组件之间配合使用能够发挥更强大的组件能力,在一个组件的<template>
模板中能够嵌套其余组件,你能够这样写:segmentfault
// ComponentB组件依赖ComponentA组件 // ComponentA组件的编写与普通组件编写相同,这里省略 var CompoenntB = am.class ( "CompoenntB" ).extends ( am.Component ) ( { // 在构造函数中经过this.depComponents来指定该组件的依赖组件数组 constructor : function () { // 和ES6的class关键字定义类同样,在构造函数中需首先调用super()函数,不然将会抛出错误 this.__super (); this.depComponents = [ ComponentA ]; }, init : function () { ... }, render : function () { this.template ( "<component-a></component-a>" ); } } );
当ComponentA
和ComponentB
组件都编写在单独的文件中时,你须要在模块中同时引入 组件 及 嵌套组件 ,像这样:数组
<template>...</template> <script> // 在ComponentB组件中只需经过this.depComponents = [ ComponentA ]指定它所依赖的组件便可,而后在使用的模块中统一引入这些组件文件 // 由于ComponentB组件依赖ComponentA组件,因此需在ComponentB以前引入ComponentA // 此时ComponentA组件就能够被ComponentB所获取到 var ComponentA = import ( "component/ComponentA" ); var ComponentB = import ( "component/ComponentB" ); new am.Module ( ... ); </script>
组件做为一个单独的封装块,它必须与其余组件或模块进行通讯,你能够在模块中分发数据到不一样组件,也能够在组件中分发数据到嵌套组件中。在组件中可使用props
进行数据的通讯,使用subElements
进行html模板块分发。网络
props
传递静态值<template> <!-- 在组件元素上定义任何非指令属性(属性名不为“:”开头),它都会被当作props属性传入组件中 --> <dialog text="this is a external message!"></dialog> </template> <script> am.class ( "Dialog" ).extends ( am.Component ) ( { init : function () { // 在组件中使用this.props接收外部传入的数据 // this.props.text的值为"this is a external message!",即外部传入的字符串 return { text: this.props.text }; }, // ... } ); new am.Module ( ... ); </script>
props
传递动态值props
还支持使用插值表达式的方式传递状态数据,这被称为 动态props
。动态props
将建立一个对外部状态数据的代理属性,当在组件内更改了此代理属性时,外部对应的状态数据也将同步更新。以下:dom
Dialog
组件的视图中,将状态属性text
传入组件后,组件的this.props.text
即为该状态属性的代理属性。<template> <dialog text="{{ text }}"></dialog> </template> <script> new am.Module ( { init : function () { text: "this is a external message!" }, // ... } ); </script>
Dialog
组件的代码中,可经过this.props.text
获取外部传递的text
状态属性。am.class ( "Dialog" ).extends ( am.Component ) ( { init : function () { return { // 使用text1接收并使用this.props.text的值 text1: this.props.text, // 若是你但愿更新外部的text属性后,组件视图中挂载了this.props.text数据的地方也同步更新, // 你能够在组件中建立一个计算属性做为this.props.text的代理,以下建立的text2计算属性: computed: { var _this = this; text2: { get: function () { return _this.props.text; }, set: function ( newVal ) { _this.props.text = newVal; } } // 由于组件内对this.props.text的值更新后,外部的text状态属性也会同步更新,反之也成立 // 这样在组件视图中挂载text2就等于挂载props.text // 此时需注意的是,更改text2的值也将同步更改外部text属性的值 } }; }, // ... } );
当你但愿开放你所编写的组件给其余开发者使用时,你不肯定其余开发者传入的props
参数是否符合组件内的处理要求,此时你能够为你的组件设置props
数据验证,你能够在组件的init
函数内调用this.propsType
函数进行验证:
am.class ( "Dialog" ).extends ( am.Component ) ( { init : function () { // 每项值的验证均可以设置validate、require和default属性 this.propsType ( { text: { validate: String, // 表示当传入text值时它必须为字符串 require: true, // 表示text参数为必须传入的参数,默认为false default: "Have no message" // 表示不传入text参数时的默认值,默认值不会参与props验证,不指定default时无默认值 // validate的值能够有四种类型的参数,分别为: // ①. 基础数据构造函数,分别有String、Number、Boolean三种基本数据类型构造函数,Function、Object、Array三种引用类型构造函数, // 以及undefined和null,它表示容许传入的数据类型 // ②. 正则表达式,如/^1\d{10}$/表示只容许传入一个手机号码 // ③. 函数,它接收此props参数值,必须返回true或false表示是否经过验证,如: // function ( text ) { return text.length > 5 } // ④. 数组,数组内是以上三种值的组合,经过数组内任意一项验证均可以经过,至关于“||”运算符 } // 当text属性验证只要设置validate属性时,可直接以下缩写: // text: String } ); return { text: this.props.text }; }, // ... } );
subElements
分发html片断若是你想开发一个更加通用的Dialog
组件,你应该不但愿Dialog
的视图布局是固定不变的,而是能够根据不一样的需求自定义Dialog
视图,由于这样才显得更加灵活多变,组件的subElements
就是用来解决这个问题的,它可使你从组件外部传入html片断与组件视图混合:
<dialog> <!-- <span>的内容会被做为html片断传入组件内 --> <span>this is external HTML template</span> </dialog>
而后在组件内经过subElements属性获取外部传递的视图,并插入到组件视图中的任意位置。subElement接收的视图可分为 默认subElements 、 subElements的单数分块 和 subElements的不定数分块 三种形式。
在组件元素中传入html片断时,组件内将会建立一个默认的subElements
局部变量,你能够在组件内的模板中经过{{ subElements.default }}
插入此html片断:
am.class ( "Dialog" ).extends ( am.Component ) ( { init : function () { ... }, render : function () { // {{ subElements.default }}将会输出外部传入的“<span>this is external HTML template</span>” this.template ( "<div>{{ subElements.default }}</div>" ); } // ... } );
Dialog
组件将会被渲染成:
<div> <span>this is external HTML template</span> </div>
【注意】分发的html片断也可使用模板指令与组件,此html片断解析时挂载的数据来源是组件外部的状态数据,以下:
<template> <dialog> <!-- {{ text }}的数据来源是此模块的状态 --> <!-- 它就像JavaScript中传入某个函数内的回调函数,该回调函数可对外部数据访问而不是函数内 --> <span>{{ text }}</span> </dialog> </template> <script> new am.Module ( { init : function () { return { text: "this is external HTML template" }; }, // ... } ); </script>
若是你但愿开发的Dialog
分为头部和内容两部分视图,再混合到组件模板的不一样位置,subElements
也容许你这样编写html片断:
<dialog> <header> <span>this is a title</span> </header> <content> <span>this is external HTML template</span> </content> </dialog>
<header>
和<content>
将分发的代码块分为了两部分,你能够在组件视图中分别将它们插入到不一样的位置,只需在组件中分别定义Header
、Content
两个 组件子元素 :
am.class ( "Dialog" ).extends ( am.Component ) ( { init : function () { ... }, render : function () { // 指定分块的组件子元素名 // 组件子元素名也需遵循首字母大写的驼峰式规则,在组件元素内使用时也是所有小写的中划线式规范 this.subElements ( "Header", "Content" ) .template ( [ "<div>", "<div>{{ subElements.Header }}</div>", "<div>{{ subElements.Content }}</div>", "</div>" ].join ( "" ) ); // <header>、<content>两个子元素只能做为<dialog>子元素使用 // 组件模板中分别经过subElements.Header和subElements.Content输出对应的html分块片断 } // ... } );
此时Dialog
组件将会被渲染成:
<div> <div><span>this is a title</span></div> <div><span>this is external HTML template</span></div> </div>
【注意】①. 如没有在this.subElements
函数中定义相应的组件子元素时,Amaple只会将它们做为普通dom元素对待。
②. 除<header>
、<content>
外的其余html片断会自动包含在subElements.default
中。
subElements
的分块分发可能会让你想到不少原生的元素,如<ul>
和<li>
、<select>
和<option>
、<table>
和<tr><td>
等,他们都属于包含与被包含的关系,但你会发现其实<ul>
中能够定义一个或多个,在subElements
中你也能够定义一个 组件子元素 的不定数分块,如Grid
组件(网格)可包含不定个数的GridItem
:
<grid> <grid-item>a</grid-item> <grid-item>b</grid-item> <grid-item>c</grid-item> </grid>
在组件中这样定义不定数分块的subElements
:
am.class ( "Grid" ).extends ( am.Component ) ( { init : function () { ... }, render : function () { this.subElements ( { elem: "GridItem", multiple: true } ) .template ( [ "<ul>", "<li :for='item in subElements.GridItem'>{{ item }}</li>", "</ul>" ].join ( "" ) ) .style ( ... ); // 此时局部变量subElements.GridItem为一个包含全部GridItem分块片断的数组,在组件内使用:for指令循环输出, // 也可使用数组索引如subElements.GridItem [ 0 ] // 其实上面定义单数分块的Header的全写是{ elem: "Header", multiple: false },但它可缩写为"Header" } // ... } );
继续学习下一节:【AmapleJS教程】5. 插件
也可回顾上一节:【AmapleJS教程】3. 模板指令与状态数据(state)