Matsu

Nothing more than curiosity

Route和Middleware

Have no fear of perfection - you’ll never reach it.
― Salvador Dali

昨天第一次介紹Express後端框架,簡單說明如何在Node當中使用以及它的主要功用,另外也提到Middleware的基本概念,今天就來更有效利用Express的超能力吧!

  • Express Middlewares
    • Express Routes
  • 結語

Express Middlewares

比起中介軟體,我還是習慣直接唸英文,或者想成是橋接器都比中介軟體念起來順手多,在概念上也更容易成形。首先我們建立新的檔案app.js,上一篇文章都有提到,忘記的話可以回上一篇回顧。

1
2
3
4
5
6
7
8
9
const express = require('express');
// Express處理檔案路徑的核心模組
const path = require('path');
const app = express();

// 內建函數用於提供靜態檔案
app.use(express.static(path.join(__dirname, 'public')));

app.listen(3000);

這裡特別注意的地方是app.use這段程式碼當中,我們透過Express內建函數static()提供靜態檔案,而路徑的設定方式則是使用核心模組path處理,主要用途在於可以匯入CSS檔案給HTML。

至於path.join函數則可以根據作業系統(OS)將檔案串接起來,舉例來說:
假設路徑路徑為public -> css -> index.css,則會對應如下
Windows: \public\css\index.css
Linux: /public/css/index.css
不一樣的地方在於預設路徑的寫法,現在我們不用去煩惱不同作業系統間的寫法啦!此外,若沒有透過path模組,在Express應用程式當中必須使用絕對路徑。

基本上,我們只需要安裝需要的套件就可以執行功能,省去相當多繁瑣的細節。

Express Routes

最後想特別提及的部分是關於Routes的處理,在Express當中我們一樣是經由Middleware的概念去處理Routes。因為Routes大部分都和網頁URL有關,固然app.use本身就可以拿來處理Routes。

可以根據不同請求方式,改寫Middleware啟動的時機,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app.get((req, res, next) => {
console.log('Mioali is passed!');
next();
});

app.post((req, res, next) => {
console.log('Hsinchu is passed!');
next();
});

app.put((req, res, next) => {
console.log('Taoyuan is passed!');
next();
});

app.delete((req, res, next) => {
console.log('Taipei is arrived!');
});

Express Middleware的get, post, put, delete剛好能夠各自對應到http的不同傳輸方式。現在,我們實際上來操作看看Routes。

首先新增routes的資料夾,接著分別建立welcome.js和users.js。程式碼分別如下:
welcome.js

1
2
3
4
5
6
7
8
9
10
11
const express = require('express');
const path = require('path');
// 使用Express內建函數來指向切割後的Routes
const router = express.Router();

router.get('/', (req, res, next) => {
res.sendFile(path.join(__dirname, '..', 'views', 'welcome.html'));
});

// 匯出檔案的Routes
module.exports = router;

users.js

1
2
3
4
5
6
7
8
9
10
const express = require('express');
const path = require('path');

const router = express.Router();

router.get('/users', (req, res, next) => {
res.sendFile(path.join(__dirname, '..', 'views', 'users.html'));
});

module.exports = router;

我們將原本寫在app.js處理Routes的Middleware切割到其他的檔案當中,透過Express提供的Router函數,我們因此可以指向相同的URL。在GET發出請求後,我們回傳res.sendFile()函數,它的功能如同函數名稱一樣,用途在於傳送檔案給使用者,聽起來似乎很奇怪,但有時候我們必須在開發者和使用者角度不斷切換,才有助於理解程式開發。在res.sendFile當中,一樣需要透過path模組去調整我們要存取的路徑,以welcome.js檔案當中,get最後會回傳的檔案會指向類似C:\Users\user\tutorial\views\users.html的檔案路徑。

