# React-Hooks or function
紀錄一些比較少用到的 hooks 或函式,有些是在課程
https://www.udemy.com/course/react-the-complete-guide-incl-redux/?couponCode=LETSLEARNNOW 中學到
## useContext
可以想像成是一個全域變數,被這個 context 的 provider 包覆住的元件(包含元件內的子元件)都可以使用這裏面的內容,最常被使用在是傳入一些 state 和修改 state 的函式,可以用來避免 props drilling的情況。
> props drilling 的情況就是假如有一個變數或函式要在比較內層的子物件中使用,可能就必須透過 props 來一層層傳遞到下層,即使中間這些傳遞的物件並沒有使用到這裏面的變數或函式。這樣不僅不直觀,也會讓整個 props 變的冗長。
### 使用方法:
首先,要先創建一個 context ,一般會建議新增一個檔案然後把這個 context export 出去。
例如:
```jsx
import { createContext, useContext } from "react";
export const CartContext = createContext({
items: [],
addItemToCart: ()=>{}
})
```
接下來,將這個 context import 後,在最外層的地方使用 provider 包覆,例如:
```jsx
const ctxValue = {
items:shoppingCart.items,
addItemToCart: handleAddItemToCart
};
return (
<CartContext.Provider value = {ctxValue}>
<Header
cart={shoppingCart}
onUpdateCartItemQuantity={handleUpdateCartItemQuantity}
/>
<Shop onAddItemToCart={handleAddItemToCart} />
</CartContext.Provider>
);
```
:::warning
記得要加入 provider 和 value,如果不加 provider 可能會讀到預設值,不加 value 會讀到 undefined。
參考: https://react.dev/reference/react/useContext
:::
接下來,內部的元件如果要使用這個 context ,有兩種方法:
1. 透過 useContext 來使用 (推薦):
剛才已經透過 provider 提供了 context,接下來就可以透過
```jsx
const {addItemToCart} = useContext(CartContext)
return (
<article className="product">
<img src={image} alt={title} />
<div className="product-content">
<div>
<h3>{title}</h3>
<p className='product-price'>${price}</p>
<p>{description}</p>
</div>
<p className='product-actions'>
<button onClick={() => addItemToCart(id)}>Add to Cart</button>
</p>
</div>
</article>
);
```
來使用 context 的內容
> 此處透過解構來使用,當然要寫成 useContext 獲取整個 CartContext 的內容再透過 `.addItemToCart `使用也可以
2. 透過 consumer 使用:
另一種方法是透過 consumer 使用,但這樣寫可能會讓程式比較難讀。
記住在 Consumer 內必須有一個函式回傳內容,而 cartCtx 就是獲取的 context。
```jsx
return (
<CartContext.Consumer>
{(cartCtx) => {
return (
<article className="product">
<img src={image} alt={title} />
<div className="product-content">
<div>
<h3>{title}</h3>
<p className='product-price'>${price}</p>
<p>{description}</p>
</div>
<p className='product-actions'>
<button onClick={() => cartCtx.addItemToCart(id)}>Add to Cart</button>
</p>
</div>
</article>
)
}}
</CartContext.Consumer>
);
```
可以看到為了使用會變得比較冗長,因此一般還是建議使用 useContext。
## useReducer
可以想成用來操作比較複雜的邏輯時可以使用的 state,舉例來說:
```jsx
import React from 'react;'
function App() {
const [count,dispatch] = React.useReducer(counterReducer,0);
return (
<div id="app">
<h1>The (Final?) Counter</h1>
<p id="actions">
<button onClick = {()=>dispatch("increase")}>Increment</button>
<button onClick = {()=>dispatch("decrease")}>Decrement</button>
<button onClick = {()=>dispatch("reset")}>Reset</button>
</p>
<p id="counter">{count}</p>
</div>
);
}
export default App;
```
首先要宣告一個 function ,這個 function 是用來定義當接收到 action 時,應該做甚麼動作,在這個範例中就是 counterReducer。
```jsx
export function counterReducer(state, action) {
if(action === "increase"){
return state + 1;
}
if(action === "decrease"){
return state - 1;
}
if(action === "reset"){
return 0;
}
return state;
}
```
使用時類似 useState,useReducer 包含兩個參數,第一個是前面定義行為的function,第二個是初始值。
```jsx
const [count,dispatch] = React.useReducer(counterReducer,0);
```
接下來,假如想使用這個 reducer(例如按下按鈕 +1):
```jsx
<button onClick = {()=>dispatch("increase")}>increase</button>
```
dispatch function 類似於 setState,但傳入的是要執行的行為,如果透過 useState,可能必須分開寫成三個函式,分別是:
```jsx
setCount(prev=>prev + 1)
setCount(prev=>prev - 1)
setCount(0)
```
儘管實際寫的程式碼沒有變少,但可以讓程式碼的可讀性提高。
## memo
提到 memo 之前,要先說明 react 中各種元件更新的方法:
首先,component 中如果又有其他 custom component,這樣就會產生樹狀的結構,舉例來說:

這張圖中, App component 又用了兩個 custom component,而這些 custom component 又用了其他的 compnent。
假設此時 APP component 中的 state 變動,這會讓 APP component 和他的兩個子元件重新渲染。
但考慮到另一個情況,假如 App component 的變動實際上和他的子元件沒有關係,但這些元件還是會被重新渲染,這明顯不太符合效益。
也因此出現了 memo,這是 react 中提供的函式,用法是:
```jsx
Counter = memo(function Counter({ initialCount }) {
//...return jsx
});
export default Counter;
```
實際上就是用 memo 把原本的 function component 包起來而已,使用 memo 的function component 會在 parent component 重新渲染時檢查自己的 props 是否被變動,如果有變動,function component 才會重新被執行。
聽起來那好像把所有 component 都用 memo 包起來效能會最好,但實際上,用了 memo 相當於每次都要對 props 進行比對,實際上也會對效能造成影響,因此使用 memo 時要注意下面幾點:
1. 如果是會頻繁被 parent component 影響的 component,那不要使用 memo,否則已經要頻繁更新了,還要浪費效能去進行比對。
2. 盡可能在 tree 的上層使用,因為上層不更新時,下面的 component 也不會更新。
3. memo 只針對外部,component 自己的 state 更新時當然還是會重新渲染。
## useCallback
前面講到 memo 可以避免在 props 沒有改變時還重新渲染的情況,但有一點要注意,就是如果傳入的 props 是一個函式,那這個函式會在重新被渲染時再次創建,這會導致其 child component 即使加上了 memo 還是被重新渲染,因為函式記憶體位置變了。
要解決這個問題,可以透過 useCallback,使用方法類似 useEffect:
```jsx
const func = React.useCallback(function(){
console.log("strong");
},[a,b])
```
這個 function 在 a,b 沒有被改變時不會被重新創建,記憶體位置也不會變動,如此一來就可以避免前面提到的問題。
## useMemo
useMemo 是用來阻止複雜的運算,以下是一個範例,假設下面的程式碼是在一個 component 內,n 是某個 props的內容:
```jsx
const num = complex_function(n);
```
如果這個 complex_function 的運算很複雜,當然會希望避免重複運算,因此可以透過 useMemo:
```jsx
const num = useMemo(()=>complex_function(n),[n])
```
和 useCallback、useEffect 相同,useMemo 第一個參數是一個有回傳值的函式,第二個則是 dependency array,裡面的內容有變才會被重新運算。
## createPortal
這是一個在 react-dom 中的函式,最常被應用的地方是彈窗,語法為:
```jsx
createPortal(children, domNode, key?)
```
這允許這個 component 出現在 dom-tree 的任何地方,而不只是包含在當前這個 component。
舉例來說,如果想要有一個彈出視窗出現在最外面,可以使用:
```jsx
{showModal && createPortal(
<ModalContent onClose={() => setShowModal(false)} />,
document.body
)}
```
這就代表把 ModalContent 這個 component 插入到元素中(如果要插入在別的位置,可以用 getElementByID)。
另外可以搭配 dialog 標籤使用,例如
```jsx
function Modal({ open, children, onClose }) {
const dialog = useRef();
useEffect(() => {
if (open) {
dialog.current.showModal();
} else {
dialog.current.close();
}
}, [open]);
return createPortal(
<dialog className="modal" ref={dialog} onClose={onClose}>
{open ? children : null}
</dialog>,
document.getElementById('modal')
);
}
```
這會把這個 component 插入到 modal 這個 dom 內,並且可以透過 showModal 把不能訪問的部分變暗。