Arthur Blog
訪問人數:
Arthur Blog
首頁
技術文章
一般文章
關於
搜尋文章
文章列表
從零開始的React (2) - tsx(html)和css
## 前言 上篇已經建立好專案了,本篇會教你常用的**html**和**css**,畢竟前端的基本功就是切版。 ## 修改tsx和css <font color="red">p.s無論是刪除或者是修改,在改完後記得儲存 ctrl + s,這樣你的畫面才會跟著改變</font> 1. 首先把 `index.css` 和 `App.css` 裡的東西全部刪除。 2. 開啟 `index.css`,將以下css加入: ```css /*可以讓元素的 width 和 height 包含內邊距和邊框,方便設計*/ *, *::before, *::after { box-sizing: border-box; } ``` 3. 開啟 `App.tsx`,將綠框和紅框的部分刪除,如下圖:  4. 接著你會得到下圖,紅框的部分就是**React**框架中**html**的部分:  ## html 簡單來說html是呈現資料(文字)、圖片、影片等等的地方。 修改 `App.tsx`: ```tsx import { useState } from "react"; import "./App.css"; function App() { const [count, setCount] = useState(0); return ( <> <div>Hello World!</div> <div>Hello World!</div> </> ); } export default App; ``` 開啟[localhost:5173/](http://localhost:5173),你就會看到我們上面加入的`Hello World!`,如圖:  ## className 在html中的class,在react裡會寫做className,甚麼是className呢? 就是給你html裡的元素一個類別,讓你的css可以根據各個類別的名稱去做設定,我們來給程式碼設個className,如下: ```tsx import { useState } from "react"; import "./App.css"; function App() { const [count, setCount] = useState(0); return ( <> <div className="item1">Hello World!</div> <div className="item2">Hello World!</div> </> ); } export default App; ``` ## css css就是用來設定html內元素的字體顏色、大小、背景顏色、邊框等等,下面會介紹一些常用的css並解釋其用途。 1. **color**: 文字的顏色。 2. **font-size**: 文字的大小。 3. **font-weight**: 文字的粗度。 4. **padding**: 元素內的間距。 5. **margin**: 元素外的間距。 6. **background-color**: 背景顏色。 7. **border**: 邊框。 8. **border-radius**: 圓角。 9. **width**: 元素的寬度。 10. **height**: 元素的高度。 11. **flex**: 排版。 接著讓我們嘗試一下,修改 `App.css`: ### color 和 font size 和 font weight ```css /*沒有. 就是針對元素去做設置*/ div { font-weight: 600; } /*有. 就是針對該className去做設置*/ .item1 { color: red; font-size: 24px; } .item2 { color: blue; font-size: 16px; } ``` 會得到下圖:  ### padding 和 margin 和 background color padding、margin這兩個有很多種寫法,這邊以padding來舉例: 1. `padding: 10px;`,這樣代表上下左右內間距都為10px。 2. `padding: 10px 0;`,第一個數表示上下,第二個數表示左右。 3. `padding: 10px 0 10px 0;`,這四個數分別代表上、右、下、左。 4. `padding-{方向}: 10px;`: 也可以單獨針對所需要的方向來做設置。 接著修改 `App.css` 試試看,下面兩種寫法: ```css .item1 { padding-bottom: 10px; } ``` ```css .item1 { margin-bottom: 10px; } ``` 會發現無論是哪種程式碼,乍看之下都會一樣,如下圖:  這時候我們將 `.item1` 加上background-color,這樣就可以很清楚的知道padding、margin的差別了,如下: ```css .item1 { padding-bottom: 10px; background-color: yellow; } ```  ```css .item1 { margin-bottom: 10px; background-color: yellow; } ```  ### border 和 border-radius 和 width 和 height 馬上嘗試一下,修改 `App.css`: ```css .item1 { width: 300px; height: 80px; border-radius: 8px; border: 1px solid red; } .item2 { width: 150px; height: 150px; border-radius: 50%; border: 1px solid blue; } ``` 這裡看一下 `.item2` 只要width、height相等並讓 `border-radius: 50%` 這樣就會得到一個圓了,如圖:  ### flex 這是用來布局版面的常用的有: 1. `display: flex;`: 賦予該元素flex屬性。 2. `flex-direction`: 排版方向可以設置為row(橫的)或是column(直的)。 3. `justify-content`: 以排版方向畫一條線當作主軸,並以主軸為基準來進行排版,如下圖:  4. `align-items`: 以主軸的90度來畫一條線當作交錯軸,並以交錯軸為基準來進行排版,如下圖:  讓我們實際試一下,修改 `App.css`: ```css .item1 { width: 300px; height: 80px; border-radius: 8px; border: 1px solid red; display: flex; flex-direction: column; align-items: end; justify-content: flex-end; } .item2 { width: 150px; height: 150px; border-radius: 50%; border: 1px solid blue; display: flex; flex-direction: row; align-items: center; justify-content: center; } ``` 就可以得到下圖:  ## 最後 今天的介紹就到這裡,html、css在這篇文章中只是稍微帶過而已,讀者可以上網搜尋專門在教html和css的文章,下次見。
2025-02-10
React
Frontend
從零開始的React (1) - 環境和架構介紹篇
## 前言 最近有一個朋友想加入前端的行列,但他完全沒有接觸過程式(HTML、CSS 都不懂的那種)。所以我想做一個麻瓜都能懂得React系列,此系列會盡量以最詳細的方式來做介紹,其中會包含HTML、CSS、Typescript等等。 ## 環境建置 首先我們需要下載兩樣東西: 1. [Nodejs](https://nodejs.org/en),打開連結後點擊紅框區域下載,並安裝。  2. [VsCode](https://code.visualstudio.com/),打開連結後點擊紅框區域下載,並安裝。  兩者在安裝過程中不用修改任何東西一直點 **Next**(**下一步**) 即可。 ## 確認Nodsjs是否安裝成功 安裝完成後,讓我們確定**Nodejs**已經安裝成功,打開命令提示字元(CMD)並輸入: 如何開啟命令提示字元(CMD): **Windows + Q** 並輸入**命令提示字元**或**CMD** ```console node -v ``` <font color="red">輸入完</font> `node -v` <font color="red">後請按Enter</font> 出現和下圖一樣表示你已經安裝成功(版本號不同沒有關係)。  ## 創建React專案 打開命令提示字元(CMD)並輸入: ```console npm create vite@latest my-react-app --template react ``` `my-react-app` 是你專案的名稱可以自行定義,我的專案名是 `react-demo`。 選擇React,如下圖:  接著選擇Typescript,如下圖:  ## 使用VsCode開啟專案 開啟剛剛下載的**VsCode**,依照下圖開啟專案資料夾,資料夾路徑通常為(C:\Users\電腦的使用者名稱\專案名稱):  看到下圖表示專案已開啟  ## 下載專案所需套件 接著我們需要下載該專案所需的套件,首先按照下圖新增一個**Terminal**:  接著輸入: ```console npm i ``` 然後等待一些時間,出現和紅框部分一樣後表示下載成功  ## 讓該專案在網頁上呈現 要讓專案出現在網頁上,請在**Terminal**輸入: ```console npm run dev ``` 等待一些時間,出現和紅框部分一樣後表示成功  打開網址: 預設網址通常為[localhost:5173/](http://localhost:5173),如下圖  ## 專案目錄結構  這裡簡單介紹一下目錄結構,看不懂沒關係之後有使用再回來看即可: 1. **資料夾node_modules**: 上面透過`npm i`下載後的套件都會放在這裡。 2. **資料夾public**: 用來放靜態檔案的地方,像是網站的圖標(icon)等等。 3. **資料夾assets**: 用來存放圖片、語音、影片等等。 4. **.css**: 以此為結尾的檔案是用來寫**css**的地方。 5. **.tsx**: 以此為結尾的檔案是用來寫**Typescript**和**Html**的地方。 6. **package.json**: 管理該專案所需使用到的套件以及配置一些選項的地方。 7. **vite.config.ts**: 定義和調整**Vite**構建工具的地方。 ## 最後 這樣就可以準備開始你的前端之旅了,下篇會學到如何更改 `localhost:5173` 的頁面,下次見。
2025-02-08
React
Frontend
PWA進階 - cache機制
## 前言 前一陣子為了要讓Web像是App一樣可以接收推播,於是在網頁新增了PWA[(原文在此)](firebase-fcm),在上篇文章中PWA的部分只是簡單的帶過,所以今天特地寫了一個主題來介紹PWA及cache機制。 ## Service worker 代碼 找到public下的 `sw.js` 並修改 ```javascript const CACHE_NAME = "v1"; self.addEventListener("install", (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => { return cache.addAll(["/", "/index.html", "/logo192.png"]); }) ); self.skipWaiting(); }); self.addEventListener("activate", (event) => { event.waitUntil( caches .keys() .then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) => { if (cacheName !== CACHE_NAME) { return caches.delete(cacheName); } }) ); }) .then(() => { return self.clients.claim(); }) ); }); self.addEventListener("fetch", (event) => { event.respondWith( caches.match(event.request).then((response) => { if (response) { return response; } return fetch(event.request).then((networkResponse) => { return caches.open(CACHE_NAME).then((cache) => { if ( event.request.method === "GET" && event.request.url.includes("/static") ) { cache.put(event.request, networkResponse.clone()); } return networkResponse; }); }); }) ); }); ``` ## 代碼介紹 ### CACHE_NAME 版本名稱,用來判斷service worker版本是否更新。 ### install 在此會載入新的service worker並且將你設定的靜態檔給cache住,cache後就可以在離線的情況下運行,就是像真的App一樣。 ### skipWaiting 在service worker的機制中,會等到舊的service worker終止後才會執行新的service worker,但舊的service worker只有在網頁被關掉時侯後或是重新整理才會終止,所以這時候就可以透過 `skipWaiting()` 直接讓新的service worker開始運行。 ### activate 舊的service worker被卸載後,新的service worker就會觸發activate,所以我們可以透過 `CACHE_NAME` 這個變數來判斷版本是否有改變,若版本改變了就把舊有的cache給清除掉。 ### claim `claim()` 此方法是為了service worker立即生效而不用重新加載。 這邊讀者可能會有疑問,明明已經透過 `skipWaiting()` 讓新的service worker開始運行了為甚麼還需要 `claim()`。 這裡舉個例子: 假設你在第一個分頁開起網頁後service worker的版本是 `v1`,這時候網站更新了 `v2` 版本,接著你在第二個分頁開啟的網頁service worker版本會就會是 `v2`,這就會導致你開了同樣的頁面但功能卻有所不同,此時就可以使用 `claim()` 該方法,將第一個分頁的service worker變成 `v2` 版,而不用關閉該網頁或重新整理。 ### fetch 在此會做兩件事: 1. 判斷網頁要拿得資源是cache的還是線上的。 2. 是否要將新獲取的資源放入cache中。 這邊讀者可能會提問,為何不把需要cache的資源在install的時候就設定好。 <br>  <br> 看上圖可以知道我載入了名為 `main.5fd0f9d6.js` 的檔案,但 `5fd0f9d6` 是一個hash值,在每次build專案時都會不一樣,所以我們無法在install階段就將其寫入至cache。 接下來需要判斷要cache的資源是否為 `method=GET 且 url包含/static`,原因在於: 1. cache只能存放`GET`(如果硬要將POST的資料放入cache也是可以的,但這裡先不介紹)。 2. `/static` 是我們編譯完成的靜態檔案的路徑,如果不加此條件那麼所有的 `GET` 都會被加入cache之中 ,會導致cache存放一堆沒用的檔案,例如: 地圖套件的圖資就是 `GET` 的png檔案,這類型的檔案是不需要存在cache中的。 ## service worker運作流程 當每開啟一次網頁時就會按下面的流程執行: `開啟網頁` -> `註冊service worker` -> `install基本資源並啟動新的service worker` -> `判斷版本是否有更新,若有則清除舊的cache` -> `當有request時判斷是否從cache中獲取,若從網路獲取則判斷是否符合存至cache的條件`。 ## 最後 service worker的cache機制確實是可以達到減少網路流量及加快網頁的載入速度,但我認為如果你的網頁不需要離線操作那麼就不需要cache,原因是現今網路速度這麼快的情況下使用者的體感是感覺不出差異的,所以一般情境下單純的service worker就可以了。 若有問題可以留言給我,下次見。
2024-12-11
Frontend
PWA
React
react-syntax-highlighter - 將markdown的代碼區塊變漂亮,並加上複製功能
## 前言 最近上網看文章時發現有些文章的代碼區塊會根據代碼的檔案類型來附加顏色,讓我也想將我的 **blog** 加上此功能,於是想到就做,此篇文章主要是做一個紀錄和教學。 ## 步驟 1 先下載所需的套件: ```console npm i react-markdown npm i remark-gfm npm i react-syntax-highlighter ``` 1. **react-markdown**: 解析markdown檔案。 2. **remark-gfm**: 支援url、刪除線、表格等功能。 3. **react-syntax-highlighter**: 本文主角,用來處理代碼區塊。 ## 步驟 2 使用 react-markdown 來解析 markdown,並修改代碼區塊: ```tsx import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"; import { oneLight } from "react-syntax-highlighter/dist/cjs/styles/prism"; const PostPage: React.FC = () => { const post = `...你的markdown內容...` SyntaxHighlighter.registerLanguage("ts", typescript); SyntaxHighlighter.registerLanguage("typescript", typescript); SyntaxHighlighter.registerLanguage("tsx", tsx); return ( <div className="post-page"> <ReactMarkdown remarkPlugins={[remarkGfm]} components={{ code({ inline, className, ...props }: any) { const hasLang = /language-(\w+)/.exec(className || ""); return !inline && hasLang ? ( <SyntaxHighlighter style={oneLight} language={hasLang[1]} PreTag="div" showLineNumbers={true} useInlineStyles={true} > {String(props.children).replace(/\n$/, "")} </SyntaxHighlighter> ) : ( <code className={className} {...props} /> ); }, }} > {post} </ReactMarkdown> </div> ); }; export default PostPage; ``` 1. **oneLight**: 白色模式的style,也有其他style可根據喜好來做使用。 2. **SyntaxHighlighter.registerLanguage**: 將markdown代碼區塊中會使用到的語言給註冊進來,這邊我會將 `ts` 和 `typescript` 都註冊起來,這樣只要輸入兩者其一都可以套用到style。同理,可以根據所需來引入所需語言,例如: `json`、`dart`、`javascript` 等等。 3. **hasLang**: 取出是使用何種語言。 ## 步驟 3 - 代碼複製 將代碼變得漂亮後,怎麼可以沒有複製代碼的功能呢,以下是實作: ```tsx import { useState } from "react"; import { FaCheck } from "react-icons/fa"; import { FaRegCopy } from "react-icons/fa6"; const Copy = ({ value }: { value: string }) => { const [isCopy, setIsCopy] = useState<boolean>(false); return isCopy ? ( <FaCheck /> ) : ( <FaRegCopy onClick={() => { navigator.clipboard.writeText(value); setIsCopy(true); setTimeout(() => { setIsCopy(false); }, 3000); }} /> ); }; export default Copy; ``` 此功能在點擊之後會複製代碼,並替換icon,3秒後才可以重新複製。 ## 同場加映 - 標題導航 這裡同場加映一下**文章標題導航**的功能: ### 下載所需套件 ```console npm i rehype-slug npm i rehype-autolink-headings ``` 1. **rehype-slug**: 將markdown的標題產生id。 2. **rehype-autolink-headings**: 將已有id的地方產生連結並指向該標題。 ### 導航 當套件幫我們產生連結後,接下來只要透過 `<a />` 就可以導航到該標題了: ```tsx <a href={`#這裡填入產生的id`}>這裡填入產生的id</a> ``` ## 最後 由此可見,該功能在知道使用何種套件後並不難實現,至成果展示,就是上面那些代碼區塊了,我們下次見~
2024-10-25
React
markdown
highlighter
Firebase - 讓React(web)也可以接收訊息推播
## 前言 這幾天和朋友聊到手機App的推播,讓我聯想到Web端是否也可以像App一樣有接收推播的功能,當下越想越興奮,於是就誕生出了這篇文章。 ## 今日目標 **在部落格上實作訂閱功能** 要達成這個功能除了使用 **Firebase Cloud Messaging** 外,還需要再專案內加入**PWA**和註冊**Service Worker**,原因之後會提到,PWA的介紹和教學在[這裡](react-pwa)。 P.S 由於這篇文章程式碼比較多,各位請慢慢看才不會漏掉小細節倒致無法成功。 ## 註冊Firebase 1. 前往[Firebase](https://firebase.google.com/)點擊右上方的 **Go to console**,註冊並登入。 2. 點選建立專案,輸入專案名稱後,點選建立。 3. 進入 `專案設定 -> 一般設定 -> 點選 </> 的圖標` 來建立網頁應用。 4. 完成後,會得到 `firebaseConfig` 這樣就完成註冊了。 ## 在專案內加入PWA 由於我的Blog是使用Nextjs,PWA的配置方法會略有不同: 1. `npm i next-pwa` 2. 修改 `next.config.mjs` ```javascript import withPWA from "next-pwa"; const nextConfig = { output: "export", pwa: { dest: "public", register: true, skipWaiting: true, }, }; export default nextConfig; ``` 3. public下新增 `manifest.json` 和一張 **192x192**的圖片 ```json { "name": "Arthur Blog", "short_name": "A'Blog", "start_url": "/", "display": "standalone", "background_color": "#ffffff", "theme_color": "#000000", "icons": [ { "src": "/logo192.png", "sizes": "192x192", "type": "image/png" } ] } ``` 4. 修改 `app/layout.tsx` ```tsx ... <html lang="en"> <head> <link rel="manifest" href="/manifest.json" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucen" /> </head> <body> ... </body> </html> ... ``` ## 註冊Service Worker 1. public下新增 `sw.js` ```javascript self.addEventListener("install", (event) => { event.waitUntil( caches.open("my-pwa-cache").then((cache) => { return cache.addAll(["/", "/index.html", "/icon-192x192.png"]); }) ); }); self.addEventListener("fetch", (event) => { event.respondWith( caches.match(event.request).then((response) => { return response || fetch(event.request); }) ); }); ``` 2. 修改 `app/page.tsx` ```tsx ... useEffect(() => { if (typeof navigator !== "undefined") { if (typeof window !== "undefined" && "serviceWorker" in navigator) { navigator.serviceWorker .register("/sw.js") .then((registration) => { console.log( "Service Worker registered with scope:", registration.scope ); }) .catch((error) => { console.error("Service Worker registration failed:", error); }); } } }, []); ... ``` 3. 確認是否完成**PWA**和**Service Worker**: 查看網址最右邊是否有出現 `安裝` 且打開 `F12 -> Application` 查看Service Workers是否有註冊成功。 ## 讓Web可以接收FireBase訊息 接下來我們要讓專案可以接收到推播的訊息: 1. `npm i firebase` 2. public下新增 `firebase-messaging-sw.js` 並將剛註冊的 ``firebaseConfig`` 貼上 ```javascript importScripts( "https://www.gstatic.com/firebasejs/9.0.0/firebase-app-compat.js" ); importScripts( "https://www.gstatic.com/firebasejs/9.0.0/firebase-messaging-compat.js" ); firebase.initializeApp({ //你的firebaseConfig }); const messaging = firebase.messaging(); messaging.onBackgroundMessage((payload) => { const notificationTitle = payload.notification.title; const notificationOptions = { body: payload.notification.body, icon: payload.notification.icon, }; self.registration.showNotification(notificationTitle, notificationOptions); }); ``` 3. 接下來這個步驟非常重要,由於Nextjs是SSR所以接收訊息的Function必須放在 `useEffect` 內,打開 `app/page.tsx` 並修改 ```tsx ... const firebaseConfig = {...}; //你的firebaseConfig const app = initializeApp(firebaseConfig); const requestForToken: any = async (messaging: Messaging) => { return getToken(messaging, { vapidKey: "", }) .then((currentToken) => { if (currentToken) return currentToken; return null; }) .catch((err) => { return null; }); }; useEffect(() => { const messaging = getMessaging(app); onMessage(messaging, (payload) => { new Notification(payload.notification.title, { body: payload.notification.body, icon: payload.notification.icon, }); }); }, []); ... ``` 4. 這裡解釋一下上面的參數和函式 **requestForToken**: 用來獲取用戶token的函式,這樣Firebase才知道要推播給誰。 **vapidKey**: 這是一組公鑰,可以前往 `專案設定 -> 雲端通訊 -> Generate key pair`,來獲得。 **onMessage**: 將拿到的消息透過訊息框的方式通知使用者。 ## 發送訊息 接著讓我們來發送訊息測試一下是否成功: 1. 新增一個nodejs專案,不會的可以前往[這裡](nodejs-reptile)看步驟1。 2. `npm i firebase-admin` 3. 修改 `index.js` ```javascript const admin = require("firebase-admin"); admin.initializeApp({ credential: admin.credential.cert("./key.json"), }); const tokens = [...]; function test() { // 建立訊息內容 tokens.map((token) => { const message = { notification: { title: "測試訊息標題", body: "我是內容", }, token: token, }; // 推播訊息 admin .messaging() .send(message) .then((response) => { console.log("Successfully sent message:", response); }) .catch((error) => { console.log("Error sending message:", error); }); }); } test(); ```` **keyjson**: 可以從`firebase 專案設定 -> 服務帳戶 -> 產生私密金鑰` 來獲得。 **tokens**: 放入用 `requestForToken` 此函式獲取的token。 4. 執行 `node index.js`,這樣你的網頁就能獲得推播的訊息了。 ## 在沒有開啟網頁的情況下接收推播 這就是我們使用Service Worker的目的了: 1. 在電腦端的使用者 "點選網址最右邊的安裝"。 2. 在手機端的使用者將該網頁 "加入主畫面"。 3. 接著關閉網頁。 4. 在執行一次 `node index.js` 來看看效果。 ## 最後 這樣今天的目標就完成了,此網頁左上方的鈴鐺,就是本篇的功能展示,各位可以按下訂閱來獲取我最新的消息,下次見~
2024-10-17
Frontend
FCM
PWA
性價比極高的程式語言JavaScript - 詳細教學(1)
## 前言 最近有人問我<font color="red">**如果想踏入軟體行業該學習甚麼語言**</font>,而我的回答是<font color="red">**JavaScript(以下簡稱 JS)**</font>。 原因是 JS 幾乎無處不在,從網頁[前端](frontend-developer)的 React、Vue、Angular 到後端的 Nodejs,甚至是 App 方面的 React Native 都是使用 JS,但不是說這些工作都一定使用 JS,而是學會 JS 後可以往這些方向來做發展,不會被侷限在一個領域。 此系列會以我個人對 JS 的見解來做介紹,主要目的是讓沒接觸過 JS 的人也可以看懂並學會,此外也是自己對於 JS 的一個統整。若對文章中有任何問題可以點擊右下方的對話框來與我對話。 ## 變數宣告 變數的宣告有三種類型 **var** 、 **let** 、 **const**,會有許多人搞不清楚這三者的差異,這邊直接解釋清楚 ### var es6 以前是沒有 let 和 const 的,且 var 有可改變值、可重複宣告的特性 ```javascript //改變a的內容 var a = "hello"; console.log(a); //hello a = "test"; console.log(a); //test //重複宣告 a var a = "hello"; var a = "test"; console.log(a); // test //重複宣告 a 後在區塊語法下也會更動到 a,例如for迴圈(不懂for迴圈的下面會做介紹) var a = "hello"; console.log(a); //hello for (var a = 0; a < 2; a++) console.log(a); //0, 1 console.log(a); //2 ``` ### let let 算是 var 的升級版一樣有著可以改變值的特性,但**不可以重複宣告**,大大的降低了使用者宣告重複變數的問題 ```javascript //改變a的內容 let a = "hello"; console.log(a); //hello a = "test"; console.log(a); //test //不可重複宣告 let a = "hello"; let a = "test"; console.log(a); //Identifier 'a' has already been declared ``` ### const const 有著不可重複宣告、不可改變值的特性,通常會用來宣告不能更改的變數,例如: api 的 host(不懂也沒關係之後會介紹到) ```javascript //不可改變a的內容 const a = "hello"; console.log(a); //hello a = "test"; console.log(a); //Assignment to constant variable. //不可重複宣告 const a = "hello"; const a = "test"; console.log(a); //Identifier 'a' has already been declared ``` ## 函式 Function 函式分為 **函式** 和 **箭頭函式**,差別在於: 1. 箭頭函式語法不同、寫法也較簡潔 2. 箭頭函式沒有自己的 `this`、也無法直接修改 `this` 的指向 3. 箭頭函式沒有自己的 `arguments` 4. 箭頭函式不能作為構造函式使用 這邊不懂沒關係先看過就好。 ```javascript const a = 1; //箭頭函式 const test = () => { const a = 2; return a //會先判斷函式內是否有a變數,若有會使用函式內的a變數,反之會往外找a變數 } //函式 function test1 { return a } console.log(test()) // 2 console.log(test1()) // 1 console.log(a) // 1 ``` ## 判斷式 - 運算子 和判斷式相關的運算子有**比較運算子**、**邏輯運算子**,首先先介紹各個運算子: 1. **比較運算子**: **嚴格等於(===)**: `1 === 1` 會回傳 true。 **等於(==)**: `1 == "1"` 會回傳 true,若使用嚴格等於 `1 === "1"` 會回傳 false,數字不該等於字串所以請使用嚴格等於。 **嚴格不等於(!==)**: `1 !== 2` 會回傳 true。 **大於(>)**: `1 > 1` 會回傳 false。 **大於或等於(>=)**: `1 >= 1` 會回傳 true。 **小於(<)**: `1 < 1` 會回傳 false。 **小於或等於(<=)**: `1 <= 1` 會回傳 true。 2. **邏輯運算子**: **AND(&&)**: 所有條件都符合才會回傳 true,`1 === 1 && 1 === 2` 會回傳 false。 **OR(||)**: 其中一個條件都符合就會回傳 true,`1 === 1 || 1 === 2` 會回傳 true。 **NOT(!)**: `!(1===1)` 會回傳 false,`!(1===2)` 會回傳 true。 ### 判斷式 if...if else...else... ```javascript const a = 1; if (a === 1) { console.log("a = 1"); //當a等於1 會執行此行 } else if (a === 2) { console.log("a = 2"); //當a等於2 會執行此行 } else { console.log("a不等於1或2"); //不符合上面的if 會執行此行 } ``` ### 三元運算子 三元運算子算是 if...else...的簡化寫法,讓我們將上面的代碼轉換成三元運算子 ```javascript const a = 1; a === 1 ? console.log("a = 1") : a === 2 ? console.log("a = 2") : console.log("a不等於1或2"); ``` ### switch case 同理將上面的代碼轉換成 switch case ```javascript const a = 1; switch (s) { case 1: console.log("a = 1"); //當a等於1 會執行此行 break; //跳出switch讓代碼不會繼續往下執行 case 2: console.log("a = 2"); //當a等於2 會執行此行 break; default: console.log("a不等於1或2"); //不符合上面的if 會執行此行 break; } ``` ## 迴圈 常見的迴圈有 **for** 、 **while** 、 **do while** 、**遞迴**,下面將用這 4 種方法來達成 0 加到 10。 ### for ```javascript let a = 0; for (let i = 0; i < 10; i++) a++; console.log(a); // 10 ``` ### while 當條件符合才會執行代碼 ```javascript let a = 0; while (a < 10) { a++; } console.log(a); // 10 // while也可以當無線迴圈使用 let a = 0; while (true) { //當條件等於true時則一直執行,就會變成無限迴圈 a++; if (a === 10) break; //我們可以使用判斷式+break,來達到跳出無限迴圈的效果 } console.log(a); // 10 ``` ### do while 跟 while 差不多的寫法,差別在於 do while**會先執行一次代碼**才判斷是否該中斷迴圈 ```javascript let a = 0; do { a++; } while (a < 0); console.log(a); // 這樣會印出 1; //若使用while let a = 0; while (a < 0) { a++; } console.log(a); // 這樣則會印出 0; ``` ### 遞迴 遞迴則是使用一直回傳 Function 的方式來達簡化迴圈的目的。 ```javascript const add = (a) => (a < 10 ? add(a + 1) : a); console.log(add(0)); // 10 ``` ## 最後 今天介紹了**變數**、**函式**、**判斷式**以及**迴圈**,所有的程式都是從這些基礎衍生的,之後會介紹**Object**、**Array**等,我們下次見。
2024-10-11
Frontend
JavaScript
React - 漸進式網路應用程式(PWA)
## 注意 由於 `create-react-app` 已棄用,請到[這裡](#使用vite創建pwa)來看如何使用vite來創建pwa專案。 ## 前言 甚麼是 **漸進式網路應用程式(PWA)** ,簡單來說就是讓 Web 的介面變成像是原生 App 一樣,並且可以下載至主畫面,且 **PWA** 有兩個吸引我的地方: 1. **可離線操作**: 由於 PWA 會將 Web 的資料 Cache 在 Local 端,在網路受限的情況下可以繼續做使用。 2. **仿原生 App**: 就像上述提到的可以將 Web 變成類 App 的樣子,而且可以省去上架至 Google Play & Apple Store 的步驟和 ~~開發者帳號的費用~~ 實現時時更新不用重新下載。 今天的文章會介紹如何讓 React 專案快速變成 PWA。 ## 步驟 1 新專案直接使用下面代碼即可 ```console npx create-react-app react-pwa --template cra-template-pwa-typescript ``` 若是在原有的專案下新增 PWA,由於 React 目前沒有提供此機制,建議使用上面代碼創建專案後將 `service-worker.ts` 和 `serviceWorkerRegistration.ts` 兩個檔案複製到`src/`下。 ## 步驟 2 接著開啟 `src/index.tsx` ,輸入以下代碼 ```tsx import * as serviceWorkerRegistration from "./serviceWorkerRegistration"; serviceWorkerRegistration.register(); ``` ## 步驟 3 開啟 `public/manifest.json` 輸入 ```json { "short_name": "React PWA", "name": "React PWA", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" }, { "src": "logo192.png", "type": "image/png", "sizes": "192x192" }, { "src": "logo512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } ``` ## 步驟 4 最後打開 `public/index.html` 輸入 ```html <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> ``` 1. **viewport**: 告訴瀏覽器載入後寬度為設備寬且不進行縮放。 2. **apple-mobile-web-app-capable**: 在 ios 添加到主畫面時是否全屏運行,這很重要,若是沒有設定 ios 上會沒有 pwa 效果。 3. **apple-mobile-web-app-status-bar-style**: ios 上狀態欄的顏色,使用 **black-translucent** 可以讓狀態欄等於背景顏色。 ## 使用vite創建pwa 此部分於 2024-10-29 更新 ### 創建專案 前往[這裡](/post/react-kit)使用vite來新增專案。 ### 下載套件 `npm install -D vite-plugin-pwa workbox-window` ### 設置vite 修改 `/vite.config.ts` ```ts import { defineConfig } from "vite"; import react from "@vitejs/plugin-react-swc"; import { VitePWA } from "vite-plugin-pwa"; export default defineConfig({ plugins: [react(), VitePWA()], }); ``` ### 新增manifest 跟原步驟一樣至 `public` 下新增 `manifest.json` ### 新增service-worker 新增一個檔案 `src/service-worker.js` ```js import { precacheAndRoute } from "workbox-precaching"; precacheAndRoute(self.__WB_MANIFEST); ``` ### 註冊service worker 修改 `app.tsx` ```tsx ... useEffect(() => { if ("serviceWorker" in navigator) { window.addEventListener("load", () => { navigator.serviceWorker .register("/service-worker.js") .then((registration) => { console.log("SW registered: ", registration); }) .catch((registrationError) => { console.log("SW registration failed: ", registrationError); }); }); } }, []); ... ``` ## 最後 完成上述步驟後 React 專案就支援 PWA 了,[這裡](https://smr24425.github.io/hospital-demo/)是我做一個小作品支援 PWA 有興趣的各位可以去看看,我們下次見~
2024-10-07
Frontend
React
PWA
快速學會Nodejs爬蟲
## 前言 當初會研究爬蟲是因為自己喜歡看小說,但又不想使用瀏覽器看(不想等每章的 request 時間和廣告),於是就開始了爬蟲之旅 XD。 <font color="red">請注意! 爬蟲的結果僅供個人使用不要公開或做商業用途,以免觸犯法律。</font> ## 目標 原本是想要直接爬取小說內容並寫入txt來做範例的,但不知是否違法於是將目標改成: **獲取 iT 幫幫忙一到五頁的文章標題並寫入 txt** ### 步驟 1 首先創建一個全新的 node 專案並加入所需套件: ```console mdkir reptile cd reptile npm init npm i axios npm i cheerio ``` ### 步驟 2 創建一個`index.js`檔案,並引入相應套件: ```js const axios = require("axios"); const cheerio = require("cheerio"); const fs = require("fs"); ``` ### 步驟 3 偽裝**Headers**, 這很重要,沒有偽裝可能會導致無法爬取: ```js const headers = { Accept: "*/*", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" }; ``` ### 步驟 4 爬蟲 function,用來獲取每篇文章的 title ```js const GetITHelpItems = (page) => { return new Promise((resolve, reject) => { axios .get(`https://ithelp.ithome.com.tw/?page=${page}`, { headers }) //你要爬取的網址 .then((v) => { var $ = cheerio.load(v.data); var titles = $(".tabs-content .qa-list .qa-list__title-link"); //要爬取內容的css結構 var content = ""; //將所有符合爬取內容的資料提取並透過resolve回傳 for (let i = 0; i < titles.length; i++) content += titles[i].children[0].data + "\n"; resolve(content); }) .catch(() => { reject("error"); }); }); }; ``` ### 步驟 5 將獲取的結果寫入 txt ```js const start = async () => { for (let i = 1; i <= 5; i++) { var text = await GetITHelpItems(i); fs.appendFile("./a.txt", text, function (error) {}); } }; ``` ## 完整代碼 ```js const axios = require("axios"); const cheerio = require("cheerio"); const fs = require("fs"); const headers = { Accept: "*/*", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" }; const GetITHelpItems = (page) => { return new Promise((resolve, reject) => { axios .get(`https://ithelp.ithome.com.tw/?page=${page}`, { headers }) .then((v) => { var $ = cheerio.load(v.data); var titles = $(".tabs-content .qa-list .qa-list__title-link"); var content = ""; for (let i = 0; i < titles.length; i++) content += titles[i].children[0].data + "\n"; resolve(content); }) .catch(() => { reject("error"); }); }); }; const start = async () => { for (let i = 1; i <= 5; i++) { var text = await GetITHelpItems(i); fs.appendFile("./a.txt", text, function (error) {}); } }; start(); ``` ## 最後 本文章是以**存入 txt**作為範本,讀者可以根據需求變成**存入資料庫**、**信箱發送**、**google 試算表**還是**Telegram**等都是可以的。 爬蟲除了可以爬取文字外也可以做其他應用,例如: 股票資訊、圖片、新聞等等,根據你的需求來修改代碼,最後再提醒一次,<font color="red">僅個人使用以免觸犯法律</font>,下次見~
2024-06-17
Nodejs
Reptile
Docker - 來將React專案製作成Image吧
## 前言 為何需要 **Docker Image**? 當你需要讓專案給別人使用或是在別台電腦使用,並且不想影響到該電腦的環境,那這時就會需要 **Docker Image**。 ## 目標 **將 React 專案製作成 Docker Image 並讓其他人可以下載**,本目標可以拆成三個步驟: 1. 創建專案 2. 製作 Image 3. 上傳至 Docker Hub ## 步驟 1 先創建一個名為 docker-react 的專案: 2024-10-29 更新,由於 `create-react-app` 已棄用可以前往[這裡](/post/react-kit)使用vite來新增專案。 ```console npx create-react-app docker-react --template typescript ``` 接著我們需要 build 該專案: ```console cd docker-react npm run build ``` ## 步驟 2 接著我們在 docker-react 的專案根目錄新增兩個檔案 `dockerFile` 和 `nginx.conf` dockerFile ```dockerfile From nginx:latest copy build /var/www copy nginx.conf /etc/nginx/conf.d/default.conf ``` 第一行: 使用 nginx 當基底 第二行: 將剛剛 build 好的檔案放入`/var/www` 第三行: 將我們創建的 nginx.conf 檔案 放入 nginx 的預設路徑 `nginx.conf` 如何設定可以看[這裡](/post/nginx) 現在可以開始製作 Image 了: ```console docker build -f .\dockerFile . -t docker-react ``` 創建好後,試著在本地執行: ```console docker run -it -p 80:80 docker-react ``` 打開`http://127.0.0.1/`,就可以了我們的專案了。 ## 步驟 3 接著我們要將剛剛創建好的 Image 上傳至**Docker Hub**讓其他人可以下載 1. 打開`https://hub.docker.com/`,創建帳號並登入 2. 點擊`Create repository`,讓我們 Image 有地方上傳 3. 接著執行兩行代碼: ```console docker tag docker-react {username}/docker-react:v1 ``` 這行的意思是將我們本地的 Image 連接到剛剛創建的**repository**,**v1**是版本的意思 ```console docker push {username}/docker-react:v1 ``` 就可以在 Docker Hub 看到我們的 Image 了  ## 最後 這樣就完成了 **Docker Image** 的建置,將 react 封裝成 Image 並不會太難,希望能幫助到沒有接觸過 docker 的人,下次見~
2024-06-16
Docker
Docker Image
React
使用Drone來建置CI/CD
## 前言 今天的內容是使用**Drone**來建置**CI/CD**,畢竟身為前端工程師甚麼都要略懂才行! ## 步驟 1 沒有docker的請先到[這裡](https://www.docker.com/products/docker-desktop/)下載並安裝。 首先準備一份 `docker-compse.yaml` 內容如下: ```yaml version: "3" services: drone-server: image: drone/drone:2.24 # tag:2 image id:86e71e93066f container_name: drone-server ports: - 8000:80 volumes: - ./drone_data:/data restart: always environment: ## domain - DRONE_SERVER_HOST= # drone server - DRONE_SERVER_PROTO= - DRONE_RUNNER_CAPACITY= - DRONE_RPC_SECRET= #共用密碼(隨機) # Gitea - DRONE_GITEA_CLIENT_ID= # client id - DRONE_GITEA_CLIENT_SECRET= #client secret - DRONE_GITEA_SERVER= # gitea server # db - DRONE_DATABASE_DATASOURCE= # mysql配置,要與上邊mysql容器中的配置一致 - DRONE_DATABASE_DRIVER=mysql # Gitea Auth - DRONE_LOGS_DEBUG=true - DRONE_LOGS_PRETTY=true - DRONE_LOGS_COLOR=true - DRONE_GIT_ALWAYS_AUTH=true # For mount auth in Drone - DRONE_USER_CREATE=username:Arthur,admin:true drone-runner: image: drone/drone-runner-docker:1.8.3 # tag: 1 image id: 56dddb548a45 restart: always depends_on: - drone-server volumes: - /var/run/docker.sock:/var/run/docker.sock environment: - DRONE_RPC_HOST=drone-server - DRONE_RPC_PROTO=http - DRONE_RPC_SECRET= ``` 該代碼有幾個變數需要特別說一下: 1. **DRONE_RPC_SECRET**: 隨機使用一組密碼只要與下方保持一致即可。 2. **DRONE_GITEA_CLIENT_ID**: 需要到 gitea 上設定,如下圖 3. **DRONE_GITEA_CLIENT_SECRET**: 需要到 gitea 上設定,如下圖  1. **Application Name**: 填你要取的名稱,例: my-drone 2. **Redirect URIs**: 你 drone 的網址(記得+login),例: http:localhost:8000/login 設定好後按儲存就可以得到**DRONE_GITEA_CLIENT_ID**、**DRONE_GITEA_CLIENT_SECRET**了。 接著執行: ```console docker-compose up -d ``` ## 步驟 2 再準備一份 `.drone.yml`,放在你的專案跟目錄,其內容如下: ```yml kind: pipeline type: docker name: clone steps: - name: frontend-install image: node:16.17.1 commands: - npm i - name: frontend-build image: node:16.17.1 volumes: - name: frontend-path path: /frontend_build commands: - npm run build - rm -rf /frontend_build/* - mv build/* /frontend_build/. when: branch: - master volumes: # 設定路徑 將build完的檔案放到你主機上的路經 - name: frontend-path host: path: /var/www/frontend_build trigger: # 觸發 pipeline 條件,分支為 master,且進行 push 行為 branch: - master event: - push ``` 設定完之後,只要每次`push master`就會觸發該 drone 檔,它會自動將 build 好的檔案放入`/var/www/frontend_build`,接著我們只需將 Nginx 的路徑指到這裡後就完成了自動部屬的全部流程,Nginx 可以參考這裡[這裡](/post/nginx)。 ## 步驟 3 開啟步驟1所說的 `Redirect URIs` 網址,並選擇要建置CI/CD的專案,將 `Settings -> Trusted` 打勾,這樣就可以開始CI/CD了。 ## 最後 最後讀者可以根據自己的需求在 step 裡面加入相應的步驟,例如telegram通知、信箱通知等等,今天就介紹到這邊,下次見~
2024-06-15
Drone
CI/CD
1
2
返回頂部
複製連結
留言版