最後因為我們指向兩個html檔案,自然就需要建立它們,因此先建立views資料夾後再各自建立welcome.html和users.html,內容簡簡單單如下即可:
welcome.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome</title>
</head>
<body>
<h2>Welcome</h2>
</body>
</html>

users.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Users</title>
</head>
<body>
<ul>
<li>Max</li>
<li>Matsu</li>
<li>Complex</li>
</ul>
</body>
</html>

最後實際啟動npm start後,我們就能夠順利看到welcome.html和users.html的畫面囉!

localhost:3000的畫面
welcome
localhost:3000/users的畫面
users

此外若想要套用CSS,只需要將.css檔案,直接寫在HTML檔案各自的head即可。


結語

今天再Review過一次Express的一些基本概念和設定,其實完全算不上真正的開工哈哈哈。不過沒關係,一步一腳印,至少今天已經搞懂在Express切割Routes以及如何在HTML檔案當中參照到專案中的CSS檔案,很多隱藏在背後的細節其實需要深思過後才能夠漸漸明瞭運作,若只是單純的套用別人已經寫好的API,我想久而久之會失去自己的思考能力吧!

「物事の本質を考えろう!」と阿部寛がこう言った。真正重要的是思考事物的本質。

繼續苦等東大特訓班2…

Express介紹與基本概念

I have not failed. I havve just found 10,000 ways that won't work.
― Thomas A. Edison

Express是伺服器或稱作後端框架,最近才認知到Express有別於React、Angular和Vue,說起來也算是天大誤會,好在還沒弄成笑話。今天一起來看看這個框架可以給予我們什麼樣的能力吧!

  • Express介紹
    • Express使用
  • 結語

Express介紹

誠如先前所說,Express是伺服器框架其一 ,其餘的類似的框架有Laravel、Flask等等。Express主要的用途在於協助Node去打造Web應用程式的框架和API,大幅減輕開發的負擔。以往在Express開發前,處理API和Web應用程式,需要針對網頁的請求與回應逐一處理,但現在只需要透過Express就可以達到一樣的結果。Express扮演的角色就像是遙控器一樣,開啟電視只需要按下開機關機鍵,若沒有遙控器的話則必須直接按電視螢幕本身的開關,但結果上來看是一樣的。

簡言之,Express就是Node處理網站的遙控器!

Express使用

在使用Express前,我們需要先認識中介軟體(Middleware),中介軟體本身可以想成火車,假設火車從台中北上開往台北,這一路上必須經過苗栗、新竹、桃園、新北幾個城市,而中介軟體則是城市與城市間的溝通方式,並且溝通結果會影響到接下來的行程。

首先在一個資料夾內建立app.js的檔案,基本上檔案名不會影響,不過一般都會命名為app或index兩者其一。建立完成後,切換到資料夾內執行npm init設定專案檔。一路按Enter到底將專案檔設為預設即可。大致上會看起來像下方圖片的路徑。

pic1

接著便於開發我們分別下載nodemon套件以及必備的express框架,執行以下指令:
npm install nodemon --save-dev
npm install express --save
第一個套件的主要功能是用於自動重啟伺服器,而不用手動一直重新開啟伺服器,安裝完成後,將package.json檔案寫入一個新的腳本檔案,就可以無痛使用nodemon從此不再手殘,如下:

script

接著回到app.js檔案,這裡我們將Express套件引入檔案中:

1
2
3
4
5
6
7
8
9
10
// 匯入Express框架套件
const express = require('express');

// 建立Express應用程式 -> 物件具有許多Function和Settings
const app = app.express();

// 等同於
// const server = http.createServer();
// server.listen(3000);
app.listen(3000);

這段程式碼當中,我們建立Express的應用程式,透過它我們可以使用Express提供的各種Middleware來處理網站,從最後一行app.listen()可以看出Express已經將http核心模組打包起來。
const server = http.createServer()
server.listen(3000)

app.listen(3000)

接下來我們將app.js稍微修改一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const express = require('express');
const app = express();

