大家好,小弟飛狐。好久沒來思否了,再來帶來了的一定是乾貨。從Deno開始,飛狐帶來的絕對到目前為止前所未有的Deno系列。話不多說,用技術說話。
你學不動的 Deno 來了
還記得 Github 上那個讓人學不動的 Deno 麼?就在2020年5月13日,Deno1.0正式釋出。作為新晉網紅執行時,Deno真的會替代 Node 嗎?這個問題可以追溯到2018年,從Node之父 Ryan Dahl的演講說起,Ryan在演講中談及對Node有十大不滿之處,並且在演講的最後公佈了Deno專案。我在這裡只列三大新特性。
- 首先,Deno 作為一個JavaScript/TypeScript 執行時,底層基於效能超高的 Rust 編寫,在效能和儲存安全上有先天的優勢。
- 其次,Deno 擁有完整的標準庫,不再有 NPM 或 node_modules 資料夾,允許從任何地方匯入所需模組。
- 另外,Deno 整合 TypeScript,不再像以前一樣藉助工具編譯,而是通過內部轉換。不過似乎又要剝離。
綜上所述,你會發現,Deno真的是青出於藍而勝於藍。是否替代,只是時間問題而已。我也看到很多人在做deno和node的效能比較,但在目前我認為做這兩者的效能比較完全沒有必要。
安裝
我們一開始甭管deno底層用的go還是rust,為啥要從go換成rust、或者是deno的技術架構是咋樣?這些目前都不要關心,我們就把deno當成一個新的執行時,只做執行時。從零到一,通過搭建一套腳手架,再慢慢去深入。Deno可以在Mac、Linux、Windows三大系統上執行。Deno也不需要其他依賴。通過如下方式安裝:
- Shell (Mac, Linux):
curl -fsSL https://deno.land/x/install/install.sh | sh
- PowerShell (Windows):
iwr https://deno.land/x/install/install.ps1 -useb | iex
- Homebrew (Mac):
brew install deno
- Chocolatey (Windows):
choco install deno
- Scoop (Windows):
scoop install deno
第一個例子也是來自官方,每一行我都加了解釋。如下:
// 建立檔案 /server.ts
// 不需要像node一樣去npm,這裡直接引入
import { serve } from "https://deno.land/std@0.63.0/http/server.ts";
// 構建服務,設定埠
const s = serve({ port: 8000 });
// 這裡是直接返回
for await (const req of s) {
req.respond({ body: "Hello World\\n" });
}
整個程式碼非常簡單,語法也是ts,和node非常像。有node基礎的同學直接入手。
執行命令:deno run --allow-net ./server.ts
,
然後在瀏覽器開啟http://localhost:8000,就可以看到hello world了,如下圖:
框架之選
眾所周知,node的框架比較成熟,國外的有nest.js、國內的有egg.js。而目前deno的生態其實並不成熟,框架也都是模仿其他框架,多的就不介紹了,這裡我給大家推薦兩個框架。如下:
- oak
- alosaur
推薦oak的原因很簡單,就是我們整個腳手架搭建都是基於oak的。oak模仿的是node的框架koa,從名字也可以看出來。 而推薦alosaur的原因就一點,可學性很強。有興趣可以去看這個框架的原始碼,非常多值得借鑑的地方。飛狐教大家搭建腳手架,雖然不用這個框架,但很多底層實現也是借鑑的這個框架,比如註解路由。好啦,框架就介紹到這裡啦,後面我們再慢慢深入。
話不多說,我們先來個oak例子:
// 引入oak框架
import { Application } from "https://deno.land/x/oak/mod.ts";
// 初始化
const app = new Application();
// 跟koa一樣,執行上下文
app.use((ctx) => { ctx.response.body = "Hello World!"; });
// 監聽埠
await app.listen({ port: 8000 });
在執行的時候,可能會報錯,如下:
是不是有點受挫。其實大可不必,這個問題是deno版本迭代時std版本未更新至最新引起的。
注意,deno是新玩意兒,有不少坑,官方也在頻繁迭代解決這些坑。 所以,理解萬歲。最簡單的解決辦法只需要把oak的版本升級成最新就好了,如下面的例子。
路由
路由部分oak跟koa不一樣的是,oak直接提供路由,只需引入即可。如下:
// 升級到oak的最新版本
import { Application, Router } from "https://deno.land/x/oak@v6.0.1/mod.ts";
// 這是官方的例子
const books = new Map<string, any>();
books.set("1", {
id: "1",
title: "聽飛狐聊deno",
author: "飛狐",
});
// 建立路由
const router = new Router();
// 路由,和koa-router的用法一樣
router
.get("/", (context) => {
context.response.body = "Hello world!";
})
.get("/book", (context) => {
context.response.body = Array.from(books.values());
})
.get("/book/:id", (context) => {
if (context.params && context.params.id && books.has(context.params.id)) {
context.response.body = books.get(context.params.id);
}
});
const app = new Application();
// 應用路由
app.use(router.routes());
// 允許路中介軟體引入
app.use(router.allowedMethods());
await app.listen({ port: 8000 });
切記,一定要用最新的版本。同樣的,輸入命令執行。如下圖(postman測試介面):
拆分路由
MVC模式大家都不陌生了,我們這裡僅僅做個簡單的拆分,把路由和控制層獨立出去。 建立一個controller資料夾,在該資料夾下建立一個bookController檔案,如下:
// 建立bookController.ts,
// 我們把資料先移到這來再說
const books = new Map<string, any>();
books.set("1", {
id: "1",
title: "聽飛狐聊deno",
author: "飛狐",
});
// 這裡直接返回一個物件,把路由對映的方法也搬到這裡
export default {
getbook: ((context: any) => {
context.response.body = Array.from(books.values());
}),
getbookById: ((context: any) => {
if (context.params && context.params.id && books.has(context.params.id)) {
context.response.body = books.get(context.params.id);
}
})
}
再在根目錄下建立router.ts,程式碼如下:
import { Router } from 'https://deno.land/x/oak@v6.0.1/mod.ts';
// 引入控制層的檔案
import bookController from './controller/bookController.ts'
const router = new Router();
router
.get("/", (context) => {
context.response.body = "Hello world!";
})
.get("/book", bookController.getbook)
.get("/book/:id", bookController.getbookById)
export default router
原來的入口檔案server.ts,就變得十分簡潔了,如下:
import { Application } from 'https://deno.land/x/oak@v6.0.1/mod.ts';
import router from './router.ts'
const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 8000 });
這樣就很簡潔了,也利於擴充套件。比如:
- 後續用到業務邏輯的時候,我們可以再新增service層
- 後續用到中介軟體的時候,我們可以再新增middleware層
- 後續擴充套件異常處理等等
在這個基礎上,在後面的篇章裡我們再繼續深入。今天的內容其實已經完成了。這裡再介紹一下,為啥說這個系列是架構級思想呢?因為我們在寫程式碼的時候,就會基於一些特定的場景考慮,比如微服務等。就像golang裡的go-micro,會整合grpc、etcd、gin等等一樣。有興趣我也可以寫一套golang系列分享給大家,小弟我有太多想和大家夥兒分享的東東了,比如,TensorFlow.js、Julia量化交易等。呃~好像跑偏了,還是拉回來先送大家一個結尾彩蛋,為下一篇打基礎。
彩蛋(改造入口檔案)
從現在起,後面的部分我們要用類class的方式來寫程式碼了,先從入口檔案開始,改造如下:
// server.ts
import { Application } from 'https://deno.land/x/oak@v6.0.1/mod.ts';
import router from './router.ts'
const app = new Application();
class Server {
constructor () {
this.init()
}
async init () {
app.use(router.routes());
app.use(router.allowedMethods());
this.listen()
}
async listen () {
await app.listen({ port: 8000 });
}
}
new Server()
這裡我不用註釋了,單獨講解一下這個地方,主要三點:
- 1.建立一個server類,建立一個初始化函式init()
- 2.在建構函式裡呼叫初始化函式,把路由,中介軟體,監聽函式,一切需要初始化執行的函式都拎進去
- 3.最後執行這個類 這樣做的好處,一個標準化,一個是利於擴充套件。
下回預告
回顧一下,這篇內容很淺顯,主要是安裝deno,簡單實用oak框架,拆分路由而已。 下回我們直接聊typescript裝飾器模式,註解,並且實現註解路由。控制層也按照類class的寫法,就跟Java的springmvc一樣,如下圖:
從上圖可以看到,這樣根本就不需要router檔案啦。不管是node、deno還是golang,飛狐真的很不喜歡一個單獨的router檔案去維護路由,太麻煩。所以每次搭架子的時候,我一定是先把路由給搞定了,免得麻煩。現在理解為啥下回直接開幹註解路由了吧,deno的註解路由還是很一波三折的,因為完全照搬node會有些坑。期待吧,嘿嘿?~