# 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,這樣就會產生樹狀的結構,舉例來說: ![image](https://hackmd.io/_uploads/BJzFjtN0a.png) 這張圖中, 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 把不能訪問的部分變暗。