app.use((req, res, next) => {
console.log('Set off at Taichung City!');
// 告訴Middleware可以前往下一個Middleware!
next();
});

app.use((req, res, next) => {
console.log('Mioali is passed!');
next();
});

app.use((req, res, next) => {
console.log('Hsinchu is passed!');
next();
});

app.use((req, res, next) => {
console.log('Taoyuan is passed!');
next();
});

app.use((req, res, next) => {
console.log('New-Taipei is passed!');
next();
});

app.use((req, res, next) => {
console.log('Taipei is arrived!');
});

app.listen(3000);

透過next()可以調動middleware不斷前往下一個middleware直到發生狀況或是抵達終點。
最後npm start啟動伺服器後就可以發現我們的中介軟體確實可以一路由台中抵達台北!


結語

今天簡單講解Middleware的概念,其實Middleware可以細分成不同層級的Middleware,就留到下次有機會再來討論。希望不會有人再跟我一樣傻傻分不清楚前後端框架。

最近真的是想到什麼就寫什麼,完全沒有規劃,不過總是想的比較多。這系列文章估計也會寫好幾篇,希望能和React結合在一起XD。

Node介紹與基本概念

Never memorize something that you can look up.
― Albert Einstein

在愛因斯坦的身上,總能夠得到發人省思的一番話,背後一席大道理,目前的我還不太能夠理解。

今天是新系列文想來聊聊關於Node的故事。經過React一番洗禮兼摧殘,終於成為一名React菜鳥,不過可能有人會想連前端都還沒搞專精,就來接觸後端是不是哪裡搞錯? 但是我個人的想法是,唯有學習前端與後端後,才有可能真正專精前端或後端,總不可能後端完全不懂,就串起各種API,就算真的串起來,估計後續也有很多延伸問題,我想至少這是我目前的看法,那,我們趕緊進入正題!

  • Node介紹
    • Node核心模組
  • 結語

Node介紹

最近才突然頓悟前後端的區別,以前總覺得自己好像有一定的認知,才發現認知完全是錯誤的。大學期間曾經透過Express和Pug樣板去製作簡單的應用程式,也曾經使用過Flask搭配Pug去處理簡單的網頁,但在學習完React的這陣子後,我才突然想到為何學習前端都沒有聽到Express和Flask,最近發現原來這兩個框架是屬於後端框架! 完全跟React八竿子打不著,話是這麼說,但究竟差異在何處?

隨著JS的強勢,2009年源自瑞安·達爾的Node逐漸竄起,甚至可以說是當前最熱門的後端程式語言其一,透過Google開發的V8引擎上而得以運行的伺服器端環境。從此脫離Apache的手掌心,JS更因此從前端語言一躍而出。

Node模組絕大多數以JS撰寫,因此JS本身就能夠自成一套完整的系統,感覺就像是3C產品都用Apple的感覺,一體感所帶來的快感實在美好。簡言之,沒有不用Node的道理!

Node核心模組

基本上Node的核心模組有http、fs、process等等,數不勝數,不過說這麼多還是直接來實際看看Node的使用方式吧!

面對不同面向會有不同的模組可以使用,不管是要處理HTTP的回應、檔案的讀取寫入、執行緒的控制,甚至是加密演算法都能夠透過Node去實現。

今天我們來透過運行伺服器去理解Node的使用,網路一般的運作方式是透過以下的方式:
使用者請求 -> 瀏覽器請求 -> 伺服器回應 -> 瀏覽器回應 -> 使用者讀取
大致上可以上述粗略的方式去理解瀏覽網頁的經過,接著我們嘗試透過Node去建立自己的伺服器。

1
2
3
4
5
6
7
8
9
10
// 匯入處理http的核心模組
const http = require('http');

// 建立http協議的伺服器,回傳Server物件
const server = http.createServer();

console.log(server);

// 伺服器監聽埠(PORT)3000的所有資訊
server.listen(3000);

