總結
快速入門 React 後,將其 JS 部分改為 TypeScript,記錄下相關筆記以及轉換途中遇到的坑 另外因為已經學過 Vue,故以下筆記會包含一些與 Vue 類似或相異的比較
JS 版原始碼:https://github.com/tzynwang/react-practice/tree/speed-converter TS 版原始碼:https://github.com/tzynwang/react-practice/tree/speed-converter-ts
教材內容取自:從 Hooks 開始,讓你的網頁 React 起來
版本
react: 17.0.2typescript: 4.4.4React 部分
- React components are regular JavaScript functions, but their names must start with a capital letter or they won’t work! 作為元件的 function 其名稱一定要大寫開頭
JSX
-
基本上接收任何資料(strings, numbers, and other JavaScript expressions),需注意在進行條件判斷的時候只能使用 expressions
App = () => {const [userInput, setUserInput] = useState(0);const handleUserInput = (e) => {setUserInput(e.target.value);};return (<div className="container"><div className="card-header">Network Speed Converter</div><div className="card-body"><UnitControl /><UnitConverteruserInput={userInput}handleUserInput={handleUserInput}/></div><CardFooter userInput={userInput} /></div>);};const UnitConverter = ({ userInput, handleUserInput }) => {return ({/* 略 */}<inputtype="number"className="input-number"min="0"value={userInput}autoFocusonChange={handleUserInput}/>{/* 略 */})}handleUserInput透過 props 傳給UnitConverter元件,UnitConverter在 onChange 事件發生時,即可透過handleUserInput修改userInput的值 -
放 CSS Object 的話就多包一層
{}const TodoList = () => {return (<ulstyle={{backgroundColor: "black",color: "pink",}}><li>Improve the videophone</li><li>Prepare aeronautics lectures</li><li>Work on the alcohol-fuelled engine</li></ul>);}; -
JSX looks like HTML, but under the hood it is transformed into plain JavaScript objects. You can’t return two objects from a function without wrapping them into an array. This explains why you also can’t return two JSX tags without wrapping them into another tag or a fragment. 每個 react 元件只能回傳單一內容,超過一個元素請用
<></>包起來;不能回傳超過一個元素是因為這些元件最後會被轉為 JS Object,複數個元素在 return 時一定要打包成單一元素 -
JSX requires tags to be explicitly closed: self-closing tags like
<img>must become<img />, and wrapping tags like<li>orangesmust be written as<li>oranges</li>. 所有的HTML 元素一定要 closed(舉例:<br>要改寫為<br />) -
JSX turns into JavaScript and attributes written in JSX become keys of JavaScript objects.
useState()
- Returns a stateful value, and a function to update it.
- 放到
useState(...)裡的變數才會被 React 追蹤,概念類似資料要放在data()中才會被 Vue 追蹤、修改後才會跟著驅動畫面更新
改裝為 TS
將 TS 加入專案中
透過以下手續將原本是 JS 的 React 專案改為 TS 版本:
npm install --save typescript @types/node @types/react @types/react-dom @types/jest- package.json 中的
scripts/build內容改為tsc npx tsc --init- 根據實際情況調整 tsconfig.json 中
rootDir與rootDir的設定(官方文件預設為src與build) - 設定
jsx為preserve(參考討論串:Error when opening TSX file: Cannot use JSX unless ‘—jsx’ flag is provided) .js副檔名改為.ts或.tsx(enable JSX with TypeScript,參考TS 官方文件)
完整說明參考 React 官方文件:Adding TypeScript to a Project
備註:如果是要從零開始一個 React+TS 專案的話,直接npx create-react-app <app-name> --template typescript即可
將原始碼改為 TS
-
偷吃步:在 vs code 中如果不知道該 event 的類型,可以在
{}中寫出 e 然後透過滑鼠 hover 來查看 event 類型
-
需注意:在透過
e.target.value取使用者輸入的值(value)的時候,取到的value會是string型態資料- MDN
<input>element value: It can be altered or retrieved at any time using JavaScript to access the respectiveHTMLInputElementobject’s value property. - MDN
HTMLInputElementvalue property: It is string, returns / sets the current value of the control. - 所以
handleUserInput在改寫成 TS 版加上的 type 必須為string:
const handleUserInput = (e: ChangeEvent<{ value: string }>) => {const rawInput = Number(e.target.value);if (isNaN(rawInput)) return;setUserInput(rawInput);}; - MDN
-
在將 function
handleUserInput透過 props 傳給元件UnitConverter的時候想不通「function 的類型究竟是什麼」,透過關鍵字搜尋後發現與其說是 function 的「type」是什麼,不如說是告訴 TS「這個 function 的 shape 是什麼形狀」// in UnitConverter.tsxinterface propsType {userInput: number// handleUserInput的形狀是:接受e做為參數傳入,並「不回傳」任何值,所以 => voidhandleUserInput: (e: ChangeEvent<{ value: string }>) => void}const UnitConverter = ({ userInput, handleUserInput }: propsType) => {return ({/* 略 */}<inputtype="number"className="input-number"min="0"value={userInput}autoFocusonChange={handleUserInput}/>{/* 略 */})} -
使用 deconstruct 技巧處理 props 又必須給予 type 時,可使用以下寫法:
const CardFooter = (props: { userInput: number }) => {/* 略 */};或是這樣:
const CardFooter = ({ userInput }: { userInput: number }) => {/* 略 */};或是這樣:
interface userInputType {userInput: number;}const CardFooter = ({ userInput }: userInputType) => {/* 略 */};以上做的都是「設定 props 中 userInput 的類型為
number」同一件事情
關於 React.FC
參考 React+TypeScript Cheatsheets,小總結:不建議使用React.FC,可改用React.VFC;React.FC與一般 function 不同的部分如下列:
React.FunctionComponentis explicit about the return type, while the normal function version is implicit (or else needs additional annotation).- 引用自 PJ:
React.FC會明確定義 Function Component 回傳的型別一定要是JSX.Element或null而不能是undefined。 - It provides typechecking and autocomplete for static properties like
displayName,propTypes, anddefaultProps. Note that there are some known issues usingdefaultPropswithReact.FunctionComponent. - It provides an implicit definition of children - however there are some issues with the implicit children type (e.g. DefinitelyTyped#33006), and it might be better to be explicit about components that consume children, anyway.
- 引用自 PJ:
React.FC會自動接受children作為可以傳入的 props,即使在這個 React 元件中並不會使用到children。
補充
named export, default export
-
If a module defines a default export, then you can import that default export by omitting the curly braces.
foo.js export default function foo() {console.log("hello!");}import foo from "foo";foo(); // hello! -
A file can have no more than one default export, but it can have as many named exports as you like. export default 只能有一個,但 named exports 可以有無數個
-
When you write a default import, you can put any name you want after
import. For example, you could writeimport Banana from './button.js'instead and it would still provide you with the same default export. -
In contrast, with named imports, the name has to match on both sides. That’s why they are called named imports!
