如果你第一次看Sencha Touch MVC應用程序的例子,很可能會被那一個個文件夾和一堆堆js文件搞得頭昏腦脹,它們之間到底有什么關系?互相之間如何協作?這就是你在下手寫代碼之前必須搞清楚的第一件事了,本專題將為你解決這個困惑。
這篇文章的英文原址是 http://docs.sencha.com/touch/2-0/#!/guide/mvc_dependencies
原文標題是:Managing Dependencies with MVC(管理MVC依賴項)。
Sencha Touch 交流QQ群213119459歡迎您的加入。
Managing Dependencies with MVC
管理MVC依賴項
注:為方便起見,文中所有出現 Sencha Touch的地方均以 ST簡寫替代。
There are two main places that dependencies can be defined in a Sencha Touch 2 app - on the application itself or inside the application classes. This guide gives some advice on how and where to declare dependencies in your app.
ST2應用程序用來定義依賴項的地方主要有兩個,application本身和應用程序內部類。本指南將給出一些關於應用程序如何放置和在哪里放置依賴項的建議。
Application Dependencies
應用程序依賴項
When you create an MVC application, your Ext.application gives you a convenient way of specifying the models, views, controllers, stores and profiles that your application uses. Here's an example:
當你創建一個MVC應用程序時,Ext.application會提供一個直觀的方式來設置應用程序會用到的數據模型、視圖、控制器、數據存儲器和配置文件等,如下例所示:
Ext.application({
name: 'MyApp',
views: ['Login'],
models: ['User'],
controllers: ['Users'],
stores: ['Products'],
profiles: ['Phone', 'Tablet']
});
These 5 configuration options are convenient ways to load the types of files that applications usually consist of - models, views, controllers, stores and profiles. Specifying these configurations means your application will automatically load the following files:
這5個配置項是用來加載應用程序常用文件(數據模型、視圖、控制器、存儲器、配置文件)的快捷方式。如上的配置意味着應用程序會自動加載下列文件:
app/view/Login.js
app/model/User.js
app/controller/Users.js
app/store/Products.js
app/profile/Phone.js
app/profile/Tablet.js
In terms of what gets loaded, the example above is equivalent to defining dependencies manually like this:
就加載文件而言,上面的例子跟下面的定義是等價的:
Ext.require([
'MyApp.view.Login',
'MyApp.model.User',
'MyApp.controller.Users',
'MyApp.store.Products',
'MyApp.profile.Phone',
'MyApp.profile.Tablet'
]);
As you add more classes to your application, these configurations become more and more useful in helping you avoid typing out the full class names for every file. Be aware, however, that three of those configurations do more than just load files. As well as loading the files, they do the following:
在你需要加載更多的類文件情況下,這種配置方式就會更有用,它能避免你為每一個文件都拼寫又臭又長的完整類名。除了把依賴文件加載進來之外,這幾個配置還會做更多的事情:
l profiles - instantiates each Profile and determines if it should be active. If so, the Profile's own dependencies are also loaded
配置文件 – 實例化每一個Profle並判斷哪一個當前可用。當前可用的那個profile中所有依賴項也將被加載
l controllers - instantiates each Controller after loading
控制器 – 加載完成后實例化每一個控制器
l stores - instantiates each Store, giving it a default store ID if one is not specified
存儲器 – 實例化每一個存儲器,沒有指定id的存儲器會被指定一個默認id
What this means is that if you want to take advantage of all of the convenience MVC offers you, you're advised to use these configuration options when defining your application dependencies.
這意味着,如果你要享受MVC帶給你的便利,那么載你定義應用程序依賴項的時候,最好使用配置選項這種方式。
Profile-specific Dependencies
配置文件指定的依賴項
When using Device Profiles, chances are it means you have some classes that are used only on certain devices. For example, the Tablet version of your app probably contains more functionality than the Phone version, which usually means it will need to load more classes. Additional dependencies can be specified inside each Profile:
當你使用設備配置的時候,可能會有一些類是僅在特定設備上使用的。例如,平板電腦版本的應用程序可能包含比手機版本更多的功能,這當然意味着要加載更多的類。每個Profile都可以在內部定義額外的依賴項。
Ext.define('MyApp.profile.Tablet', {
extend: 'Ext.app.Profile',
config: {
views: ['SpecialView'],
controllers: ['Main'],
models: ['MyApp.model.SuperUser']
},
isActive: function() {
return Ext.os.is.Tablet;
}
});
Now what's going to happen here is that the dependencies specified in each Profile are going to be loaded regardless of whether or not the Profile is active. The difference is, even though they're loaded, the Application knows not to do the additional processing like instantiating profile-specific Controllers if the profile is not active.
然后每個profile中定義的依賴項都會被加載,不管這個profile是否active,不過盡管都被加載,但應用程序不會去做類似實例化非active狀態profile指定的控制器這樣的無用功。
This probably sounds counter-intuitive - why download classes that aren't going to be used? The reason we do this is to produce a universal production build that can be deployed to any device, detect which profile it should use and then boot the app. The alternative is to create custom builds for each profile, craft a micro-loader than can detect which profile a device should activate and then download the code for that profile.
這聽起來有點不合常規,為什么要下載那些不會用到的類文件呢?這么做的原因是產生一個可以在任何設備上運行的通用程序版本,然后檢測哪一個profile應該被使用,接着從這個profile啟動應用程序。與之相對的選擇是為每個profile創建一個應用版本,然后啟動一個微型加載器來檢測哪個profile該被選擇,然后去下載該profile需要的代碼文件。
While the universal build approach does mean you're downloading code you don't need on every device, for the vast majority of apps this amounts to so little additional size it's difficult to detect the difference. For very large apps the difference will become more noticeable so it's possible we'll revisit this subject after 2.0.
的確這種通用架構的程序會在每個設備上都下載一些根本用不到的代碼文件,不過對於絕大多數應用程序來說,你多下載的這點文件影響實在是微乎其微。而對於比較龐大的應用程序來說,這個問題可能更值得注意,所以我們可能在2.0的后續版本對它進行處理。
Nested Dependencies
級聯依賴
For larger apps it's common to split the models, views and controllers into subfolders so keep the project organized. This is especially true of views - it's not unheard of for large apps to have over a hundred separate view classes so organizing them into folders can make maintenance much simpler.
大一些應用通常會把數據模型、視圖、控制器分別存儲在不同子文件夾下,這樣可以讓整個項目看起來更清晰明了一些。對於視圖來說尤其如此,大型應用擁有上百個獨立的視圖類並非天方夜譚,因此分文件夾存儲幾乎不可避免。
To specify dependencies in subfolders just use a period (".") to specify the folder:
指定子文件夾中的依賴項只需要使用“.”來分割文件夾路徑即可:
Ext.application({
name: 'MyApp',
controllers: ['Users', 'nested.MyController'],
views: ['products.Show', 'products.Edit', 'user.Login']
});
In this case these 5 files will be loaded:
上例中將會加載下列5個文件
app/controller/Users.js
app/controller/nested/MyController.js
app/view/products/Show.js
app/view/products/Edit.js
app/view/user/Login.js
Note that we can mix and match within each configuration here - for each model, view, controller, profile or store you can specify either just the final part of the class name (if you follow the directory conventions), or the full class name.
我們可以混合使用兩種方式來定義每個數據模型、視圖、控制器、配置文件和存儲器:快捷路徑方式(符合mvc推薦原則的類只寫最后的類名即可)和完整路徑方式(自定義路徑的類則寫完整路徑加類名)。
External Dependencies
外部依賴項
We can specify application dependencies from outside our application by fully-qualifying the classes we want to load. A common use case for this is sharing authentication logic between multiple applications. Perhaps you have several apps that login via a common user database and you want to share that code between them. An easy way to do this is to create a folder alongside your app folder and then add its contents as dependencies for your app.
我們可以通過指定想要加載的完整類名方式來定義應用程序之外的類作為依賴項。這種情況最常見的用途就是在多個應用之間共享認證邏輯。我們可能會有好幾個應用程序都要到同一個用戶數據庫進行驗證並實現登錄,這時我們當然希望它們能夠共享用戶登錄的代碼。比較容易的方式就是在應用程序文件夾之外創建一個單獨的文件夾然后把其中的內容作為依賴項添加到應用程序中去。
For example, let's say our shared login code contains a login controller, a user model and a login form view. We want to use all of these in our application:
我們假定共享的登錄代碼包含一個login控制器,一個用戶model和一個login表單視圖。我們要在應用程序中把它們全部用上:
Ext.Loader.setPath({
'Auth': 'Auth'
});
Ext.application({
views: ['Auth.view.LoginForm', 'Welcome'],
controllers: ['Auth.controller.Sessions', 'Main'],
models: ['Auth.model.User']
});
This will load the following files:
上述代碼將加載以下的文件:
Auth/view/LoginForm.js
Auth/controller/Sessions.js
Auth/model/User.js
app/view/Welcome.js
app/controller/Main.js
The first three were loaded from outside our application, the last two from the application itself. Note how we can still mix and match application files and external dependency files.
前面三個文件加載自應用程序外部,后兩個則來自應用程序內部。同樣我們可以混合調用內外部依賴項。
Note that to enable the loading of external dependencies we just have to tell the Loader where to find those files, which is what we do with the Ext.Loader.setPath call above. In this case we're telling the Loader to find any class starting with the 'Auth' namespace inside our 'Auth' folder. This means we can drop our common Auth code into our application alongside the app folder and the framework will be able to figure out how to load everything.
想要啟用外部依賴項加載,我們只需告訴Loader到哪里可以找到這些文件即可,Ext.Loader.setPath就是干這個的。上例中我們告訴Loader所有以Auth命名空間中的文件都可以到Auth這個文件夾中找到。這樣我們就能把應用程序文件夾之外的通用驗證代碼都拽進來了,其他的事情由ST框架來處理。
Where Each Dependency Belongs
依賴項應該放在哪里
The general rule when deciding where to declare each dependency is to keep your classes completely self-contained. For example, if you have a view that contains several other views, you should declare those dependencies inside the view class, not the application:
決定在哪里聲明依賴項的一個基本原則就是保證你的類文件完整的內部包含。例如,你有一個視圖A包含了幾個其他的視圖,你就應該在這個A視圖內部聲明它的依賴項,而不是在application中:
Ext.define('MyApp.view.Main', {
extend: 'Ext.Container',
requires: [
'MyApp.view.Navigation',
'MyApp.view.MainList'
],
config: {
items: [
{
xtype: 'navigation'
},
{
xtype: 'mainlist'
}
]
}
});
And in your app.js:
App.js中這么寫:
Ext.application({
views: ['Main']
});
This is the best way to declare those dependencies for two reasons - it keeps your app.js clean and enables you to reliably require your MyApp.view.Main and know that it already has all of its dependencies satisfied. The alternative would be to list all of your views inside your app.js like this:
這才是依賴項的最佳聲明方式。兩個原因:1、確保app.js干凈;2、讓你知道主程序依賴MyApp.view.Main就已經足夠。不好的方式就是下面這樣把視圖都羅列在app.js里:
//this is bad
Ext.application({
views: ['Main', 'Navigation', 'MainList']
});
A simple way of thinking about this is that app.js just contains top-level views. If you use Ext.create('MyApp.view.SomeView') inside your app, that view can be considered top-level. If a view is only ever constructed as a sub-view of another view (as with MyApp.view.Navigation and MyApp.view.MainList above), it doesn't belong in app.js.
換種方式來描述這個問題,app.js只需要包含最頂級的視圖即可。你在應用程序內部通過Ext.create('MyApp.view.SomeView')來創建的視圖就可以視作頂級視圖。其他那些僅僅被作為某些視圖內部子視圖的(比如例子中的MyApp.view.Navigation和MyApp.view.MainList)就不應該出現在app.js里面。
Changes since 1.x
跟1.x版本的不同之處
In Sencha Touch 1, dependencies were often specified in Controllers as well as in the Ext.application call. While this offered some conveniences, it also masked the true architecture of the system and coupled views, models and stores too closely to controllers. Here's what you could do in 1.x:
在ST1中,依賴項經常在控制器中指定(就跟Ext.application中那樣)。這樣的確提供了一些便利,但同時也模糊了系統的真實架構,而且視圖、數據模型和數據存儲器與控制器之間靠得太緊密。在1.x中我們是這么做的:
//1.x code, deprecated
Ext.regController('SomeController', {
views: ['Login'],
models: ['User'],
stores: ['Products']
});
This is exactly the same as defining the views, models and stores inside Ext.application, but also gave some convenience methods for accessing those classes inside your controller. 1.x generated two functions - getLoginView() and getUserModel() - and exposed a getStore() function that returned a reference to any of the Stores you defined in this Controller. In 2.x these functions no longer exist, but it's easy to use the alternatives.
這種方式跟Ext.application中定義視圖、數據模型和數據存儲器是一樣的,這么做的確也為控制器內部訪問這些類提供了很方便的方法。ST1.x框架會自動產生兩個方法getLoginView()和getUserModel(),還有一個getStore()函數可以返回對控制器中定義的任意數據存儲器的引用。在ST2.x中這些函數都不復存在,但是也提供了很便利的方法。
In each case here the first line refers to Sencha Touch 1.x, with the second line showing the 2.x way:
下面例子的第一行是ST1.x的寫法,第二行是ST2.x的寫法:
//creating a view - 2.x uses the standardized Ext.create
this.getLoginView().create();
Ext.create('MyApp.view.Login');
//getting a Model - just type out the Model name (it's shorter and faster)
this.getUserModel();
MyApp.model.User;
//Ext.getStore can access any Store whereas the old this.getStore only
//accessed those Stores listed in your Controller
this.getStore('Products');
Ext.getStore('Products');
Removing these functions speeds up application launching because the framework no longer needs to generate one function for each model and view defined in each Controller. It also means that the conventions for MVC match the conventions for the rest of the framework, leading to a more predictable API.
去掉這些方法加速了應用程序的運行速度,因為ST框架無需再為控制器中的每個視圖和數據模型來生成一個方法了。這就意味着使用MVC的便利之處同樣也對其他框架有效,從而使得整個API更加受控。