先前我們提到Node有相當多的核心模組,http即是用來處理相關請求、回應的模組。在匯入模組後,建立http協議的伺服器給server,最後監聽server在3000埠口的所有資訊。一種簡單的想法是,server就是在稱作3000路口的監視錄影機,可以處理所有經過路口的車輛與行人。

為了處理經過車輛與行人資訊,我們必須告訴Server車輛與行人要去哪裡,要以何種方式經過路口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const http = require('http');

// req: 接收的資訊(車輛與行人) res(監視器偵測的回應)
const server = http.createServer((req, res) => {
// 擷取req請求的網址(URL)
const url = req.url;
// 擷取req請求的方式(GET, POST, PUT, DELETE)
const method = req.method;

console.log(url);
console.log(method);
});

server.listen(3000);

在Server物件當中有相當龐大的預設資訊,但最主要的則是伺服器接收的訊息與給予的回應,在上方我們拆解出請求伺服器的網址,預設的情況下是/,而預設的請求方法則是GET。但網頁不僅止於此,我們同樣可以給予伺服器其他的資訊:

1
2
3
4
5
6
7
8
9
10
11
12
13
const http = require('http');

const server = http.createServer((req, res) => {
// 寫入請求後回應的資訊
res.write('<html>');
res.write('<body><p>Welcome to Node.js</p></body>');
res.write('</html>');

// 結束接收回應
res.end();
})

server.listen(3000);

我們直接在回應的部分寫入HTML格式的程式碼,實際執行後就能夠見到Welcome to Node.js的字樣出現在localhost:3000的網頁當中,不僅告訴Server所有資訊的流向與方式,我們同樣可以掌握車輛與行人的數量。這就是最簡單,最基本地使用Node核心模組http的方式。

http

我們再看一個範例:

1
2
3
4
5
6
7
8
9
10
11
12
const http = require('http');
// 匯入處理檔案的核心模組
const fs = require('fs');

const server = http.createServer((req, res) => {
// 同步寫入檔案 -> writeFileSync('檔案名稱', '檔案內容')
fs.writeFileSync('message.txt', 'Node is powerful');
res.write('<body><h2>You are writting file!</h2></body>');
res.end();
});

server.listen(3000);

匯入fs的模組後,我們呼叫fs模組內writeFileSync函數,這個函數主要用於同步處理檔案寫入,不過這邊暫且不討論同步非同步的問題。實際執行後,會在同樣的資料夾下建立message.txt的檔案,裡面確實寫入Node is powerful的字樣。

fs


結語

我曾用Expres和Flask各自寫些小網頁,結果才發現是一大烏龍,就覺得自己很蠢很好笑。不過今天又重新學習Node,希望我在未來不久內可以了解更多關於後端的運作,特別是API的部分,該找時間寫篇關於API的文章,因為最近用React串接其他公司的API實在慘不忍睹,只能說自己還要多加油。

希望這篇能對剛入門Node的讀者們有點幫助,就讓我們一起成長吧!!

Closures閉包入門

Faith is about doing. You are how you act, not just how you believe.
― Mitch Albom, Have a Little Faith: a True Story

最近在用React寫組件時,突然想到閉包(Closures)的使用。具體原因已經有點想不太起來,只記得當天研究閉包一段時間。上一篇文章已經是五月底的事情,時間過的很快,不知不覺也在公司的案子裡埋頭三天多,不過進度很緩慢,心情也是有點忐忑,不確定自己能不能好好完成,也不確定一路走來是對是錯。回歸正題,今天就來探究一下閉包的使用吧!

  • 何謂閉包
    • 基本閉包運用
    • 實際閉包運用
  • 結語

何謂閉包

若沒有特別研究,可能會以為閉包是什麼神奇的東西,它很神奇卻也不神奇。在撰寫JS的日子中,我們很常就會運用到閉包的概念。

先來看看MDN關於閉包的敘述:
A closure is the combination of a function bundled together with references to its surrounding state.

