# 嬿媜 q1~q5_code review ### q1 #### playMovieMessage 命名 改為 IsPlayMovie 或 showMovieStatus? ### verify #### IsPositiveInteger 命名 命名叫 IsPositiveInteger 是正整數,但是 0 不是正整數 ```javascript! export function IsPositiveInteger(input){ if(Number(input) < 0){ throw new Error('請輸入正整數^^'); } } ``` #### ==符號有沒有擋到要確認一下== ### q2 宣告的 function 可以提出去外面,比較好閱讀。 可以再看一下巢狀要怎麼改。 ### q3 #### calculateTicketPrice 命名 改為 GetTicketPriceByAge? #### calculateTicketPrice 確定不會動的東西都用 const 先宣告,然後再修改其他情況。 ```javascript! // 原本寫法 export function calculateTicketPrice(userInput) { let ticketPrice = 400; if (userInput <= 6 || userInput >= 65) { ticketPrice = ticketPrice * 0.5; return `您的票價為:${ticketPrice} 元`; } else { return `您的票價為:${ticketPrice} 元`; } } // 可以改為 export function calculateTicketPrice(userInput) { const originalPrice = 400; const discount = 0.5; let ticketPrice; if (userInput <= 6 || userInput >= 65) { ticketPrice = ticketPrice * discount; return `您的票價為:${ticketPrice} 元`; } else { ticketPrice = originalPrice; return `您的票價為:${ticketPrice} 元`; } } ``` ### q4 #### 驗證放一起 ```javascript! if(Number(userInput) === 0) { console.log(`請輸入比 0 大的數字^^`); main(); } else { ... } // 這個 0 的可以搬到後面的驗證一起 ``` #### 讓 let i = 0 always 從 0 開始 通常不會改這個初始值 ### test 現在是算式和總和分開測 不知道最終 console.log() 出來的東西是不是你想要的,可以放在一起測,測一整個 main ### 推薦看 array.reduce Code Completed Ch11 # 季芳 q1~q5_code review ### q1 #### 1. 用 let 的用意?後面會對這個重新賦值嗎? ```javascript! let playMovieMessage = playMovie(userInput); ``` 季芳目前都是 const 用到底,如果變數要重新賦值才會改成 let。 #### 2. 知道rl是非同步嗎? - 還有另一個套件,同步的: [heapwolf/prompt-sync: a synchronous prompt for node.js](https://github.com/heapwolf/prompt-sync),會等使用者輸入之後才會做下一步。 #### 3. 命名:大駝峰小駝峰 目前看都寫小駝峰,但 verify.js 裡面的`Is…`是大駝峰,命名時大小駝峰選一個用就好。 #### 4. `checkInputValidity` 這個 function 放在 verify 位置有點奇怪。 目前 verify 大多放獨立驗證的 function,`checkInputValidity`更像是容器在對內容作抽換。 可以看一下 [Jade](https://gitlab.com/dwatow/j1/-/blob/jade/Jade/common_module/validators.js) 寫的驗證。 #### 5. 沒驗到科學記號e `1e01` 目前是從**使用者輸入**去驗。 還有一種驗法是:只能輸入什麼,最後一個用驗證用正則擋。 ### q1 test 從執行結果看,執行結果沒有被描述出來。 Chirs 有提過:測試可能會給不懂程式的人看,假設這裡是給業主看的。 單從這邊的描述看不出測完的輸出結果,只看出有通過,但通過的結果是什麼不知道。這樣不會知道有沒有符合功能的需求。 - 可以用`describe` 當作容器把內容整理一下。 [Globals · Jest](https://jestjs.io/docs/api#describename-fn) 可以參考 [季芳](https://gitlab.com/-/ide/project/dwatow/j1/edit/FangLee/-/01/Q1module.test.js) 的,使用 describe 看看 ```javascript! import { Q1 } from "./Q1module.js"; describe("Q1:好想電影院規定除非完全沒有客人買票,否則就算只有一位顧客也照常播放電影,讓使用者輸入客人的人數,並用「!」判斷人數,不為零顯示「照常播放電影」。", () => { test(`輸入"10",會輸出"照常播放電影"`, () => { expect(Q1("10")).toBe("照常播放電影"); }); test(`輸入"0"會輸出"不播放電影"`, () => { expect(Q1("0")).toBe("不播放電影"); }); }); describe("測試 Q1 不會輸出", () => { test(`輸入"10",不會輸出"不播放電影"`, () => { expect(Q1("10")).not.toBe("不播放電影"); }); test(`輸入"0",不會輸出"照常播放電影"`, () => { expect(Q1("0")).not.toBe("照常播放電影"); }); }); describe("測試 Q1 以下都會拋出異常", () => { test("無輸入", () => { expect(() => Q1("")).toThrow(); }); test("輸入空格", () => { expect(() => Q1(" ")).toThrow(); }); test("輸入+0", () => { expect(() => Q1("+0")).toThrow(); }); test("輸入-0", () => { expect(() => Q1("-0")).toThrow(); }); test("輸入0.5", () => { expect(() => Q1("0.5")).toThrow(); }); test("輸入-5", () => { expect(() => Q1("-5")).toThrow(); }); test("輸入中文", () => { expect(() => Q1("中文")).toThrow(); }); test("輸入english", () => { expect(() => Q1("english")).toThrow(); }); }); ``` ### q2 1. 主程式裡面兩個問題有點巢狀 *Callback Hell*,想一下要怎麼把第二題提出來。 - 第一題的輸入傳到第二題 2. 可以安裝 [Code Spell Checker - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker),會幫你檢查拼錯字(藍色底線)。 3. 同第一題使用 let 宣告的原因 4. 驗證完沒有轉型 ### q3 1. 驗證完沒有轉型 2. calculateTicketPrice ```javascript! // before export function calculateTicketPrice(userInput) { let ticketPrice = 400; if (userInput <= 6 || userInput >= 65) { ticketPrice = ticketPrice * 0.5; // 這裡會直接改到ticketPrice本身,ticketPrice會變成200 return `您的票價為:${ticketPrice} 元`; } else { return `您的票價為:${ticketPrice} 元`; } } // after export function calculateTicketPrice(userInput) { const ticketPrice = 400; if (userInput <= 6 || userInput >= 65) { return `您的票價為:${ticketPrice * 0.5} 元`; // 這樣ticketPrice還是400 } else { return `您的票價為:${ticketPrice} 元`; } } ``` ### q4 #### 1. `Number(userInput) === 0` 應該也是在做驗證吧? 這個可以收在`checkInputValidity`裡面,另外提到外面用if/else處理的原因是? #### 2. 驗證完沒有轉型 #### 3. createFormula:`const n = input;` 這一行的用意是什麼?不能直接拿 input 去寫嗎? 通常不會用單一字母`n`來命名,也不會用簡寫。 #### 4. gstSum:結構很巢狀 不好讀:function 包 function,然後又if/else裡面包if/else,可以想一下怎麼拆成好讀一點的結構。 ```javascript! // 原本 function accumulate(currentInput, originalTotal) { let currentTotal = originalTotal; let currentValue = Number(currentInput); if (currentValue === 1) { currentTotal = currentTotal + currentValue; } else { if (currentValue % 2 === 0) { currentTotal = currentTotal + currentValue; } else { currentTotal = currentTotal - currentValue; } return accumulate(currentValue - 1, currentTotal); // 要寫 return 因為要把 total 的值傳給下一個呼叫的自己 } return currentTotal; } export function getSum(input) { const total = accumulate(input, 0); return total; } ``` ```javascript! // 改寫 function accumulateSum(currentNumber, runningTotal) { const isMinimumValue = currentNumber === 1; if (isMinimumValue) { return runningTotal + currentNumber; } const updatedTotal = getUpdatedTotal(currentNumber, runningTotal); return accumulateSum(currentNumber - 1, updatedTotal); } function getUpdatedTotal(number, total) { const currentNumberIsEven = number % 2 === 0; if(currentNumberIsEven) { return total + number; } return total - number; } export function getSum(number) { const sumResult = accumulateSum(number, 0); return sumResult; } ``` ### q5 #### 1. umleven、umlevenFormula 這兩個 function 裡面的內容看起來是重複的,可以想一下怎麼把重用的部分抽出來。 ``` ``` - 如果對迴圈練熟了,可以嘗試使用array method ### 推薦看 array method # 阿傑 q1~q5_code review ### rl ```javascript! import { createInterface } from 'readline'; const rl = createInterface({ input: process.stdin, output: process.stdout }); export { rl }; // 可以不用包 {},因為沒有要做其他事情 ``` ### verify ```javascript! export function IsNotEmpty(input) { if (!(String(input).trim().length > 0)) { throw new Error('請輸入東西^^'); } } // rl 拿到其實就是 String 了,不用轉 export function IsNotEmpty(input) { if (!(input).trim().length > 0)) { throw new Error('請輸入東西^^'); } } // 可改為 export function IsNotEmpty(input) { if (!(input).trim().length) { throw new Error('請輸入東西^^'); } } ``` 怎麼知道自己在寫啥:) ```javascript! !(input).trim().length -> (input) 是拿到什麼?字串(假設我輸入3) !(input).trim().length -> !"3".trim().length trim() 是什麼?會把前後空白都去掉的 funciton,會回傳字串 -> !"3".length length 是什麼?會把字串轉為字串長度的 funciton,會回傳數字 -> !1 !1 會轉為布林值 1 會轉為 true 所以 !true 是 false ``` ### 注意各處 let 考慮是否用 const ### isNaN / Number.isNaN ### Number.isInteger 看有沒有其他可以轉為數字的方式 [參考](https://dev.to/sanchithasr/7-ways-to-convert-a-string-to-number-in-javascript-4l) ### <!-- # Chris q1~q5_code review --> ### q2 ```javascript! // 原本寫法 export function compareRemainders(firstNumber, secondNumber) { if (firstNumber % 3 === secondNumber % 3) { return `${firstNumber} 和 ${secondNumber} 餘數相同`; } else { return `${firstNumber} 和 ${secondNumber} 餘數不相同`; } } // 可以改為 export function compareRemainders(firstNumber, secondNumber) { const fistReminder = firstNumber % 3 const secondReminder = secondNumber % 3 if (fistReminder === secondReminder) { return `${firstNumber} 和 ${secondNumber} 餘數相同`; } else { return `${firstNumber} 和 ${secondNumber} 餘數不相同`; } } // 甚至可以讓這隻 function 只回傳布林值,讓主程式做印出 export function areRemindersEqual(firstNumber, secondNumber) { const fistReminder = firstNumber % 3 const secondReminder = secondNumber % 3 return fistReminder === secondReminder } // main const result = areRemindersEqual ? `${firstNumber} 和 ${secondNumber} 餘數相同` : `${firstNumber} 和 ${secondNumber} 餘數不相同` console.log(result); ``` ### 一個 function 只做一件事 ### 讓 let i = 0 從 0 開始 如果要從一開始跑的可以這樣寫 ```javascript! for(let i = 0; i < n; i++) { if(i === 0) { continue; } ... } ``` ### 驗證用一個 陣列跑forEach ```javascript! export function checkInputValidity(input, moreValidation){ //basic isNotEmpty(input); isNumber(input); isInteger(input); isPositiveInteger(input); // moreValidation.forEach(validation => { validation(input) }); } // 這樣呼叫 checkInputValidity(userInput, [isNotZero, OverFour, isEven]); ``` ### 閉不閉包 ```javascript! // 原本 export function calculateSum(input) { const total = accumulate(input); return total; function accumulate(currentInput) { // side effect let total = 0; let currentValue = Number(currentInput); if (currentValue === 1) { total = total + currentValue; console.log(total) } else { if (currentValue % 2 === 0) { total = total + currentValue; console.log(total) } else { total = total - currentValue; console.log(total) } accumulate(currentValue - 1); } return total; } } ``` > 怕有副作用問題,所以改寫為變數就作用在自己的 function 裡面,再回傳出去 ```javascript! // 阿傑調整 export function calculateSum(input) { const total = accumulate(input, 0); return total; function accumulate(currentInput, originalTotal) { let currentTotal = originalTotal; let currentValue = Number(currentInput); if (currentValue === 1) { currentTotal = currentTotal + currentValue; } else { if (currentValue % 2 === 0) { currentTotal = currentTotal + currentValue; } else { currentTotal = currentTotal - currentValue; } return accumulate(currentValue - 1, currentTotal); // 要寫 return 因為要把 total 的值傳給下一個呼叫的自己 } return currentTotal; } } ``` # Chris q1~q5_code review 🔥๛๛๛ก(ー̀ωー́ก ) ### 到底要測什麼?測試應該如何訂定範圍、邊界 > 就是測:使用者輸入到輸出。 #### 現在我 q5 測的是:算式測一個,答案測一個 ```javascript! 但是其實我們要測的是:"輸入" 印出 "算式=答案" >> 這一整串 原本測的方式 輸入 4 算式對,輸出 2*4,答案也對,輸出 8 但其實介面會不會是 2*4 8 還是 2*4 = 8 這你不會知道,測試不會噴錯 所以建議要"包起來一起測試"。 測輸入到輸出,都測到了,邏輯也跟介面分開(還是不太懂這句的意思)。 ``` ```javascript! // 原本寫法 function main() { rl.question('來算 2*4 + 4*6 + 6*8...+(n-2)*n 吧,請輸入一個 至少為 4 的偶數 n:', (userInput) => { try { checkInputValidity(userInput); const umlevenSum = getUmlevenSum(Number(userInput)); const umlevenFormula = getUmlevenFormula(Number(userInput)); const result = `${umlevenFormula} = ${umlevenSum}`; return result; rl.close(); } catch (error) { console.log(error.message); main(); } }); } main(); // 改為 function main() { rl.question('來算 2*4 + 4*6 + 6*8...+(n-2)*n 吧,請輸入一個 至少為 4 的偶數 n:', (userInput) => { try { const result = Q5(userInput); console.log(result); rl.close(); } catch (error) { console.log(error.message); main(); } }); } main(); // 就測 Q5 一整個就好 function Q5(userInput) { checkInputValidity(userInput); const umlevenSum = getUmlevenSum(Number(userInput)); const umlevenFormula = getUmlevenFormula(Number(userInput)); const result = `${umlevenFormula} = ${umlevenSum}`; return result; } ``` #### 要測輸入錯誤的時候會怎樣 ```javascript! // 測試例子至少測這些: 最小值 最小值-1 最大值 -> 要設一個值給他,錯誤會是我設定的 Error,想語言極限到哪裡 最大值+1 其他一堆不正常的輸入 ``` ### main() export 拿掉 ### verify ```javascript! parseInteger.parseFloat -> parse 是解析 -> 解析什麼? 字串 isNotEmpty 你的命名有隱含是要放字串,所以裡面不一定要轉態 其實有兩派: 1. 丟什麼轉好再執行,避免 garbage in(丟數字會報錯的) 2. Chris 是不轉派的,自己寫自己用的,不做這件事就會跑比較快(少一個 stack) 寫容錯率很高 funciton 通常就是要給別人用的 ``` ### 對象 ```javascript! 要想一下這個東西是要"給哪個對象用的" 舉例:我的驗證是我自己在用的,所以可以不用轉態,錯了再噴錯、自己看得到就好了 對象會影響你寫程式的錯誤處理機制! ``` ### checkInputValidity 可以分兩段 ```javascript! export function checkInputValidity(input) { isNotEmpty(input); isStartsWithPlusOrMinusZero(input); isNumber(input); isInteger(input); isPositiveInteger(input); overFour(input); isEven(input); } // 可以分為兩段,比較好區別驗證字串和驗證數字區 export function checkInputValidity(input) { isNotEmpty(input); isStartsWithPlusOrMinusZero(input); isNumber(input); isInteger(input); isPositiveInteger(input); overFour(input); isEven(input); } ``` ### isNaN / Number.isNaN,我還是很混亂但總之先用 Number.isNaN Number.isNaN > The boolean value true if the given value is a number with value NaN. Otherwise, false. 如果給定值是值為 NaN 的數字,則布林值 true。否則為 false。 -> 只有數字型別的 NaN 丟的進來,其他都false ```javascript! NaN 字串轉型為數字會是 NaN Number("NaN") // NaN - Number.isNaN("NaN") // false isNaN("NaN") // true isNaN("NaN") // true isNaN("ddd") // true isNaN(Number("NaN")) // true isNaN(Number("ddd")) // true Number.isNaN("NaN") // false Number.isNaN("ddd") // false Number.isNaN(Number("NaN")) // true Number.isNaN(Number("ddd")) // true ``` ### 沒擋到 ```javascript! 1e2 0x110 0xFE 因為要擋這些事情,所以要對數字型別非常清楚/有什麼表達方式 學進制轉換( 12轉8 啊,8轉10 啊,才有辦法知道這些事情) ``` ### 科學記號補充 [IEEE 754](https://zh.wikipedia.org/zh-tw/IEEE_754) [從 IEEE 754 標準來看為什麼浮點誤差是無法避免的](https://medium.com/starbugs/see-why-floating-point-error-can-not-be-avoided-from-ieee-754-809720b32175) [BigInt](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/BigInt) ### 有意識の注意型別 ```javascript! 寫 function 的時候注意"輸入和輸出各是什麼型別" 看完上面補充,可以知道為什麼JS會有這麼多數字型別 ``` ### 驗證輸入 0,我還是很混亂 ```javascript! // 輸入 0,不會跑錯誤訊息, // 沒擋到 0,會直接跑 overFour()那段訊息 export function isPositiveInteger(input){ if(Number(input) < 0){ throw new Error('請輸入正整數^^'); } } export function overFour(input) { if(Number(input) < 4) { throw new Error('請輸入至少為 4 的數字^^'); } } // 改為: export function isPositiveInteger(input){ if(Number(input) <= 0){ throw new Error('請輸入正整數^^'); } } ``` ### why 要寫 verify > 引導熟悉型別的表達方式 ### 不習慣這樣那樣讀 (使用多運算子時,會有運算先後順序的差別,不想去記) ```javascript! const isMinimumSum = number === minimumValidNumber; ``` ```javascript! // 改寫 function(Chris 的習慣) funciton isMinimumSum() { return number === minimumValidNumber; } if(isMinimumSum()) { ... } // 或寫括號 const isMinimumSum = (number === minimumValidNumber); ``` ### 哲學問題:轉態後才進 function / 進 function 才轉態 ```javascript! const finalTicketPrice = getTicketPriceByAge(Number(userInput)); or function getTicketPriceByAge() { const nUserInput = Number(userInput); ... } ``` >判別標準:它是屬於誰的 >funciton 這一步,該煩惱它的型別嗎? ```javascript! Chris 偏好轉型完再進 function,因為我就"認定裡面的東西一定是這個型別" 這個功能本身是假設好型別的,不要背負著轉態這件事情,我知道我要轉、而且我也轉好了! ``` ### 匈牙利命名法 userInput 轉態完可以命名為 `nUserInput?`,[歷史看這裡](https://ithelp.ithome.com.tw/articles/10203983) ### return 要 return 一個 result 嗎 ```javascript! // 這個 result 像是中性的命名 const umlevenFormulaResult = `${getUmlevenFormula(number - 2)} + ${(number - 2)}*${number}`; return umlevenFormulaResult; // Chris 分享: 看到這串會先看到等號,咦,ok 他們相等,然後等號右邊和等號左邊名稱很像 左邊本就是右邊的結果,如果這個命名沒有特殊意義(這會有點像沒有命名),Chris 會直接寫 return。 如果是 array method 也不太會命名,就直接 return 除非超難讀,因為不會影響除錯閱讀速度,他只有要 return 沒有要做什麼處理 ``` ### q5 可以試試看用算式直接算答案 ```javascript! // 第一種:eval eval('2*4 + 4*6') // 32 // 第二種:new Funciton var f = new Function('return 2*4 + 4*6'); // 宣告 f(); // 32 ``` ### q1 - isPlayMovie ```javascript! // 原本寫法像 over design 🥵 export function isPlayMovie(customerCount) { const hasCustomer = (Number(customerCount) !== 0); return hasCustomer; } export function showMovieStatus(isMoviePlaying) { if (isMoviePlaying) { return '今日營業中,放映電影:)'; } else { return '今天休假,暫停放映:('; } } ``` ```javascript! // 簡潔寫法,回傳一個布林值 export function showMovieStatus(customerCount) { const isMoviePlaying = (Number(customerCount) !== 0); if (isMoviePlaying) { return '今日營業中,放映電影:)'; } else { return '今天休假,暫停放映:('; } } ``` ### q1 - showMovieStatus ``` 他不是狀態碼,可以命名為一個公告文字之類的 ``` ### q3 - getTicketPriceByAge 的 const 把變數都先列出來 ```javascript! // 原本寫法 export function getTicketPriceByAge(age) { const originalTicketPrice = 400; const maxChildAge = 6; const minSeniorAge = 65; const discountRate = 0.5; const isChild = age <= maxChildAge; const isSenior = minSeniorAge <= age; if (isChild || isSenior) { return `您的票價為:${originalTicketPrice} 元`; } else { const discountedTicketPrice = originalTicketPrice * discountRate; return `您的票價為:${discountedTicketPrice} 元`; } } ``` ```javascript! // Chris 寫法 export function getTicketPriceByAge(age) { const originalTicketPrice = 400; const maxChildAge = 6; const minSeniorAge = 65; const discountRate = 0.5; if (maxChildAge < age && age < minSeniorAge) { return `您的票價為:${originalTicketPrice} 元`; } else { const discountedTicketPrice = originalTicketPrice * discountRate; return `您的票價為:${discountedTicketPrice} 元`; } } // 如果下次 maxChildAge < age && age < minSeniorAge 又有其他新的意思,再把它變成一個新的變數 ``` ### 德摩根定理 ```javascript! 對符號做思考 ! 先加上去邏輯顛倒 ! 對符號乘法分配率對符號做顛倒邏輯 ``` ### 額外宣告變數好嗎 ```javascript! const finalTicketPrice = getTicketPriceByAge(Number(userInput)); console.log(finalTicketPrice); v.s. console.log(getTicketPriceByAge(Number(userInput))); // 第一種寫法 - 會好讀、好維護、好測試結果,但是否有需要考慮額外宣告變數的內存? //第二種寫法 - 可能在沒有那麼複雜的時候可以用 ``` ```javascript! 思考:左右兩邊有重複的命名,是否有必要,這會決定"這個變數有沒有存在價值",而不是這個寫法怎麼樣。 舉例:三角函數,爆算了一座山的高度 給左邊的結果命名:玉山高度 or 三角函數結果 前者就比較具有意義,後者就乾脆直接 return 算了 但結論是自己要看起來舒服。 而可讀是要"過自己那一關",是自己過幾個月回來也是要可讀。 ``` ### 熟悉度 ```javascript! 要考慮到協作的人的熟悉度 你都寫柯里化,團隊的新手都來問你 其實可以寫簡單一點一般寫法,然後你可以做自己的事XD ``` ### 看 code 的時候不要靠腦子的記憶,全部用眼睛看出來 ```javascript! 可以試試看不靠記憶去讀,這樣才不會一直寫的東西都因為你知道,所以看得出來,但別人看不出來 ``` ### 哲學問題 - 檔案分類方式,不要為了分檔案分檔案 ```javascript! 1. 你喜歡就好 2. 都嘗試看看選個好讀的 3. 方便除錯 但是之後你要選一個對自己來說"最簡單"的為優先(先簡單再好用) 不知道要選什麼,就隨便選一個方法,去體驗:為什麼要分開?分開是適合的嗎?之後不夠用,就知道另外一個好處是什麼了。 ``` ### 目前我的可以怎麼分檔 ```javascript! 可以試試看用 verify 就好,每隻的 validation 不用分(放到Q5),Q5 拉出去 import { checkInputValidity } from './modules/validation.js'; import { getUmlevenSum, getUmlevenFormula } from './modules/umleven.js'; 這兩個就會出現在 Q5 ``` ### 滿幾個月了,為什麼大家 J1 都寫超過一個月XD ```javascript! 不用把所有語法弄懂再繼續下去,趕快把它寫一寫 但重要的是:你的想法要跟語法一致! - function: 四種寫法可以就好(建構式、一般呼叫、method、立即執行) 忍者一和二以 ES6 為分水嶺(推忍者,其他都是輔助書) 型別: 盡量去認識就好,有遇到記得,有遇到記得,不用急著一次把他認識完 多的是機會,多的是認識這語法的機會~ 認為覺得去認識測試,語意語法合在一起,時間拉長、熟悉度會提高,因為這是沒有標準答案的問題,需要時間去醞釀,語法就是看語法書學就好 鼓勵有問題馬上問,不用等看 code 的時候問即時問 記憶新鮮度降低,就不會覺得是痛點了,所以馬上問是最好的。 結論:速度是正常的,不用擔心。 ``` ### 要學哪個框架? ```javascript! 學框架重要的是學觀念。 (React 文件寫得比較好) 學哪一個都沒關係,會 Vue 也可以找 React 和 Angular 工作。 求職重要的是看你可不可以很快學一個東西。 ```