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