個人而言,第一次讀到這段話的時候似懂非懂,但唯一可以確信的是,某函數可以參照到它周圍的狀態,至於這個周圍如何判斷就需要透過實際範例來討論,這就好像讀一篇文章,我們必須透過上下文來判斷一句話真正的意圖,程式語言當然也不例外。

基本閉包運用

首先來看一個簡單的閉包範例:

1
2
3
4
5
6
7
8
9
let findNinja = 'ninja';

const trackNinja = () => {
if(findNinja === 'ninja'){
console.log('We find the ninja!');
}
};

trackNinja();

上述是很常見的函數,我們單純宣告一個全域變數以及函數,但若依照函數可以參照周圍的狀態,則其實我們正在使用閉包,卻完全沒有發現。不過僅僅是這樣無法理解閉包的奧義,接著看:

1
2
3
4
5
6
7
8
9
10
const findNinja = () => {
let ninja = 'Matsu';
const catchNinja = () => {
console.log(ninja);
}
return catchNinja;
}

let realNinja = findNinja();
realNinja();

這次的範例特別的地方在於return catchNinja會先在catchNinja函數執行前先執行,因此若真是如此return過後findNinja就應該找不到Matsu忍者,同樣也無法列印出Ninja的名稱,但是在實際執行後,卻可以找到Matsu忍者,這就是閉包在展現身手的時刻。可是似懂非懂? 我們一步一步解釋,首先當realNinja被指派findNinja函數後參照到catchNinja函數,catchNinja則會建立閉包產生一個泡泡去包住範圍內的變數,而可參照的範圍則是findNinja內的所有變數。我們再看另外一個基本範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let ninja = 'matsu';
let ninja_hide;

const findMoreNinja = () => {
let realNinja = 'Matsu';

const moreNinja = () => {
if(ninja === 'matsu') {
console.log('I found ninja matsu!');
}
if(realNinja === 'Matsu') {
console.log(realNinja, 'I found real ninja Matsu!');
}
}
ninja_hide = moreNinja;
}

findMoreNinja();
ninja_hide();

先後執行findMoreNinja和ninja_hide後,可以發現ninja_hide參照到findMoreNinja裡頭的moreNinja函數,照理說在findMoreNinja結束後,就無法再取得moreNinja的內容,但我們卻能夠取得,換句話說,ninja_hide()本身建立起新的閉包!


結語

今天簡簡單單講解閉包的存在,其實閉包可以更加的複雜,只是透過深入理解日常閉包存在的方式,在撰寫JS的過程中便能多留意這個概念,往後在撰寫網站或應用程式時,就更夠理解JS背後運行的方式。學習框架更需要理解這些基本的概念,當開始談起生態圈與優化時,沒有JS基礎的底子,很難去真正釐清程式寫不好的原因在哪裡。

我覺得自己的基礎不是很好,希望能夠透過一篇又一篇的文章,一天一天的進步。今天為了透過Git Pages展示UI給公司的同事了解狀況,研究兩個帳號要如何在本地端切換,數小時過後才終於釐清SSH的使用方式,說真的都能夠再寫成另外一篇文章,很有趣的是,在五月初,剛架好部落格時就遇到同樣的問題,當初是打算要透過另外一個Github帳號去重新實作架設Hexo卻在SSH上面碰壁,時隔一個月,在今天才真的理解SSH的使用方法…,突然在IT邦看見其他大神的文章,才頓悟他們的文章在寫什麼,只能說自己真的還太菜!

希望今天這篇文章能夠對想初步理解閉包的讀者有幫助,謝謝。

玩轉Typescript

Work without love is slavery.
― Mother Teresa

沒有愛的工作是奴役,但要怎麼去確認自己的工作是不是有愛?

本篇內容主要源自於上一篇React系列-玩轉Typescript待辦清單(上)。

