首頁  >  文章  >  web前端  >  Node.js框架 ThinkJS 開發 controller講解

Node.js框架 ThinkJS 開發 controller講解

巴扎黑
巴扎黑原創
2017-07-17 16:00:112283瀏覽

原創:荊秀網網頁即時推送 | 轉載請註明出處
連結:

本系列教學以ThinkJS v2.x 版本(官網)為例進行介紹,教程以實際操作為主。

本篇繼續講解 Controller 的使用。

建構方法

如果想要在物件實例化的時候做點事情,建構方法是最好的選擇。 ES6 提供的建構方法是 constructor

constructor方法是類別的預設方法,透過new指令產生物件實例時,自動呼叫方法。一個類別必須有constructor方法,如果沒有明確定義,一個空的constructor方法會被預設為添加。
方法
ECMAScript 6 入門作者:阮一峰

init 與constructor

thinkjs 強大的地方在於,我們不僅可以規規矩矩的export default class 自己宣告Class ,也提供了動態建立Class 的方法:think.controller

但是thinkjs 動態建立的Class 沒有constructor,而是提供了一個init 作為建構方法的替代方法,該方法的使用方式與constructor 一致。

上一篇文章(Node.js 國產MVC 框架ThinkJS 開發controller 篇基底類別與繼承鏈部分)中也有init 方法的使用範例,再看程式碼:


// src/home/controller/base.js'use strict';export default class extends think.controller.base {
  init(...args) {super.init(...args);// 要求全部 url 必须携带 auth 参数let auth = this.get('auth');if (think.isEmpty(auth)) {  return this.error(500, '全部 url 必须携带 auth 参数');}
  }}

當然這並不是表示不能使用constructor 方法了,假如你是像我一樣習慣使用export default class 自己宣告Class 的筒子,還是可以用回標準的constructor 方法的。

thinkjs 動態建立 Class 的方法請參考官方文檔,這裡不再贅述。

魔術方法

thinkjs 實現了幾個很有用的魔術方法,為開發提供了極大的便利,手動點讚~

__before 前置動作

顧名思義,前置操作會搶先在Controller 中具體的Action 執行之前執行,就是「在xxx 之前執行」的意思。來看程式碼:


// src/home/controller/user.js'use strict';export default class extends think.controller.base {
  __before() {console.log('this is __before().');
  }
  indexAction() {console.log('this is indexAction().');return this.end();
  }}// 访问 /home/user/index 的执行结果如下:// this is __before().// this is indexAction().

那麼可能有人會說:看起來 __beforeinit 是一樣的用途。老規矩,來看程式碼:


// src/home/controller/user.js'use strict';export default class extends think.controller.base {
  init(...args) {super.init(...args);console.log('this is init().');
  }
  __before() {console.log('this is __before().');
  }
  indexAction() {console.log('this is indexAction().');return this.end();
  }}// 访问 /home/user/index 的执行结果如下:// this is init().// this is __before().// this is indexAction().

看到了嗎?執行還是有先後順序的,再來個複雜一點的:


// src/home/controller/base.js'use strict';export default class extends think.controller.base {
  init(...args) {super.init(...args);console.log('this is base.init().');
  }}// src/home/controller/user.js'use strict';export default class extends think.controller.base {
  init(...args) {super.init(...args);console.log('this is user.init().');
  }
  __before() {console.log('this is user.__before().');
  }
  indexAction() {console.log('this is user.indexAction().');return this.end();
  }}// 访问 /home/user/index 的执行结果如下:// this is base.init().// this is user.init().// this is user.__before().// this is user.indexAction().

好吧,你會說「意料之中」~

__after 後置操作

明白了前置操作,後置操作也不難理解,看程式碼:


#
// src/home/controller/user.js'use strict';export default class extends think.controller.base {
  init(...args) {super.init(...args);console.log('this is init().');
  }
  __before() {console.log('this is __before().');
  }
  __after() {console.log('this is __after().');
  }
  indexAction() {console.log('this is indexAction().');return this.end();
  }}// 访问 /home/user/index 的执行结果如下:// this is init().// this is __before().// this is indexAction().

咦?貌似有地方不對。 。 。 __after 沒執行。

