组件生命周期
Ext 中的所有组件都扩展至 Ext.Component
(另一个名字也叫 Ext.AbstractComponent
)。
当创建组件时,都会经历一个叫 “组件生命周期” 的过程。基本上,生命周期分 3 个阶段:初始化过程,呈现过程,和销毁过程。
初始化阶段初始化我们的新实例,并将它登记到组件管理器中;然后,呈现阶段将在 DOM 上创建全部所需的结点;最后销毁阶段将从 DOM 上删除结点和侦听器 (listener)。
Ext.AbstractComponent/Ext.Component
类引导了这个生命周期过程,每个扩展至该类的组件都将自动参与这个生命周期。
初始化阶段
这个阶段的主要目的是基于我们的配置信息创建一个组件实例。它还将我们的组件登记到组件管理器中,以及进行其它一些操作。
下面是初始化阶段每步的详细描述:
- 第 1 步是将配置信息应用到我们要创建的类实例上。
- 第 2 步是定义通用事件,如每个组件的 enable, disable, show 等。
- 第 3 步是为该实例赋于一个 ID。如果在配置信息中有指定 ID 就用指定的,不然会自动生成一个。由于组件 ID 会用到 DOM 元素上,故要确保唯一性,因此最好不要指定,应自动生成。
- 第 4 步检验是否在配置信息中定义了插件,有的话创建这些插件的实例。
- 第 5 步运行
initComponent
方法。像 initComponent 等 Component 类中的模板函数都可以在子类中重载。 - 第 6 步将实例登记到
Ext.ComponentManager
对象中。我们可以通过var panel=Ext.get("panelId")
或Ext.ComponentQuery.query('xtypeValue')
来引用组件。 Component
类有 2 个 mixin,一个用于事件管理,另一个用于状态管理。第 7 步通过调用这两个 mixin 的构造函数对它们进行初始化。- 如果定义有插件,那么插件在第 4 步就已经初始化了,现在将本实例作为参数,调用每个插件上的
init(component)
方法,进一步初始化。
如果在配置信息中指定了 renderTo
属性,那么接下来就开始呈现阶段。如果没有指定,也可以手动开启,如 panel.render(Ext.getBody())
。
呈现阶段
呈现阶段只当组件还未呈现时才会发生。在该阶段中,会创建全部所需的结点并插入到 DOM 中,并应用 CSS 样式,创建事件侦听器。之后我们就能看到并与它进行交互。
下面是呈现阶段每步的详细描述:
- 第 1 步触发
beforeRender
事件。如果某个侦听器返回 false,则停止该呈现阶段。 - 第 2 步执行
beforeRender
方法。并检查该组件是否是浮动组件(如 menu, window 等),并设置正确的 CSSz-index
属性值。 - 第 3 步通过设置
container
属性来定义本组件的容器。container
属性值指向一个 DOM 元素,即 Ext.dom.Element 实例。本组件将呈现到该元素中。 - 第 4 步运行
onRender
方法。这会创建el
属性,它包含本组件的主要结点元素。我们也可以为组件定义模板,如果这样的话,在本步中会基于模板创建结点并添加到组件的主要结点元素中。也可以重载onRender
,从而能添加定制的 DOM 结点。 - 第 5 步是设置可见模式。隐藏组件元素共有 3 种模式:display, visibility, offset。
- 第 6 步中,如果设置有
overCls
属性,那么会为 mouseover 和 mouseout 事件创建相应的侦听器。 - 第 7 步运行
render
方法。并触发以本组件实例为参数的render
事件。 - 第 8 步是初始化内容。共有 3 种方法来设置组件内容:
- 通过
html
属性定义。 - 定义
contentEl
属性,其值是一个现有 DOM 元素的 ID。该元素将作为组件的内容。 - 定义
tpl
属性作为模板,定义data
属性作为模板变量值对象,通过模板生成内容。
- 通过
- 第 9 步运行
afterRender
方法。如果包含有子组件,子组件也在本步中呈现。 - 第 10 步触发
afterRender
事件。通过侦听该事件可以执行一些需要所有 DOM 结点都呈现后的动作。 - 第 11 步初始化组件结点上的事件。
- 第 12 步中,如果配置信息中设置了
hidden:true
,则隐藏组件结点;如果设置了disabled:true
,则运行组件的disable()
方法,这将在组件结点上添加一些 CSS 类使该结点显示为禁用状态并设置其 disabled 值为 true。
销毁阶段
销毁阶段的主要目的是消除 DOM,删除事件侦听器,并通过删除对象和数据来清理内存。当不再需要组件时,及时进行销毁非常重要。当窗口的属性 closeAction
设置为 destroy
(默认即为该值)时,用户关闭窗口时即会触发销毁过程。
销毁过程也可以通过调用组件的方法来触发,如 cmp.destroy()
。
下面是销毁阶段每步的详细描述:
- 第 1 步触发
beforeDestroy
事件。如果某个侦听器返回 false,则会停止销毁过程,否则,继续进行销毁,如果组件是浮动的,则将其从浮动管理器中删除。 - 第 2 步执行
beforeDestroy
方法。子类通过重载该方法来删除子组件或清理内存。 - 第 3 步中,如果本组件是某组件的子组件,则删除父组件对其的引用。
- 第 4 步执行
onDestroy
方法。我们通过扩展该方法来正确的销毁组件,并确保我们添加的子组件也要销毁,以及自定义的侦听器也要清理。 - 第 5 步销毁插件及 mixin。
- 第 6 步中,如果组件已呈现,则将组件元素及侦听器都从 DOM 中删除。
- 第 7 步触发
destroy
事件。 - 第 8 步将本组件从组件管理器中删除,并清除其所有事件。
生命周期实践
Ext.define('Myapp.sample.CustomComponent',{
extend: 'Ext.Component',
// 重载父类的方法
// 一般用来配置组件属性
initComponent: function(){
var me = this;
me.width = 200;
me.height = 100;
me.html = {
tag: 'div',
html: 'X',
style: { // this can be replaced by a CSS rule
'float': 'right',
'padding': '10px',
'background-color': '#e00',
'color': '#fff',
'font-weight': 'bold',
'cursor': 'pointer'
}
};
me.myOwnProperty = [1,2,3,4];
//调用父类上的本方法
me.callParent();
console.log('Step 1. initComponent');
},
// 重载父类的方法
beforeRender: function(){
console.log('Step 2. beforeRender');
this.callParent(arguments);
},
// 重载父类的方法
onRender: function(){
console.log('Step 3. onRender');
// 父类中的本方法会创建组件主元素 el
this.callParent(arguments);
this.el.setStyle('background-color','#ccc');
},
// 重载父类的方法
afterRender : function(){
console.log('Step 4. afterRender');
// 为该组件中的某个 DOM 元素添加 click 事件侦听
this.el.down('div').on('click',this.myCallback,this);
this.callParent(arguments);
},
// 重载父类的方法
beforeDestroy : function(){
console.log('5. beforeDestroy');
this.callParent(arguments);
},
// 重载父类的方法
onDestroy : function(){
console.log('6. onDestroy');
// 销毁时确保清理掉自定义的对象和数组,并删除侦听器
delete this.myOwnProperty;
this.el.down('div').un('click',this.myCallback);
this.callParent(arguments);
},
myCallback : function(){
var me = this;
Ext.Msg.confirm('Confirmation','Are you sure you want to close this panel?',function(btn){
if(btn === 'yes'){
me.destroy();
}
});
}
});
Ext.onReady(function(){
Ext.create('Myapp.sample.CustomComponent',{
renderTo : Ext.getBody()
});
});
// 输出:
//Step 1. initComponent
//Step 2. beforeRender
//Step 3. onRender
//Step 4. afterRender
//5. beforeDestroy
//6. onDestroy
容器
容器即 Ext.container.Container
类(别名 Ext.AbstractContainer,Ext.Container)的实例,它用于管理子组件,并通过布局进行排列。如果我们需要我们的类能包含其它组件,则该类需要扩展至 Ext.container.Container
,同时容器也是组件,也扩展至 Ext.Component
,因此也有组件的全部生命周期。
所有的容器都能使用 items
属性(值为数组)来设置子组件,或通过 add
方法添加子组件。
Ext.define("MyApp.sample.MyContainer",{
// 扩展自容器类
extend: "Ext.container.Container", //Step 1
border: true,
padding: 10,
// 扩展父类的该方法进行一些属性设置
initComponent: function(){
var me = this;
// 为每个子组件设置一些属性
Ext.each(me.items,function(item){ //Step 2
item.style = {
backgroundColor:"#f4f4f4",
border:"1px solid #333"
};
item.padding = 10;
item.height = 100;
});
me.callParent();
},
onRender: function(){
var me = this;
me.callParent(arguments); // 生成 el 元素
if( me.border ){ //Step 3
me.el.setStyle( "border" , "1px solid #333" );
}
}
});
Ext.onReady(function(){
Ext.create("MyApp.sample.MyContainer",{
renderTo: Ext.getBody(),
// 容器组件 defaults 配置对象中设置的所有属性,
// 都将应用到每个子组件中。
defaults: {
xtype : "component",
width : 100
},
items: [
{
//xtype: "component",
html: "Child Component one"
}, {
//xtype: "component",
html: "Child Component two"
}
]
});
});
容器类型
Ext 中有多个容器组件。
- Ext.panel.Panel: 该组件扩展至 Ext.container.Container,它是最常用的容器。
- Ext.window.Window: 该组件扩展至 Ext.panel.Panel 类,作为应用窗口使用。它是浮动组件,可以改变大小,能拖放。它也能最大化来填充整个 viewport。
- Ext.tab.Panel: 该组件扩展至 Ext.panel.Panel 类,它能包含其它 Ext.panel.Panel 组件,在其 header 区域为每个 Panel 创建一个标签。它使用
card
布局来管理子组件。 - Ext.form.Panel: 该组件扩展至 Ext.panel.Panel 类,是表单的标准容器。本质上,它是一个 Panel 容器,并创建了一个基本的表单用来管理表单项组件。
- Ext.Viewport: 该容器表示应用程序区域(浏览器 viewport)。它将自身呈现到 document body,并将自动调整到浏览器 viewport 的大小。
同时每个容器都有一个 layout
属性,用来布局子组件。
Viewport
Viewport 表示应用的可见区域,最佳实践是:网页上只创建一个 viewport。
Ext.onReady(function(){
Ext.create('Ext.container.Viewport',{
padding:'5px',
layout:'auto',
style : {
'background-color': '#fc9',
'color': '#000'
},
html:'This is application area'
});
});
Panel
Ext.onReady(function(){
var MyPanel = Ext.create("Ext.panel.Panel",{
renderTo: Ext.getBody(),
// Panel 有 title 属性
title: 'My first panel...',
width: 300,
height: 220,
html:'<b>Here</b> goes some <i>content</i>..!'
});
});
Window
// 是一个浮动窗口
Ext.create("Ext.window.Window",{
title: 'My first window',
width: 300,
height: 200,
maximizable: true,//可以设置是否可最大化
closable: false, //默认为 true
html: 'this is my first window'
}).show();
布局系统
Border 布局
Border 布局将容器内的空间分成 5 个区域: north, south, west, east, center。每个区域都能放一个子组件,center 区域必须要放组件。
Ext.onReady(function(){
Ext.create('Ext.panel.Panel', {
width: 500, height: 300,
title: 'Border Layout',
layout: 'border',
items: [
{
xtype: 'panel',
title: 'South Region is resizable',
region: 'south', // region
height: 100,
split: true // 使该区域能调整尺寸
}, {
xtype: 'panel',
title: 'West Region',
region:'west', // region
width: 200,
collapsible: true, // 使该区域可折叠
layout: 'fit',
split: true // 使该区域能调整尺寸
},{
title: 'Center Region',
region: 'center',
layout: 'fit',
margin: '5 5 0 0',
html:'<b>Main content</b> goes here'
}
],
renderTo: Ext.getBody()
});
});
Fit 布局
用来只显示一个子组件,如果 items
属性中设置了多个组件,只显示第一个子组件。该子组件将占据容器的全部可用空间,并且会跟随容器改变自己的尺寸。
Ext.onReady(function(){
var win = Ext.create("Ext.window.Window",{
title: "My first window",
width: 300,
height: 200,
maximizable: true,
// 实际上 layout 属性值也可以是一个对象,用来定义布局的配置信息
layout: "fit",
defaults: {
xtype: "panel",
height: 60,
border: false
},
items: [
// 虽然定义了 2 个子组件,但是
// fit 布局只会显示第 1 个子组件
{title: "Menu", html: "The main menu"},
{title: "Content", html: "The main content!"}
]
});
win.show();
});
Card 布局
Card 布局扩展至 Fit 布局,因此一次也只显示 1 个组件。但是通过 setActiveItem(index),next(), prev() 等方法可以切换显示其它的子组件。
Ext.onReady(function(){
var win = Ext.create("Ext.window.Window",{
title: "My first window",
width: 300,
height: 200,
maximizable: true,
layout: "card",//Step 1
defaults:{ xtype: "panel", height: 60, border: false },
items: [
{
title: "Menu",
html: "The main menu"
},{
title: "Content",
html: "The main content!"
}
]
});
win.show();
var index = 1;
setInterval(function(){
// win.getLayout() 获取布局对象
win.getLayout().setActiveItem( index % 2); //Step 2
index += 1;
},3000);
});
Accordion 布局
类似 Card 布局,每次也只显示一个组件,并以手风琴扩展的形式展示。子组件的 header 部分都会显示,可以点击子组件的标题栏进行扩展/收缩。
var win = Ext.create("Ext.window.Window",{
title: "My first window",
width: 300,
height: 600,
maximizable: true,
layout: "accordion",
defaults: { xtype: "panel" },
items:[
{title: "Menu", html: "The main menu" },
{title: "Content", html: "The main content!" },
{title: "3rd Panel", html: "Content here...!" }
]
}).show();
Anchor 布局
子组件会相对容器的尺寸调整尺寸。
Ext.onReady(function(){
var win = Ext.create("Ext.window.Window",{
title: "My first window",
width: 300,
height: 300,
maximizable : true,
layout: "anchor",
defaults: {xtype: "panel", height: 60, border: false},
items: [
{
title: "Menu", html: "panel at 100% - 10 px",
anchor:'-10' // 一个值则针对宽度,数字表示 100% 容器宽度 - 10px
},{
title: "Content", html: "panel at 70% of anchor",
anchor:'70%' // 表示 70% 容器宽度
},{
title: "3rd Panel",
html: "panel at 50% width and 40% height of anchor",
anchor:'50% 40%', // 50% 宽度, 40% 容器宽度
bodyStyle:'background-color: #fc3;'
}
]
});
win.show();
});
更多布局
还有 Hbox, VBox, Table 等。