本文由郝晨光整理總結并編寫,未經允許禁止轉載。
前言
學習koa,我之前學習過express,但是在使用express的時候,還是一直使用的回調函數的方式來處理異步,現在想想真是恐怖,後來了解到koa這個架構,它相對于express來說,小巧了很多,對于異步的處理也變得更加優雅了。用官方的話來說,koa是基于Node.js平台的下一代web平台開發架構。
正文
- 首先肯定要先開始一個新的項目,建立一個檔案夾
- 在檔案夾目錄中,使用指令提示符執行
,初始化npm init -y
檔案;package.json
- 安裝
- Koa 依賴 node - v7.6.0 或 ES2015及更高版本和 async 方法支援。
- 先檢視node的版本
;node -v
- 然後執行
進行安裝;npm install koa
- 使用
- 建立index.js;
- 先上官網上最經典的hello wrold案例;
-
const Koa = require('koa'); const app = new Koa(); app.use(async ctx => { ctx.body = 'hello word!' }) app.listen(8000, () => { console.log('Koa server listen in http://localhost:8000/'); })
- 打開浏覽器,輸入
,就可以看到我們的頁面上顯示了hello world!;localhost:8000/
- 那我們的第一個koa應用就已經運作起來了~。
- 讀取 HTML 頁面
- 在Koa中,我們如果要使用 HTML 應該怎麼做呢?
- 在原生nodejs或者express中,我們使用的是fs子產品,然後使用回調函數,一層套一層;
- 在Koa中,我們依舊使用fs子產品,但是,要對fs子產品的讀取檔案,進行進一步的封裝;
- 在目錄下,我們建立一個
,并随便寫一點東西;index.html
- 接着開始我們漸行漸遠的程式生涯;
-
const Koa = require('koa'); const fs = require('fs'); const app = new Koa(); function readFile(url) { return new Promise((resolve, reject) => { fs.readFile(url, (err,file) => { if(err) { reject(err); }else { resolve(file); } }) }) } app.use(async ctx => { ctx.response.type = 'html'; // 重點 ctx.body = await readFile('index.html'); }) app.listen(8000, () => { console.log('Koa server listen in http://localhost:8000/'); }) // 本文由郝晨光整理總結并編寫,未經允許禁止轉載。
- 可以看到,我們利用Promise将fs子產品的readFile方法進行了二次封裝,讀取了本地的index.html檔案
- 重點的地方我已經标出來了,為什麼要标重點呢?
- 因為在Koa中,ctx.body預設傳回的類型是
;而我們要傳回html檔案,是以應該在傳回檔案之前,設定好傳回類型,讓用戶端可以正确的接收;text/plain
- 然後我們使用
組合,在資料讀取完成之後,傳回讀取的檔案;async + await
- 最後,打開浏覽器,可以看到浏覽器上輸出的就是我們
檔案中寫的内容index.html
- 讀取靜态資源
- html檔案我們已經可以正确的讀取并傳回了,在我們的頁面上,避免不了會使用很多的靜态資源,例如css、js、image、font等等;這些靜态資源我們應該怎麼處理呢?
- 在這裡,我們使用一個Koa的中間件
koa-static
- 在指令提示符中執行
;npm install koa-static
- 接着書寫我們的程式
-
// ~~ 省略 ~~ const app = new Koa(); // 建立koa對象 const KoaStatic = require('koa-static'); // koa靜态檔案讀取 app.use(KoaStatic(__dirname)); // 使用中間件 // ~~ 省略 ~~
- 接着,打開我們的浏覽器,可以看到靜态資源已經可以正确的請求到了;
- 當然了,這個時候,我們可以把之前傳回html檔案時設定響應類型的一步省略掉了,因為
已經幫我們做了這件事了。koa-static
- 定義路由
- 所謂的路由,就是根據不同的請求路徑,響應不同的内容;
- 在Koa中,我們可以定義原生路由,也可以使用封裝好的路由中間件;
- 先看一下原生路由的寫法吧!
-
app.use(async ctx => { if(ctx.request.method==='GET') { // GET請求 switch (ctx.request.path) { case '/': // 比對預設路由 ctx.body = await readFile('index.html'); // 傳回index.html檔案 break; case '/about': // 比對/about路由 ctx.body = 'about路由頁面'; // 傳回about路由頁面 break; default: ctx.body = await readFile('index.html'); // 預設傳回index.html檔案 break; } }else if(ctx.request.method==='POST') { // POST請求 switch (ctx.request.path) { default: ctx.body = 'post 請求'; // 響應post請求 break; } } }); //本文由郝晨光整理總結并編寫,未經允許禁止轉載。
- 可以看到我上邊寫的,對請求方式以及路由進行了處理,在不同的請求方式,不同的請求路徑下,執行不同的操作,響應不同的結果。
- 路由中間件
- 我們使用原生方式定義路由,未免有些太過繁瑣,并且代碼不便于閱覽和維護;是以,建議使用中間件的方式進行路由配置。
- 我這裡使用的是 koa-router 這個中間件,當然,koa的路由中間件不是隻有這一種。
- 在指令提示符中安裝
;npm install koa-router
- 接着,進行我們的程式生涯;
-
// ~~ 省略 ~~ const KoaRouter = require('koa-router'); const router = new KoaRouter(); router.get('/',async ctx => { ctx.body = await readFile('index.html'); }); router.get('/about',async ctx=> { ctx.body = 'about路由頁面' }); router.post('/form',async ctx => { ctx.body = 'post請求' }); app.use(router.routes()); // ~~ 省略 ~~
- 打開頁面,并進行路由切換,可以看到沒有任何問題;
- 但是這樣所有的路由配置都寫在了index.js中,不利于維護,是以我們要将路由内容和公用方法提取出來。
- 代碼子產品化
- 首先對路由進行子產品化
- 在目錄下建立routes檔案夾,并建立home.js,用來存放關于首頁的一些路由配置。
- 将上邊的路由代碼單獨寫到home.js中,并抛出,如下:
- home.js
-
const KoaRouter = require('koa-router'); const router = new KoaRouter(); router.get('/',async ctx => { ctx.body = await readFile('index.html'); }); router.get('/about',async ctx=> { ctx.body = 'about 路由' }); router.post('/from',async ctx => { ctx.body = 'post請求' }); module.exports = router;
- index.js
-
const homeRouter = require('./routes/home'); app.use(KoaStatic(__dirname)); app.use(homeRouter.routes());
- 在index.js中,直接引入使用即可,和原有代碼沒有任何差別。
- 需要注意的是,對于功能中間件,例如
這種,我們應該放在路由中間件之前。koa-static
- 否則的話可能會出錯,在路由中不能正确的讀取靜态檔案等。
- 二級路由
- 已經學會了一級路由的定義,我們趁熱打鐵,學習二級路由
- 在routes目錄下,建立user.js,定義使用者路由
-
// user.js const KoaRouter = require('koa-router'); const router = new KoaRouter({ prefix: '/user' }); router.get('/',async ctx => { ctx.body = '使用者界面' }); router.get('/:id',async ctx => { ctx.body = '使用者詳情'+ctx.params.id; }); router.post('/login',async ctx => { ctx.body = '使用者注冊成功!' }); module.exports = router; // index.js // ~~ 省略 ~~ const userRouter = require('./routes/user'); // ~~ 省略 ~~ app.use(userRouter.routes());
- 接着,打開浏覽器,我們測試一下我們的二級路由,是沒有任何問題的。
- post請求處理
- 我們都知道,使用post請求一般用來送出表單,或者修改資料等。而post請求中的資料,存放在請求體中,使用原生Node的方法的話,我們需要監聽原生req對象上的data方法以及end方法,并将資料格式轉化。而express中,我們有一個
的插件可以使用。body-parser
- 那麼在Koa中,我們應該使用什麼呢?
- 在Koa中,我們使用
這個中間件來處理post請求的資料koa-bodyparser
- 在指令提示符安裝
;npm install koa-bodyparser
- 接着我們在index.js中使用這個中間件;
- 加入下面兩行代碼,與原先使用
中間件位置一樣即可;koa-static
-
const KoaBodyParser = require('koa-bodyparser'); app.use(KoaBodyParser());
- 使用了這個中間件,我們就可以在
中拿到post請求的資料了;ctx.request.body
-
router.post('/login',async ctx => { ctx.body = ctx.request.body; });
- 接着改造一下我們的html檔案,送出一下表單;
-
<form action="/user/login" method="post"> 姓名:<input type="text" name="name" autocomplete="off"/> <br> <input type="radio" name="sex" value="男"/>男 <input type="radio" name="sex" value="女"/>女 <input type="submit" value="送出form"> </form>
- 可以看到,表單送出正常,我們也正确的拿到了表單送出的資料。
- 我們都知道,使用post請求一般用來送出表單,或者修改資料等。而post請求中的資料,存放在請求體中,使用原生Node的方法的話,我們需要監聽原生req對象上的data方法以及end方法,并将資料格式轉化。而express中,我們有一個
- CORS跨域配置
- 在我們現在的伺服器生涯中,避免不了要使用跨域,特别是CORS跨域。
- 那麼,在Koa中,我們應該怎麼去設定跨域呢?
- 我們可以使用中間件,也可以使用原生方法手寫。
- 老規矩,先看原生方法。
-
app.use(async (ctx, next) => { // 允許來自所有域名請求 ctx.set("Access-Control-Allow-Origin", "*"); // 這樣就能隻允許 http://localhost:8080 這個域名的請求了 // ctx.set("Access-Control-Allow-Origin", "http://localhost:8080"); // 設定所允許的HTTP請求方法 ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE"); // 字段是必需的。它也是一個逗号分隔的字元串,表明伺服器支援的所有頭資訊字段. ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type"); // 伺服器收到請求以後,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以後,确認允許跨源請求,就可以做出回應。 // Content-Type表示具體請求中的媒體類型資訊 ctx.set("Content-Type", "application/json;charset=utf-8"); // 該字段可選。它的值是一個布爾值,表示是否允許發送Cookie。預設情況下,Cookie不包括在CORS請求之中。 // 當設定成允許請求攜帶cookie時,需要保證"Access-Control-Allow-Origin"是伺服器有的域名,而不能是"*"; ctx.set("Access-Control-Allow-Credentials", true); // 該字段可選,用來指定本次預檢請求的有效期,機關為秒。 // 當請求方法是PUT或DELETE等特殊方法或者Content-Type字段的類型是application/json時,伺服器會提前發送一次請求進行驗證 // 下面的的設定隻本次驗證的有效時間,即在該時間段内服務端可以不用進行驗證 ctx.set("Access-Control-Max-Age", 300); /* CORS請求時,XMLHttpRequest對象的getResponseHeader()方法隻能拿到6個基本字段: Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。 */ // 需要擷取其他字段時,使用Access-Control-Expose-Headers, // getResponseHeader('myData')可以傳回我們所需的值 ctx.set("Access-Control-Expose-Headers", "myData"); await next(); })
- 原生方法來自:node.js 應答跨域請求實作(以koa2-cors為例)
- 接着我們來看中間件吧!
- 在Koa2中,我們使用
這個中間件來設定CORS跨域請求。koa2-cors
- 先安裝
;npm install koa2-cors
- 接着,在index.js中使用。
-
const KoaCors = require('koa2-cors'); // ~~ 省略 ~~ // CORS跨域 app.use(KoaCors({ origin: ctx => { return ctx.request.header.origin; }, exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'], maxAge: 5, credentials: true, allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'], allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'Origin'], })); //本文由郝晨光整理總結并編寫,未經允許禁止轉載。
- 特别需要注意的是,在
使用app.use()
的時候,我們應該把它放在别的中間件的前邊,特别是靜态資源處理和路由進行中間件的前邊,這樣可以保證我們在跨域請求靜态資源的時候不會出問題。koa2-cors
如果本文對您有幫助,可以看看本人的其他文章:
前端常見面試題(十三)@郝晨光
前端常見面試題(十二)@郝晨光
前端常見面試題(十一)@郝晨光
結言
感謝您的查閱,本文由郝晨光整理并總結,代碼備援或者有錯誤的地方望不吝賜教;菜鳥一枚,請多關照