這當然不是 __after 寫在 indexAction 上面導致的!修改程式碼:


// src/home/controller/user.js'use strict';export default class extends think.controller.base {
  init(...args) {super.init(...args);console.log('this is init().');
  }
  __before() {console.log('this is __before().');
  }
  __after() {console.log('this is __after().');return this.end();
  }
  indexAction() {console.log('this is indexAction().');
  }}// 访问 /home/user/index 的执行结果如下:// this is init().// this is __before().// this is indexAction().// this is __after().

這次 OK 了,和預期的結果一致。

我知道細心的你已經注意到有句代碼 return this.end()indexAction 移動到 __after 裡面了。

this.end() 內部執行了Node.js HTTP response.end() 操作,表示整個回應流結束了,因此如果想要啟用__after 的話,這句程式碼就要放在__after 裡面運作。

__call 空操作

這個魔術方法有點特殊,它不像前兩個魔術方法一樣用於在某個流程節點打點運行,而是分擔了init 的部分職責:用來偵測某個Controller 被存取的Action 並未定義的情況下,由__call 來接手運作。


// src/home/controller/user.js'use strict';export default class extends think.controller.base {
  init(...args) {super.init(...args);console.log('this is init().');
  }
  __call() {console.log(this.http.action + 'Action is not exists.');return this.end();
  }
  indexAction() {console.log('this is indexAction().');return this.end();
  }}// 访问 /home/user/test 的执行结果如下:// this is init().// testAction is not exists.

可以看到當存取的testAction 不存在時,框架會執行__call 來處理,我們的處理是記錄錯誤並結束響應輸出。

範例程式碼是將 __call 放在了二級子類別中,通常是放在基底類別中,可以管控全部子類別的非法存取處理。

提示:本方法只能用來捕捉 Action 不存在的情況,但假如 Controller 不存在,會直接觸發 404 錯誤(被框架接管)而無法干涉。
如要捕捉 Controller 不存在的情況,需要擴展框架的錯誤類,另文描述。

外部呼叫方式

thinkjs 官網API 中有實例化另外一個Controller 的接口,但是並沒有說明這個具體有什麼用途:


//实例化 home 模块下 user controllerlet instance = think.controller('user', http, 'home');

那麼通常這個方法可以用來實例化兄弟層級Controller ,或是取得資料、或觸發一個業務流程等,來看程式碼:


// src/home/controller/user.js 增加_getPoints() {
  return 8000;}// src/home/controller/index.jslet instance = think.controller('user', this.http, 'home');let points = instance._getPoints();console.log(points); // 打印:8000instance.indexAction(); // 与直接执行 /home/user/index 是一样的效果instance.testAction(); // 报错 [Error] TypeError: instance.testAction is not a function

可見是thinkjs 提供了一個按需實例化某個Controller 並運行其方法的途徑。

乍看之下這個方式與this.redirect 運行結果非常接近(除了不會觸發__call 的魔術方法以外),那麼thinkjs 提供這個方式有什麼用呢?來看程式碼:


// src/home/controller/util.js'use strict';export default class extends think.controller.base {
  calcGPSDistance(lat, lng){// 计算 GPS 两点直线距离return distance;
  }
  calcBaiduDistance(lat, lng){// 计算 百度大地坐标 两点直线距离return distance;
  }
  calcSosoDistance(lat, lng){// 计算 Soso坐标 两点直线距离return distance;
  }}

这是一个助手 Controller,一个“隐身”的 Controller,从 url 是无法直接访问到的,因为它的所有方法名均没有 Action 后缀。

这个场景下,运行时实例化 Controller 并操作其方法的方式就派上用场了。

内置 http 对象

控制器在实例化时,会将 http 传递进去。该 http 对象是 ThinkJS 对 req 和 res 重新包装的一个对象,而非 Node.js 内置的 http 对象。
Action 里如果想获取该对象,可以通过 this.http 来获取。

thinkjs 官网

扩展应用:增加一个 n 秒后自动跳转的过渡页功能

thinkjs 框架并没有给我们准备这样一个过渡页面的功能,那么我们可以自己实现一个来练练手,上代码:


// src/common/controller/complete.js'use strict';export default class extends think.controller.base {
  /**   * 显示中转页面   *   * 调用方式:   * let complete = think.controller('complete', this.http, 'common');   * return complete.display('应用新增成功!', '/', 5);   *   * @param msg 提示文字,支持 HTML   * @param url 后续自动跳转的目标地址   * @param delay 停留秒数   * @returns {think.Promise}   */
  display(msg, url='', delay=3) {let tpl = 'common/complete/200';let opt = think.extend({}, {type: 'base', file_depr: '_', content_type: 'text/html'});this.fetch(tpl, {}, opt).then(content => {  content = content.replace(/COMPLETE_MESSAGE/g, msg);  if (url) {content = content.replace(/TARGET_URL/g, url);content = content.replace(/WAIT_SECONDS/g, delay);  };  this.type(opt['content_type']);  return this.end(content);}).catch(function(err){  return this.end('');});
  }}


<!-- view/common/complete_200.html --><!DOCTYPE html><html><head><title>正在跳转 - 荆秀网</title></head><body><p class="header"><p class="wrap"><p class="logo"><a href="/"><img src="/static/img/logo.png" alt="XxuYou" width="60"></a></p><p class="headr"> </p></p></p><p class="wrap"><p style="margin-top:20px;height:100px;background:url(/static/img/200.gif) top center no-repeat;"></p><h1>COMPLETE_MESSAGE</h1><p class="error-msg"><pre class="brush:php;toolbar:false">提示:页面将在 <span id="_count">WAIT_SECONDS</span> 秒后重定向到 <a href="TARGET_URL">TARGET_URL</a>


// Controller 内调用方式indexAction() {
  // 业务流程。。。
  let complete = think.controller(&#39;complete&#39;, this.http, &#39;common&#39;);
  return complete.display(&#39;操作成功!&#39;, &#39;/&#39;, 5);}

以上新增的 src/common/controller/complete.js 是一个非侵入式的成功业务处理页面,其内部运行与兄弟 Controller src/common/controller/error.js 类似。

以上新增的 view/common/complete_200.html 则是相关的过渡页面的模版。其中存在三个占位符(分别对应 display 方法的入參):

  • COMPLETE_MESSAGE 用于操作成功的文字提示内容

  • TARGET_URL 用于稍后会自动进入的目标 url

  • WAIT_SECONDS 用于页面过渡时间,单位是秒

实现原理其实非常简单,阅读一下两个新增的代码就能明白。

扩展应用:快速构建 REST API

其实这部分因为太简答,我本来是不想写的。不过考虑到教程的完整性,还是写一下比较好。

REST 的概念介绍这里不再赘述,有兴趣的可以自行搜索。

thinkjs 的官网说到:

自动生成 REST API,而无需写任何的代码。

此言不虚,创建 Controller 时只要增加一个参数(thinkjs controller [name] --rest),即可生成一个能够操作数据库表的 REST API。

当然操作的约定也还是有的:

  • GET /ticket #获取ticket列表

  • GET /ticket/12 #查看某个具体的ticket

  • POST /ticket #新建一个ticket

  • PUT /ticket/12 #更新ticket 12

  • DELETE /ticket/12 #删除ticekt 12

遵从了上述操作约定,的确是可以直接操作数据库表内的数据了。

只是这样的 API 只不过是一个“裸奔”状态的 API,还是不能直接投入使用。因为它不仅要求请求方熟悉所有的数据表结构,还要依赖请求方来维护多表数据之间的关联性,更不用提什么操作路径的映射、字段映射、返回数据的映射等等问题了。

就算 thinkjs 还提供了字段过滤、自定义 GET/POST/PUT/DELETE 方法来进行更多的定制,那么最终的结果很可能是在当前的 API 外面再包裹一层能够提供操作路径映射、鉴权令牌发放和识别、字段映射、关联数据维护等等。

当然作为一个开发框架,thinkjs 确实已经做的够多了,足够优秀了,因此我们还是需要像构建一个应用系统那样去完整构建一个可供实施的 REST API 的生产模型。

以上是Node.js框架 ThinkJS 開發 controller講解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn