# [React] Use Hook ## useState 一般變數在狀態改變後並不會觸發畫面的重新渲染 (Re-render), `useState` 會紀錄 `state`,並透過 `setState()` 來改變 `state` 狀態, 並在狀態改變後 Re-render,基於 React Fiber Architecture, 支持異步 (Asynchronized) 渲染避免畫面阻塞以提高 UX, 並基於 Diffing Algorithm,藉由比較 Virtual DOM 後, 最小幅度的更新 Actual DOM 以達到效能最佳化。 ### 語法介紹 ``` javascript= import React, { useState } from 'react'; const [state, setState] = useState(initialState); ``` - state 為我們要設置的狀態 - setState 為更新 state 的方法,命名依照專案的需求而定 - initialState 為初始的 state,可以是任意的資料型別,但注意最後要 return 回一個值 ### 範例程式碼 ```javascript= import React, { useState } from 'react'; function Couunt{ const [count, setCount] = useState(0); return ( <> <p>You have clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click </button> </> ); } export default Count ``` ## useEffect `useEffect` 主要用在函式型元件中處理副作用, 副作用指的是那些與 React 的純渲染邏輯無關的操作, 包含 data fetching 或 subscriptions 等,這些行為應該寫在 `useEffect` 裡 ### 使用情況 - Event Listeners - DOM Manipulation - Subscriptions - Fetching Data - Clean Up ### 語法介紹 ``` javascript= import React, { useEffect } from 'react'; //前置工作 useEffect(function, [dependencies]); 形態一: useEffect(() => {}) 形態二: useEffect(() => {}, []) 形態三: useEffect(() => {}, [value]) ``` - function:用於處理副作用 (Side Effect),function 會在 component render 完之後才執行 - dependencies(可寫可不寫):決定了什麼情況下 useEffect 內的函式會被調用。如果省略第二個參數,每次元件渲染後都會執行 ### 範例程式碼 ```javascript= import React, { useState, useEffect } from 'react'; function Couunt{ const [count, setCount] = useState(0); useEffect(() => { // 副作用操作:添加事件監聽器 const handleClick = () => { console.log("Document was clicked!"); }; document.addEventListener("click", handleClick); // 返回的函式用來清理副作用:移除事件監聽器 return () => { document.removeEventListener("click", handleClick); console.log("Cleaned up event listener."); }; }, []); return ( <> <p>You have clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click </button> </> ); } export default Count ``` ## useContext `useContext` 允許你在元件樹中共享狀態或資料, 而不需要透過層層傳遞 props,解決了過度 `prop drilling`(屬性傳遞)的問題 ### 作用&特點 - 簡化資料傳遞: 通常我們會透過 props 來在元件之間傳遞資料,但當元件 嵌套層數較深時,props 傳遞會變得繁瑣。`useContext` 使得子元件能夠直接獲取父元件或更高層級元件中提供的資料,而不需要一層一層地傳遞。 - 跨層級共享狀態:當某個資料需要在多個不同層級的元件之間共享時,`useContext` 可以讓你在不經過中間元件的情況下,直接從提供者(Provider)那裡獲取該資料。 ### 語法介紹 - ContextProvider.jsx ``` javascript= import React, { createContext } from 'react'; export const MyContext = createContext(); export const ContextProvider = ({ children }) => { const [ state, setState ] = useState(0); return ( <MyContext.Provider value={{ state, setState }}> { children } </MyContext.Provider> ); }; ``` - App.jsx ``` javascript= import { useContext } from 'react'; import { MyContext } from './component/ContextProvider.jsx'; export const App = () => { const { count, setCount } = useContext(MyContext); return ( <button onClick={ () => setCount(count + 1) } > <p>Click Me: { count }</p> </button> ) }; ``` ## useRef React官方文件的useRef定義: ***useRef*** is a React Hook that lets you reference a value that’s not needed for rendering. 與state相似的功能是暫存值,但不同的是useRef裡的值更新了也不會造成畫面的re-render,也就不會影響到畫面的更新流程。 簡單來說,useRef 可以在不引發畫面重新渲染的情況下,保存和更新資料。 使用useRef狀況如下: ### Referencing a value with a ref(使用ref對照值) 建立一個「可變」的物件,這個物件的 .current 屬性會被初始化為我們提供的值。即使在組件重新渲染之後,useRef 依然會回傳同一個物件,而這個物件的 .current 屬性可以被改變,並且不會導致畫面重新渲染。 這使得 useRef 非常適合用來儲存不會影響 UI 的資料,例如:計時器的 ID、DOM 元素的引用等。 1.useRef 必須在組件的頂層呼叫,這樣每次重新渲染時,都能保持一致的參考。 ```jsx= import { useRef } from 'react'; function Stopwatch() { const intervalRef = useRef(0); // ... } ``` intervalRef 是用 useRef 創建的一個「可變」物件。useRef 會回傳一個物件,這個物件有一個 current 屬性,這個屬性在初次渲染時會被設定為我們提供的初始值。 2.回傳同一個物件 在後續的渲染過程中,useRef 都會回傳相同的物件,這意味著不論組件如何更新或重新渲染,intervalRef 指向的都是同一個物件。你可以透過 intervalRef.current 來存取並更改其值。 常用範例: #### 存取最新的值 我們要在一段時間(例如 5 秒)後取得某個狀態的最新值。 ```jsx= const [timeoutCount, setTimeoutCount] = useState(0); const countRef = useRef(count); countRef.current = count; const getCountTimeout = () => { setTimeout(() => { setTimeoutCount(countRef.current); }, 5000); }; ``` 為什麼要用 useRef 而不是 useState? 當 setTimeout 被創建時,它會使用當時 count 的值,並在一個閉包(closure)中保持這個值。即使在之後 count 改變了,這個閉包中的 count 值依然是創建時的舊值。因此,當 setTimeout 完成 5 秒後,你可能以為會取得更新後的 count 值,但事實上取到的是閉包中的舊值。 為了解決這個問題,我們使用 useRef。每次 count 更新時,我們都同步更新 countRef.current,這樣在 setTimeout 觸發後,我們能夠取得最新的 count 值。 ### Manipulating the DOM with a ref ### Avoiding recreating the ref contents 使用 useRef 可以幫助你避免在重新渲染 component 時不必要地重建 ref 的內容,因為 ref 會在 re-render 中保持不變,例如一些資源很大的影音 component 就可以使用此方法,避免重複拿取資源、更新畫面。 範例: ```jsx= function Video() { const playerRef = useRef(new VideoPlayer()); // 每次渲染都會調用 new VideoPlayer // ... } ``` 即使你只需要 new VideoPlayer() 在初始渲染時執行,但這樣的寫法會在每次重新渲染時都調用 new VideoPlayer(),這會浪費資源,尤其當 VideoPlayer 是個複雜的物件時,效能影響會更加明顯。 解決方法: ```jsx= function Video() { const playerRef = useRef(null); // 初始為 null if (playerRef.current === null) { playerRef.current = new VideoPlayer(); // 只在初始時創建 } // ... } ``` 這樣寫的好處是: 效能提升:只有在初次渲染時才創建 VideoPlayer,避免重複執行耗時操作。 符合 React 原則:雖然通常在渲染過程中讀取或寫入 ref.current 不推薦,但在這種情況下 ,因為 VideoPlayer 只在初始時創建,行為是可預測的,符合 React 的設計原則。 ## useNavigate ## useLocation useLocation 會回傳一個 Object 的物件, 裡面包含 pathname, search, hash, key 與 state 等資訊,可以透過 Link 的 to 屬性, 設定它 的 pathname 和 state, 我們就可以在 Settings Component 透過 useLocation 的 Hook 輕易的取得 pathname 和 state。 ```javascript= import { useLocation} from "react-router"; const Settings = () => { let location = useLocation(); console.log(location); return <div>settings component</div>; }; function App() { return ( <div className="App"> <Router> <ul> <li> <Link to={{ pathname: "/settings", state: { fromNavBar: true } }} > Settings </Link> </li> </ul> <Switch> <Route exact path="/" component={Home} /> <Route path="/settings" component={Settings} /> </Switch> </Router> </div> ); } // console // { // "pathname": "/settings", // "state": { // "fromNavBar": true // }, // "search": "", // "hash": "", // "key": "x8vmq3" // } ``` ### 屬性 #### pathname pathname 屬性返回當前組件的路徑名稱,例如 http://localhost:5173/products 的路徑名是 /products。 ```javascript= // URL: http://localhost:5173/products const location = useLocation(); console.log(location.pathname); // 輸出: /products ``` #### state state 屬性用於在不同路由之間傳遞資料,這些資料不會顯示在 URL 上,適合傳遞一些不想暴露在 URL 中的敏感或臨時資料。state 屬性特別適合在父組件設定資料,然後在導航到不同的子組件時存取這些資料。 ```javascript= // 在導航過程中設置 state <Link to="/products" state={{ fromHome: true }}>Go to Products</Link> // 在目標路由組件中存取 state const location = useLocation(); console.log(location.state.fromHome); // 輸出: true ``` #### search search 屬性返回當前 URL 中的查詢字串,從 ? 開始到結束,包括參數和值。這些參數通常用來過濾或傳遞資訊。 ```javascript= // URL: http://localhost:5173/products?age=20&name=alex const location = useLocation(); console.log(location.search); // 輸出: ?age=20&name=alex ``` #### key key 屬性是當前位置的一個唯一標識符,當路由改變時,key 會生成一個新的值,這個屬性對於實現動態頁面內容的刷新或依賴路由變化的操作非常有用。 可以用來追踪瀏覽歷史中的位置變化,或在頁面變化時重置某些狀態。 ```javascript= const location = useLocation(); console.log(location.key); // 輸出: 一個隨機生成的唯一鍵值 ``` #### hash hash 屬性通常指的是 URL 中的片段標識符,它由 # 符號後面的部分構成(例如 http://example.com/#section1 中的 #section1)。 hash 常用於在同一頁面內進行導航,特別是在單頁應用 (SPA) 中。 作用: 片段導航:hash 通常用於標識網頁中的某個部分,點擊鏈接後瀏覽器會自動滾動到對應的部分。 例如,如果網頁上有多個章節,點擊鏈接 #section1 會將頁面自動滾動到標記為 id="section1" 的元素。 不刷新頁面:當 hash 改變時,瀏覽器不會重新加載整個頁面,這讓單頁應用的用戶體驗更流暢。 使用 HashRouter 實現基於 hash 的路由導航: ```javascript= import { HashRouter, Route, Routes } from 'react-router-dom'; function App() { return ( <HashRouter> <Routes> <Route path="/home" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </HashRouter> ); } function Home() { return <h2>Home Page</h2>; } function About() { return <h2>About Page</h2>; } ``` 當訪問 http://example.com/#/home 時,Home 組件會被渲染,而不是重新加載整個頁面。 ## Route/Router Route 和 Router 是 React Router 的兩個核心概念,負責管理應用中的路由,並在單頁應用(SPA Single-Page Application)中實現不同頁面之間的導航。可以輕鬆地基於 URL 變化來展示不同的組件。 Route:用於將 URL 路徑與特定組件綁定,當路徑匹配時渲染組件。 Router:負責監聽 URL 變化並渲染對應的組件。 ### Route Route 負責將特定的 URL 路徑映射到對應的 React 組件上。當 URL 與 Route 的 path 屬性匹配時,對應的組件就會被渲染。 Route 可以設置路徑(path)、組件(element 或 Component)、加載器(loader)和動作(action)等屬性來定義複雜的應用邏輯。 主要屬性: #### path: 指定 URL 模式,當 URL 匹配時,該路由將被啟用。:teamId 是動態路由參數,可以匹配不同的 teamId。 #### element: 指定要渲染的 React 組件。 #### loader: 這是一個函數,負責在渲染組件之前加載數據。數據加載時可以使用動態參數(如 params)來從 API 中獲取對應的數據。 #### action: 用於處理表單提交,當用戶向該路徑提交數據時,會觸發這個函數。 #### errorElement: 當渲染或數據加載過程中發生錯誤時,渲染此組件。 ### Router React Router 提供了多種類型的 Router,最常用的是 BrowserRouter 和 HashRouter兩種路由模式。 .BrowserRouter:使用 HTML5 的歷史 API(例如 pushState 和 popState)來管理 URL。它適合處理正常的路徑和路由。 .HashRouter:使用 URL 中的 # 符號來模擬不同頁面,例如 /home#section1,適合不支持 pushState 的情況,通常用於不需要服務器處理的靜態頁面。 BrowserRouter 範例: ```jsx= import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; function App() { return ( <Router> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> <Route path="/contact" element={<Contact />} /> </Routes> </Router> ); } function Home() { return <h1>Home Page</h1>; } function About() { return <h1>About Page</h1>; } function Contact() { return <h1>Contact Page</h1>; } export default App; ``` BrowserRouter包裹了整個應用,這允許我們監聽 URL 的變化並根據 URL 來渲染對應的組件。 Routes 是用來定義一組路由的容器,而 Route 則用來定義具體的路由對應。 ## useMemo ## useReducer ## useCallback