---
# System prepended metadata

title: React 筆記
tags: [React]

---

# 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`