本來打算將內容都寫在同一篇文章,但還是有點勉強,因此才分為上、下篇,在上一篇文章我們定義完類別和ContextAPI後,這裡我們只需要將它們運用在組件上就大功告成了!

  • 實作Todolist
  • 結語

實作Todolist

首先處理第一個組件Todos.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// In "Todos.tsx"
import { useContext } from 'react';

import { TodosContext } from '../store/todos-context';
import TodoItem from './TodoItem';

const Todos: React.FC = () => {
// 使用ContextAPI
const todosCtx = useContext(TodosContext);

return (
<div>
<ul>
{todosCtx.todos.map((todo) => (
<TodoItem
key={todo.id}
name={todo.name}
// null告知this不需要參照,todo.id則為第一個參數傳入
onDeleteTodo={todosCtx.deleteTodo.bind(null, todo.id)}
/>
))}
</ul>
</div>
);
};

export default Todos;

Todos當中使用到TodoItem組件,當然就需要再為它建一個組件囉!

1
2
3
4
5
6
7
8
9
10
// In "TodoItem.tsx"
import React from 'react';

const TodoItem: React.FC<{name: string; onDeleteTodo: () => void}> = (props) => {
return (
<li onClick={props.onDeleteTodo}>{props.name}</li>
)
};

export default TodoItem;

注意到在React.FC後方透過使用Generic去明確定義變數name和函數onDeleteTodo,因為對於TodoItem組件來說,這兩者都是陌生人。最後,因為我們需要新增項目,自然也就需要新增清單的組件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// In "NewTodo.tsx"
import React from 'react';
import { useRef, useContext } from 'react';
import { TodosContext } from '../store/todos-context';

const NewTodo: React.FC = () => {
const todosCtx = useContext(TodosContext);

// HTMLInputElement代表DOM的元素類別
const inputRef = useRef<HTMLInputElement>(null);

const submitHandler = (event: React.FormEvent) => {
event.preventDefault();

const todoName = inputRef.current!.value;

if (todoName.trim().length === 0) {
return;
}

todosCtx.addTodo(todoName);
};
return (
<form onSubmit={submitHandler}>
<label htmlFor='name'>Todo Name</label>
<input type='text' id='name' ref={inputRef} />
<button>Add Todo</button>
</form>
);
};

export default NewTodo;

透過使用useRef Hook,可以簡單存取特定欄位的值。唯一需要注意的是HTMLInputElement,在使用Hooks也必須告訴TS相關的型別定義。其餘的都在React系列-useRef概念篇當中有詳細的了解,對於useRef使用有疑問可以參考那篇文章。

最後僅需要將App.tsx做點小修改,就可以成功執行我們的代辦清單囉!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import NewTodo from './components/NewTodo';
import Todos from './components/Todos';
import TodosContextProvider from './store/todos-context';


const App = () => {
return (
<TodosContextProvider >
<NewTodo/>
<Todos/>
</TodosContextProvider>
);
}

export default App;

稍微添加CSS樣式在其中後,實際執行程式後,大致上會如下圖一樣:
pic

給自己拍拍手! 我想是因為對於TS理解的不夠深,個人覺得在使用起來沒有太大的差別,但確實對於明確的去定義每一個細節要如何使用都非常的清楚明瞭,這應該是最能體會TS的威力的一點! 當然還有很多值得我們去探討的,就交給明天的自己吧!(飄~)


結語

結束五月,不知不覺在這一個月當中有很多成長,不管是學習還是找工作,可以說壓力不少,但卻覺得意外的有趣,看著自己一篇又一篇的文章。有時候都會想為什麼要這麼麻煩,但卻又因為學習的空虛而感到不安,對於我自己來說,忘記以前讀過的書、學會的曲子、寫過的字才是最可怕的,因為那似乎代表自己白活那麼一段時光,一想到此,身體自己就動了起來。

很多時候想的太多,真正需要的只是放手一搏而已,這段路途定然不容易,卻會是朝著理想的路上。

0%