# React 筆記 --- ### 建立 typescript 專案 :::info ```nodejs npx create-react-app 專案名稱 --template typescript ``` 專案路徑: **目前資料夾/專案名稱** ::: :::success 如果想以目前資料夾當作專案路徑, 要以 "**.**" 取代專案名稱 ```nodejs npx create-react-app . --template typescript ``` ::: :::warning 專案名稱含有大寫字母, 無法成功建立專案, 必須使用全小寫 ``` name can no longer contain capital letters ``` ::: --- ### 客製設定 :::success 因為會永久改變專案設定, 需要先執行 git commit 後才能進行 ```nodejs npm run eject ``` 完成後, 專案目錄會多了 **config** 資料夾 e.g. 修改 index.html 路徑要修改 config/path.js [參考文件](https://create-react-app.dev/docs/available-scripts/#npm-run-eject) ::: --- ### 測試或發佈 :::success #### 測試 (development mode) 啟動測試 server 讀取 public/index.html ```nodejs npm start ``` ::: :::success #### 發佈 預設輸出到 build 資料夾, 會最小化壓縮 JS, CSS...etc ```nodejs npm run build ``` ::: --- ### return JSX 建議用圓括號包起來 :::warning We split JSX over multiple lines for readability. While it isn’t required, when doing this, we also recommend wrapping it in parentheses to avoid the pitfalls of [automatic semicolon insertion](https://stackoverflow.com/q/2846283). [來源](https://reactjs.org/docs/introducing-jsx.html#embedding-expressions-in-jsx) ::: --- ### <></> 同等於 <React.Fragment></React.Fragment> :::warning function 只回傳單一 JSX element 如果最外層是多個同層級的 element, 必須透過 <></> 包裝 **JSX expressions must have one parent element.** ::: ```jsx= 自訂class { ... render() { return ( <> <元件1> ... <元件N> <> ); } } ``` --- ### 自定義 component 命名必須首字是大寫 :::warning 小寫會被 react 當成 DOM tag [來源](https://reactjs.org/docs/jsx-in-depth.html#user-defined-components-must-be-capitalized) ::: --- ### ES6 class :::warning 使用 export default 的場合 ```jsx= import React from "react"; export default class MainMenu extends React.Component{ render(){ return <> </> } } ``` 使用 export default (每個檔案最多一個) ```jsx import MainMenu from "相對路徑"; ``` 未使用 export default ```jsx import { MainMenu } from "相對路徑"; ``` [參考來源](https://stackoverflow.com/a/31853887) ::: --- ### componentDidMount :::info 當 component 插入到 DOM 之後會呼叫的 function 觸發時間點比建構子晚, 類似 unity 的 Start() ::: :::warning 如果有自己的事件管理, 通常要在這個階段才註冊事件 ::: --- ### componentWillUnmount :::info 當 component 從 DOM 移除之後會呼叫的 function 類似 unity 的 OnDestroy() ::: --- ### state :::warning 更新狀態不要直接修改, 要透過 setState() 變更才會重新繪製 ::: :::warning state 採用非同步更新, 需要透過 delegate function 才能正確將計算結果寫入 state [參考文件](https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous) ::: --- ### 事件註冊 :::success 註冊一般 function 需要綁定 class ```jsx= import React from "react"; export default class TestButton extends React.Component { constructor(prop:any){ this.onButtonClick = this.onButtonClick.bind(this); } onButtonClick(){} } render() { return <button onClick={this.onButtonClick}>click</button> } ``` ::: :::success 透過變數儲存匿名委託 function 則不需要額外綁定 (相當好用) ```jsx= import React from "react"; export default class TestButton extends React.Component { constructor(prop:any){} onButtonClick = () => {} } render() { return <button onClick={this.onButtonClick}>click</button> } ``` 需要傳遞參數 ```jsx= ... onButtonClick = (msg:string) => { console.log(msg); } ... render() { return <button onClick={this.onButtonClick.bind(this, "hello world")}>click</button> } ``` 需要傳遞事件+參數 ```jsx= ... onButtonClick = (msg:string) => { console.log(msg); } ... render() { let callback = (evt:React.MouseEvent<HTMLButtonElement>) => { //可在這裡處理, 或是丟到 onButtonClick() 處理 evt.preventDefault(); this.onButtonClick("hello world"); } return <button onClick={callback}>click</button> } ``` ::: :::danger 直接呼叫匿名委託 function (不推薦) 每次點擊都會產生新的匿名委託 function, 會影響效能 ```jsx= import React from "react"; export default class TestButton extends React.Component { constructor(prop:any){} onButtonClick = () => {} onButtonClick2 = (msg:string) => { console.log(msg); } } render() { return ( <> <button onClick={() => this.onButtonClick()}>click</button> <button onClick={() => this.onButtonClick2("hello world")}>click2</button> </> ); } ``` [參考文件](https://reactjs.org/docs/handling-events.html) ::: --- ### Expected '\=\=\=' and instead saw '\=\=' eqeqeq :::success 條件式不是判斷 null 的場合 用 <font color=red>**\=\=\=**</font> 取代 <font color=red>**\=\=**</font> 用 <font color=red>**!\=\=**</font> 取代 <font color=red>**!\=**</font> ::: --- ### 自訂 component props 和 state 型別 :::spoiler 以下例子, 需要在繼承 React.Component 就指定 props type, 否則在上層指定傳入參數的部分會出現編譯錯誤 另外後面可指定 state type ```jsx= import React from "react"; type TestParam = { //something } type TestState = { //something } export default class Test extends React.Component<TestParam, TestState> { constructor(props: TestParam){ this.state = { //set something } } } ``` props 或 state 需要儲存 callback 的情況 ```jsx= type TestParam = { //button 回傳事件 callback: (evt:React.MouseEvent<HTMLButtonElement>) => void; } ``` [參考來源](https://stackoverflow.com/a/48241131) ::: --- ### 發佈之後, 實際執行的資源路徑錯誤 假設發佈後的檔案會放在 main ``` http://hostname/main/index.html http://hostname/main/static/js/main.OOOO.chunk.js ``` 實際載入檔案的路徑少了 main 這一層 ``` http://hostname/main/index.html http://hostname/static/js/main.OOOO.chunk.js ``` :::success 解決方法: 需要在 package.json 加上 homepage 路徑 ``` { "name": 專案名稱, ... "homepage": "./", } ``` ::: --- ### html 引入外部 js, 在預覽模式出現 Uncaught SyntaxError: Unexpected token '<' :::danger * 很高的機率是抓不到檔案, 被重新導向到 index.html 本體 * 需要先檢查瀏覽器實際載入的檔案內容是否正確 ::: :::warning * 預覽模式的 html 相關檔案, 預設放在 public 資料夾 * 放在 public 之外的檔案, 在預覽模式都無法抓到 ::: :::success 預覽模式的路徑調整, 要修改 config/path.js 的欄位 ``` appPublic: resolveApp('public/'), appHtml: resolveApp('public/index.html'), ``` ::: --- ### 使用 SASS :::warning 需要先安裝 node-sass ```nodejs npm install node-sass --save ``` 如果安裝失敗, 碰到 dependency conflit 的話 1. 先刪除整個 node_modules 資料夾 2. 重新下載所有 package ```nodejs npm install --force ``` ::: 成功之後, 可將 src 資料夾底下新增 .scss 或 .sass 檔案 :::info 假設將預設的 App.css 改成 App.scss 並且內容已替換成 SASS 寫法 接下來修改 App.tsx 的 import path ```jsx import "./App.scss"; ``` [參考文件](https://create-react-app.dev/docs/adding-a-sass-stylesheet/) ::: --- ### element 事件 :::warning 規則 * class 內的事件使用 <font color=red>**React.行為Event<HTML元件類型>**</font> * class 外 function 傳入參數使用 <font color=red>**React.行為EventHandler<HTML元件類型>**</font> * function 內則跟 class 內相同 ```jsx= //class 外 function function test( aFn: React.MouseEventHandler<HTMLSpanElement>, bFn: (index: number) => void ): JSX.Element { let clickB = (evt: React.MouseEvent<HTMLButtonElement>) => { evt.preventDefault(); bFn(index); }; return ( <> <span onClick={aFn}>AAA</span> <button onClick={clickB}>{name}</button> </> ); } ``` ::: | 事件 | 回傳 | | -- | -- | | onClick | React.MouseEvent<元件類型> | | onChange | React.ChangeEvent<元件類型> | --- ### 自訂 css 沒有複寫 bootstrap 的樣式 :::warning 查看瀏覽器 Element, 通常是載入順序的問題 如果是在 html 引入 bootstrap, 將它移到 html head 區塊 ::: --- ### window.postMessage(e: MessageEvent) :::warning react 會在 development mode, 透過此事件傳遞資料 如果需要使用 postMessage, 必須自行忽略 react 的相關訊息 目前已知 react 傳遞 MessageEvent 的 data 會是 object ::: --- ### 子物件缺少 key :::warning react Each child in a list should have a unique "key" prop ::: :::success 透過陣列顯示, 需要為每個子物件加上不重複的 key [參考文件](https://reactjs.org/docs/lists-and-keys.html#basic-list-component) ::: --- ## Block is redundant no-lone-blocks :::success 需要移除多餘的 <font color=red>**{ }**</font> ::: --- ## img 缺少文字 :::warning img elements must have an alt prop, either with meaningful text, or an empty string for decorative images ::: :::success alt 補上文字或空字串 ```htmlembedded= <img src="圖片網址" alt="" /> ``` ::: ___ ## state hook 指定型別 ```jsx= import React from "react"; export function test(): JSX.Element { const [msg, setMsg] = React.useState<string>(""); clickHandler = (evt:React.MouseEvent<HTMLButtonElement>) => { setMsg("clicked")l }; return ( <button onClick={clickHandler}>click</button> ); } ``` --- ###### tags: `React`