行動 (Actions) 負責回應來自網頁瀏覽器、行動應用程式或任何其他能夠與伺服器通訊的系統的請求 (requests)。它們通常充當您的 模型 (models) 和 視圖 (views) 之間的中間人,並協調您專案的大部分 商業邏輯 (business logic):您可以使用行動來提供網頁、處理表單提交、管理第三方 API 請求,以及介於兩者之間的所有事務。
行動會綁定到您應用程式中的 路由 (routes)。當使用者代理 (user agent) 請求特定的 URL 時,綁定到該路由的行動會在內部執行商業邏輯並發送回響應。例如,您應用程式中的 GET /hello
路由可以綁定到如下的行動:
async function (req, res) {
return res.send('Hi there!');
}
每當網頁瀏覽器導航到您應用程式伺服器上的 /hello
URL 時,頁面將顯示訊息:「嗨,你好!(Hi there!)」。
行動在 api/controllers/
資料夾和子資料夾中定義(我們稍後會詳細討論控制器 (controllers))。為了讓 Sails 識別檔案為行動,檔名必須是 kebab-cased(僅包含小寫字母、數字和破折號)。在 Sails 中引用行動時(在大多數情況下,當 將其綁定到路由 時),請使用其相對於 api/controllers
的路徑,不帶任何檔案副檔名。例如,要將路由綁定到位於 api/controllers/user/find.js
的行動,您會將其 URL 指向 user/find
。
預設情況下,Sails 只知道如何解析 .js
檔案,但您可以自訂您的應用程式以使用 CoffeeScript 或 TypeScript 等。行動可以具有任何非 .md
(Markdown) 和 .txt
(text) 的檔案副檔名。
行動檔案可以使用兩種格式之一:actions2(推薦)或經典 (classic)。
自 Sails v1.0 發布以來,我們建議以更現代的 "actions2" 語法編寫您的行動,其運作方式與 Sails 助手函式 (helpers) 非常相似。透過以這種方式定義您的行動,它們本質上是自我記錄和自我驗證的。
使用 actions2 提供多項優勢
sails generate action
快速建立 actions2 檔案exits
) 都清晰可見,無需剖析程式碼req
和 res
,使其更容易重複使用或抽象化為 助手函式 (helper)請注意,當使用 actions2 時,您可以將 請求物件 (request object) 作為
this.req
存取。
或者,您可以將env
傳遞到具有inputs
和exits
的函式中,以在不使用this.req
的情況下存取req
。
簡而言之,您的程式碼將以標準化的方式編寫,使其更容易在以後重複使用和修改。而且由於您會預先宣告行動的參數,因此您將更不容易暴露邊緣案例 (edge cases) 和安全漏洞 (security holes)。
以下是 actions2 格式的範例
module.exports = {
friendlyName: 'Welcome user',
description: 'Look up the specified user and welcome them, or redirect to a signup page if no user was found.',
inputs: {
userId: {
description: 'The ID of the user to look up.',
// By declaring a numeric example, Sails will automatically respond with `res.badRequest`
// if the `userId` parameter is not a number.
type: 'number',
// By making the `userId` parameter required, Sails will automatically respond with
// `res.badRequest` if it's left out.
required: true
}
},
exits: {
success: {
responseType: 'view',
viewTemplatePath: 'pages/welcome'
},
notFound: {
description: 'No user with the specified ID was found in the database.',
responseType: 'notFound'
}
},
fn: async function ({userId}) {
// Look up the user whose ID was specified in the request.
// Note that we don't have to validate that `userId` is a number;
// the machine runner does this for us and returns `badRequest`
// if validation fails.
var user = await User.findOne({ id: userId });
// If no user was found, respond "notFound" (like calling `res.notFound()`)
if (!user) { throw 'notFound'; }
// Display a personalized welcome view.
return {
name: user.name
};
}
};
Sails 使用 machine-as-action 模組,從格式如上述範例的行動中自動建立路由處理函式。請參閱 machine-as-action 文件 以獲取更多資訊。
在行動、助手函式或腳本中,拋出任何東西都將預設觸發 error
結束 (exit)。如果您想觸發任何其他結束,您可以透過拋出「特殊結束訊號 (special exit signal)」來做到這一點。這將是一個字串(結束的名稱),或是一個物件,其中結束的名稱作為鍵,而輸出資料作為值。例如,取代通常的語法
return exits.hasConflictingCourses();
您可以使用簡寫
throw 'hasConflictingCourses';
或者,包含輸出資料
throw { hasConflictingCourses: ['CS 301', 'M 402'] };
除了易於閱讀的簡寫之外,如果您在 for
迴圈、forEach
等內部,但仍然想透過特定的結束退出,則結束訊號尤其有用。
如果您正在使用現有的程式碼庫或從 v0.12 升級的應用程式,您可能更習慣經典行動格式。經典行動宣告為具有 req
和 res
參數的函式。當客戶端請求綁定到此類型行動的路由時,函式會使用 傳入的請求物件 (incoming request object) 作為第一個參數 (req
),並使用 傳出的響應物件 (outgoing response object) 作為第二個參數 (res
) 執行。
這是一個範例行動,它通過 ID 查找使用者,然後顯示「歡迎 (welcome)」視圖,或者在找不到使用者時重定向到註冊頁面
module.exports = async function welcomeUser (req, res) {
// Get the `userId` parameter from the request.
// This could have been set on the querystring, in
// the request body, or as part of the URL used to
// make the request.
var userId = req.param('userId');
// If no `userId` was specified, or it wasn't a number, return an error.
if (!_.isNumeric(userId)) {
return res.badRequest(new Error('No user ID specified!'));
}
// Look up the user whose ID was specified in the request.
var user = await User.findOne({ id: userId });
// If no user was found, redirect to signup.
if (!user) {
return res.redirect('/signup' );
}
// Display the welcome view, setting the view variable
// named "name" to the value of the user's name.
return res.view('welcome', {name: user.name});
}
您可以使用
sails generate action
和--no-actions2
快速建立經典行動。
對於較簡單的專案和原型,通常最快開始編寫 Sails 應用程式的方法是將您的行動組織到控制器檔案 (controller files) 中。控制器檔案是一個 PascalCased 檔案,其名稱必須以 Controller
結尾,其中包含行動的字典。例如,「使用者控制器 (User Controller)」可以在 api/controllers/UserController.js
檔案中建立,其中包含
module.exports = {
login: function (req, res) { ... },
logout: function (req, res) { ... },
signup: function (req, res) { ... },
};
您可以使用 sails generate controller
快速建立控制器檔案。
就像行動檔案一樣,您可以自訂您的應用程式以使用 CoffeeScript 或 TypeScript 等,儘管 Sails 預設只知道如何解析 .js
檔案。控制器可以具有任何除了 .md
(Markdown) 和 .txt
(text) 之外的檔案副檔名。
對於更大、更成熟的應用程式,獨立行動 (standalone actions) 可能比控制器檔案 (controller files) 更好。在這個方案中,不是在單個檔案中包含多個行動,而是每個行動都在其自身的檔案中,位於 api/controllers
的適當子資料夾中。例如,以下檔案結構將等同於 UserController.js
檔案
api/
controllers/
user/
login.js
logout.js
signup.js
與控制器檔案相比,使用獨立行動有幾個優勢
foo/bar/baz.js
vs. foo/BarController.baz
)api/controllers/index.js
檔案,並使其自動綁定到您應用程式的 /
路由(而不是建立任意的控制器檔案來保存根行動)在大多數 MVC 框架的傳統中,成熟的 Sails 應用程式通常具有「精簡 (thin)」的控制器——也就是說,您的行動程式碼最終會很精簡,因為可重複使用的程式碼已移至 助手函式 (helpers),或者偶爾甚至提取到單獨的 Node 模組中。隨著應用程式複雜性的增加,這種方法絕對可以使您的應用程式更易於維護。
但同時,過早 將程式碼外推到可重複使用的助手函式中可能會導致維護問題,從而浪費時間和生產力。正確的答案介於兩者之間。
Sails 建議這個一般經驗法則:等到您第三次要使用相同的程式碼片段時,再將其外推到單獨的助手函式中。 但是,與任何教條 (dogma) 一樣,請運用您的判斷力!如果相關的程式碼非常長或複雜,那麼盡早將其拉出到助手函式中可能更有意義。相反地,如果您知道您正在建構的是快速的拋棄式原型 (throwaway prototype),您可能只需複製並貼上程式碼以節省時間。
無論您是為了熱情還是為了利潤而開發,歸根結底,目標都是盡可能充分利用您作為工程師的時間。有時這意味著編寫更多的程式碼,而另一些時候則意味著關注專案的長期可維護性。如果您不確定在您目前的開發階段哪個目標更重要,您可能需要退後一步並仔細思考(最好與您的團隊的其他成員或 其他在 Node.js/Sails 上建構應用程式的人 聊聊)。