目前這個系列已挑選ChatGPT、前端&React相關章節出版成書!
感謝大家的閱讀與支持!
《用 ChatGPT 詠唱來點亮 React & 前端技能樹》
歷經重新整理、校正、編排、與修改後,讓章節內容之間的銜接更平順,除了補充上更多的解說之外,甚至有部分章節為重新撰寫的內容,讓理解變得更輕鬆!
目前已在天瓏書局、博客來、金石堂等通路都買得到了! 也有電子書的選擇!
歡迎各位點擊連結選購,謝謝大家~
上個章節提到,Hook是React所提供的函式
讓我們可以在Functional Component中使用React提供的功能
最廣泛以及最常用的hook即是管理State(狀態) 的useState
以及處理side effect(副作用)的useEffect
這個章節要來問問ChatGPT是否還有更多的hook可以使用
對我們的開發或是優化又有什麼幫助
Go Go!
當然可以!除了
useState
和useEffect
之外,React 還有許多其他常用的 Hook,讓你可以更靈活地管理組件的狀態和生命周期。以下是一些常見的 React Hook:
- useContext
- 這個 Hook 允許你在組件之間共享全域狀態,避免了一層層地傳遞 props 的麻煩。
- useReducer
- 這是一種更強大的狀態管理器,通常用於處理較複雜的狀態邏輯,可以替代
useState
。- useCallback
- 這個 Hook 可以幫助你優化性能,避免在每次渲染時重新生成函數。
- useMemo
- 這個 Hook 也是用於性能優化,它可以在依賴數組沒有變化時,避免重複計算。
- useRef
useRef
可以用來獲得 DOM 元素的引用,也可以用來儲存任意可變值。- useImperativeHandle
- 這個 Hook 允許你在父組件中自定義子組件暴露的實例值。
- useLayoutEffect
- 與
useEffect
類似,但是在渲染之前同步觸發。- useDebugValue
- 這個 Hook 用於在 React 開發者工具中顯示自定義的 hook 名稱。
這些是一些比較常見的 React Hook,當然還有其他一些可以根據需要使用的 Hook。在開發過程中,根據具體的場景和需求,你可以靈活地選擇適合的 Hook 來優化你的組件。
OK,ChatGPT一口氣給了八個
其實每個都可以深入探討,直接做成八篇文章 XDD
這篇會先廣泛的全部帶過一下,然後從常用的開始做探討
先請ChatGPT針對這八個都提供範例程式碼並說明用途
(長度太長,ChatGPT分成三段回答不過以下我就串起來)
當然可以!我會針對每個常用的 Hook 提供範例以及解說使用時機和使用目的。
- useContext
- 使用時機:當你希望在多個組件之間共享全域狀態時。
- 使用目的:避免一層層地傳遞 props,讓全域狀態變得更容易管理。
範例:
// 在 App.js 中建立一個 Context import React, { createContext, useContext, useState } from 'react'; const GlobalContext = createContext(); const GlobalProvider = ({ children }) => { const [globalState, setGlobalState] = useState('初始值'); return ( <GlobalContext.Provider value={{ globalState, setGlobalState }}> {children} </GlobalContext.Provider> ); }; export const useGlobalContext = () => { return useContext(GlobalContext); }; export default GlobalProvider;
在其他組件中使用:
import React from 'react'; import { useGlobalContext } from './App'; const ChildComponent = () => { const { globalState, setGlobalState } = useGlobalContext(); return ( <div> <p>全域狀態:{globalState}</p> <button onClick={() => setGlobalState('新的值')}>更新狀態</button> </div> ); }; export default ChildComponent;
- useReducer
- 使用時機:當你的狀態邏輯變得複雜,需要一個更強大的狀態管理器時。
- 使用目的:幫助你更結構化地處理狀態變化。
範例:
import React, { useReducer } from 'react'; const initialState = { count: 0 }; const reducer = (state, action) => { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } }; const Counter = () => { const [state, dispatch] = useReducer(reducer, initialState); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: 'increment' })}>增加</button> <button onClick={() => dispatch({ type: 'decrement' })}>減少</button> </div> ); }; export default Counter;
- useCallback
- 使用時機:當你希望避免在每次渲染時重新生成函數時。
- 使用目的:優化性能,避免不必要的函數重新創建。
範例:
import React, { useState, useCallback } from 'react'; const MemoizedComponent = () => { const [count, setCount] = useState(0); const handleClick = useCallback(() => { setCount(count + 1); }, [count]); return ( <div> <p>Count: {count}</p> <button onClick={handleClick}>增加</button> </div> ); }; export default MemoizedComponent;
這樣你可以根據具體的情況使用這些常用的 Hook 來優化你的組件。記得根據實際需求選擇適當的 Hook 來使用。
- useMemo
- 使用時機:當你需要在依賴數組沒有變化時,避免重複計算時。
- 使用目的:提升效能,避免不必要的運算。
範例:
import React, { useState, useMemo } from 'react'; const ExpensiveComponent = () => { const [count, setCount] = useState(0); // 使用 useMemo 來計算一個昂貴的值,只有在 count 變化時才會重新計算 const expensiveValue = useMemo(() => { console.log('計算 expensiveValue'); return count * 2; }, [count]); return ( <div> <p>Count: {count}</p> <p>Expensive Value: {expensiveValue}</p> <button onClick={() => setCount(count + 1)}>增加</button> </div> ); }; export default ExpensiveComponent;
- useRef
- 使用時機:當你需要獲得 DOM 元素的引用或儲存任意可變值時。
- 使用目的:方便存取 DOM 元素或儲存不會觸發重新渲染的值。
範例:
import React, { useRef, useEffect } from 'react'; const RefComponent = () => { const inputRef = useRef(); useEffect(() => { inputRef.current.focus(); }, []); return ( <div> <input type="text" ref={inputRef} /> </div> ); }; export default RefComponent;
- useImperativeHandle
- 使用時機:當你需要在父組件中自定義子組件暴露的實例值時。
- 使用目的:提供一個介面供父組件操作子組件。
範例:
import React, { useRef, useImperativeHandle, forwardRef } from 'react'; const ChildComponent = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input type="text" ref={inputRef} />; }); export default ChildComponent;
這些是一些常見的 React Hook 的範例以及使用時機和使用目的。希望這些範例能對您理解這些 Hook 有所幫助!如果您還有其他問題,請隨時告訴我。
- useLayoutEffect
- 使用時機:當你需要在 DOM 更新之前執行一些同步的操作時。
- 使用目的:通常用於需要立即計算 DOM 尺寸或位置的情況。
範例:
import React, { useState, useLayoutEffect } from 'react'; const LayoutEffectComponent = () => { const [width, setWidth] = useState(0); useLayoutEffect(() => { // 這裡的程式碼會在 DOM 更新之後同步執行 const newWidth = document.getElementById('container').clientWidth; setWidth(newWidth); }, []); // 空的依賴陣列表示只在初始渲染時執行 return ( <div> <div id="container" style={{ width: '300px', height: '100px', background: 'lightblue' }}> 寬度:{width}px </div> </div> ); }; export default LayoutEffectComponent;
- useDebugValue
- 使用時機:當你需要在 React 開發者工具中顯示自定義的 hook 名稱時。
- 使用目的:方便開發者在工具中追蹤和除錯 Hook。
範例:
import { useState, useDebugValue } from 'react'; const useCustomHook = (initialValue) => { const [value, setValue] = useState(initialValue); useDebugValue(value > 10 ? '大於 10' : '小於 10'); // 在 React 開發者工具中顯示訊息 return [value, setValue]; }; export default useCustomHook;
這些是剩下的兩個常用的 React Hook 的範例以及使用時機和使用目的。請根據實際需求選擇適當的 Hook 來使用。如果您還有其他問題或需要進一步的解釋,請隨時告訴我!
接下來,我會針對比較常用的 useCallback
、useMemo
、useRef
進行較深入的探討useContext
和useReducer
雖然也是常見且廣泛使用的
但有關 useContext
和useReducer
的部分,
我會放在後面跟另外一個第三方套件redux放一起講
其他的hook,根據有需求的時候,或是我後面如果有足夠篇(ㄌㄧˋ)章(ㄑㄧˋ)再來介紹
當然!讓我來深入介紹
useCallback
這個 Hook。
useCallback
Hook什麼是
useCallback
?
useCallback
是一個用於優化性能的 Hook,它用於避免在每次渲染時重新生成函數。使用時機
當你希望避免因為每次渲染都重新創建函數而造成性能問題時,就可以考慮使用
useCallback
。如何使用
useCallback
接受兩個參數:要儲存的函數以及一個依賴陣列。const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b] );
- 第一個參數是要儲存的函數,它會在每次渲染時保持不變。
- 第二個參數是一個依賴陣列,只有在這個陣列中的值發生變化時,才會重新計算並返回新的函數。
範例
假設你有一個組件,其中包含一個按鈕,每次按下按鈕時都會執行一個處理函數:
import React, { useState, useCallback } from 'react'; const ButtonComponent = () => { const [count, setCount] = useState(0); // 使用 useCallback 來優化處理函數 const handleClick = useCallback(() => { setCount(count + 1); }, [count]); return ( <div> <p>Count: {count}</p> <button onClick={handleClick}>增加</button> </div> ); }; export default ButtonComponent;
在這個例子中,
handleClick
函數使用了useCallback
,並且依賴於count
的值。這樣,只有在count
發生變化時,才會重新計算handleClick
。注意事項
- 請記得只在真正需要優化性能的情況下使用
useCallback
。如果你的函數並不會在每次渲染時都被調用,那麼優化可能並不會帶來顯著的效果。- 請不要過度使用
useCallback
,過多的優化反而可能會讓代碼變得複雜難懂。希望這能幫助你更好地理解和使用
useCallback
Hook!如果你有任何進一步的問題,請隨時告訴我。
當然,讓我進一步說明在什麼情況下推薦使用
useCallback
,並提供更多範例來說明需要使用它的情況。推薦使用
useCallback
的情況:
將函數作為 props 傳遞給子組件:
假如你有一個父組件,其中包含一個按鈕,每次按下按鈕時都會調用一個處理函數。當這個處理函數需要作為 prop 傳遞給子組件時,你會想要使用
useCallback
來避免在每次渲染時重新生成函數。const ParentComponent = () => { const handleClick = useCallback(() => { // 處理按鈕點擊 }, []); return <ChildComponent handleClick={handleClick} />; };
優化 React.memo 或 PureComponent 的效能:
當你在使用
React.memo
或PureComponent
優化你的組件時,如果組件接收一個函數作為 prop,你會需要使用useCallback
來確保該函數不會在每次渲染時都被重新生成。const MemoizedComponent = React.memo(({ handleClick }) => { return <button onClick={handleClick}>按鈕</button>; }); const ParentComponent = () => { const handleClick = useCallback(() => { // 處理按鈕點擊 }, []); return <MemoizedComponent handleClick={handleClick} />; };
在
useEffect
中使用函數依賴:如果你在
useEffect
中使用一個依賴於 props 或狀態的函數,且該函數在渲染時可能會發生變化,你會需要使用useCallback
來確保該函數不會在每次渲染時都被重新生成。const ComponentWithEffect = ({ someProp }) => { const handleEffect = useCallback(() => { // 使用 someProp 做一些事情 }, [someProp]); useEffect(() => { handleEffect(); }, [handleEffect]); return <div>組件內容</div>; };
在自定義 Hook 中使用:
如果你創建了一個自定義 Hook,並且該 Hook 返回一個函數,你可能會需要使用
useCallback
來確保該函數不會在每次渲染時都被重新生成。const useCustomHook = () => { const handleAction = useCallback(() => { // 執行某些操作 }, []); return { handleAction }; };
希望這些例子幫助你更好地理解在什麼情況下推薦使用
useCallback
。如果你有任何進一步的問題,請隨時告訴我。
OKOK… 突然出現了一些新的名詞
「React.memo」、「PureComponent」、「自定義 Hook」
這個先擺一邊,等一下問ChatGPT
上面ChatGPT的範例中,useEffect的例子相當常用
如果沒有把放在useEffect依賴陣列內的function使用useCallback來處理
eslint也會跳出警告訊息,提醒你要把他放進去,否則每次render都會觸發一次useEffect
可能就和你原本的設計原意不同
接著來了解ChatGPT所提到的其他情形
React.memo
和PureComponent
都是 React 中用於優化性能的工具,它們都有助於減少不必要的渲染,提升應用的效能。React.memo
React.memo
是一個高階組件(Higher Order Component,HOC),它可以包裹組件,並在 props 沒有改變的情況下,阻止該組件的重新渲染。舉例來說:
const MyComponent = React.memo((props) => { /* 組件內容 */ });
當
MyComponent
的 props 沒有改變時,它就不會進行重新渲染。這可以節省渲染時間,特別是對於那些複雜的組件來說。PureComponent
PureComponent
是一個 React 內建的類別組件,它自動實現了shouldComponentUpdate
方法,這個方法會在每次接收到新的 props 或 state 時,檢查它們是否有變化,如果沒有變化,就不會觸發重新渲染。舉例來說:
class MyComponent extends React.PureComponent { /* 組件內容 */ }
PureComponent
對於簡單的組件來說是一個很方便的工具,因為它可以自動執行性能優化的檢查,減少了手動編寫shouldComponentUpdate
的需求。使用時機
使用
React.memo
或PureComponent
的時機通常是在你確定組件的渲染只依賴於它的 props 或 state,而不依賴於其他外部因素的情況下。這樣做可以節省不必要的渲染,提升應用的性能。然而,需要注意的是,過度使用
React.memo
或PureComponent
可能會導致某些情況下的 bug,因為這些工具只檢查 props 或 state 是否有深層的變化,並不會進行深度比較。總的來說,使用
React.memo
或PureComponent
可以是一個有效的性能優化手段,但在應用它們時,請確保了解它們的適用場景和限制,以避免出現意外的行為。
結合兩者介紹,可以知道React.memo
或是Class Component中的PureComponent
已經是優化成只有Props變化時才會重新渲染
這時候傳進來作為Props的function卻在非必要時也重新生成
那麼使用React.memo的優點與目的就喪失了
接下來的問題是,什麼是「自定義Hook」?為什麼也需要useCallback的協助?
自定義 Hook 是一種讓你可以在 React 函數組件中重用狀態邏輯的方式。它們可以幫助你把具有相似邏輯的組件邏輯抽象成可重用的函數。
自定義 Hook 是以
use
開頭的函數,例如useCustomHook
,它可以調用其他的 Hook,並且可以利用 Hook 的特性來存取 React 的狀態和生命周期。自定義 Hook 通常是一個函數,它可以接收一些自定義的參數,並返回一些狀態變數或函數,讓你可以在組件中使用。
例如,以下是一個簡單的自定義 Hook,用來追蹤滑鼠的位置:
import { useState, useEffect } from 'react'; const useMousePosition = () => { const [position, setPosition] = useState({ x: 0, y: 0 }); const handleMouseMove = (e) => { setPosition({ x: e.clientX, y: e.clientY }); }; useEffect(() => { window.addEventListener('mousemove', handleMouseMove); return () => { window.removeEventListener('mousemove', handleMouseMove); }; }, []); return position; }; export default useMousePosition;
這個自定義 Hook 可以在任何組件中使用,並且它會返回當前滑鼠的位置。
使用自定義 Hook 的好處在於它可以幫助你將具有相似邏輯的代碼抽象成可重用的函數,進而提升代碼的可讀性和可維護性。同時,它也符合 React 的鉤子(Hook)概念,使得在函數組件中管理狀態變得更為方便。
總的來說,自定義 Hook 是一個非常強大的工具,可以讓你更有效地組織和重用代碼邏輯。
上面的例子來說,如果多個component內都需要追蹤滑鼠的位置
原本的寫法會變成在這些需要使用到的component內,都放上一樣的程式碼
都需要 const [position, setPosition] = useState({ x: 0, y: 0 });
也需要用useEffect註冊事件等等
既然有機會講到自定義Hook,那就來和ChatGPT確認一下
使用自定義Hook時,有什麼原則或是注意事項
撰寫自定義 Hook 時需要注意以下幾個重要點:
- 命名規則:
- 自定義 Hook 的名稱應以
use
開頭,這樣 React 才能識別它是一個 Hook。- 不要在迴圈、條件式或巢狀函數中調用 Hook:
- Hook 的調用必須在函數的最高層級中進行,不能在迴圈、條件式或巢狀函數中使用。
- 每次呼叫都是獨立的:
- 每次呼叫自定義 Hook 都是獨立的,它們之間不會共享狀態。
- 只在函數組件中使用:
- 自定義 Hook 只能在函數組件中使用,不能在類別組件或普通 JavaScript 函數中使用。
- 可以使用其他 Hook:
- 在自定義 Hook 中,你可以使用其他 React 提供的 Hook,如
useState
、useEffect
等。- 提供清理機制(若需要):
- 如果你在自定義 Hook 中使用了訂閱或定時器等資源,請確保在組件卸載時進行清理,以避免內存洩漏。
更多使用時機與範例:
- 監聽 window 尺寸變化:
import { useState, useEffect } from 'react'; const useWindowSize = () => { const [windowSize, setWindowSize] = useState({ width: window.innerWidth, height: window.innerHeight }); useEffect(() => { const handleResize = () => { setWindowSize({ width: window.innerWidth, height: window.innerHeight }); }; window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); }; }, []); return windowSize; }; export default useWindowSize;
- 表單處理:
import { useState } from 'react'; const useFormInput = (initialValue) => { const [value, setValue] = useState(initialValue); const handleChange = (e) => { setValue(e.target.value); }; return { value, onChange: handleChange }; }; export default useFormInput;
- 主題切換:
import { useState } from 'react'; const useThemeToggle = (initialTheme) => { const [theme, setTheme] = useState(initialTheme); const toggleTheme = () => { setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light'); }; return { theme, toggleTheme }; }; export default useThemeToggle;
這些範例展示了如何利用自定義 Hook 來抽象和重用具有相似邏輯的程式碼。透過自定義 Hook,你可以更有效地組織你的程式碼邏輯,並提升代碼的可讀性和可維護性。
了解了自定義hook的使用與範例之後
我們再把主題拉回來
為什麼自定義hook內也需要用useCallback包裝function?
什麼時機自定義hook內要使用useCallback?
在自定義 Hook 中,可能需要搭配
useCallback
的情況通常是當你的 Hook 返回一個函數,並且這個函數會作為 callback 函數被傳遞到其他地方,比如事件處理函數或是 effect 中的回調函數。讓我們以一個範例來說明為什麼在某些情況下需要在自定義 Hook 中搭配
useCallback
:假設你創建了一個自定義 Hook 來處理計數器的邏輯:
import { useState } from 'react'; const useCounter = () => { const [count, setCount] = useState(0); const increment = () => { setCount(count + 1); }; return { count, increment }; }; export default useCounter;
如果你在組件中使用這個自定義 Hook,並把
increment
函數作為事件處理函數傳遞到子組件中:const ParentComponent = () => { const { count, increment } = useCounter(); return <ChildComponent handleClick={increment} />; };
那麼這裡的
increment
函數會在每次渲染時重新生成,這可能會導致ChildComponent
在每次渲染時都會得到一個不同的handleClick
函數,進而觸發不必要的重新渲染。為了避免這種情況,你可以在
useCounter
Hook 中使用useCallback
來確保increment
函數在每次渲染時都保持不變:import { useState, useCallback } from 'react'; const useCounter = () => { const [count, setCount] = useState(0); const increment = useCallback(() => { setCount(count + 1); }, [count]); return { count, increment }; }; export default useCounter;
這樣,
increment
函數就會保持穩定,不會在每次渲染時重新生成,避免了不必要的重新渲染。總的來說,當你的自定義 Hook 返回一個函數並且這個函數會在其他地方被使用時,你可能需要搭配使用
useCallback
來確保這個函數的穩定性。
充分理解useCallback之後,接下來請ChatGPT介紹useMemo
當然!讓我深入介紹一下
useMemo
。
useMemo
Hook什麼是
useMemo
?
useMemo
是一個用於優化性能的 Hook,它會在渲染過程中對具有高昂運算成本的函數進行記憶化(memoization),以減少不必要的重新計算。使用時機
當你有一個昂貴的計算或函數,且它的結果在相同的依賴不變的情況下是固定的,那麼可以使用
useMemo
來避免在每次渲染時都重新計算這個值。如何使用
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- 第一個參數是一個函數,它執行那個昂貴的計算。
- 第二個參數是一個依賴陣列,只有在陣列中的值發生變化時,
computeExpensiveValue
才會重新運算。範例
假設你有一個組件,其中包含一個按鈕,每次按下按鈕時都會執行一個昂貴的計算:
import React, { useState, useMemo } from 'react'; const ExpensiveComponent = () => { const [count, setCount] = useState(0); // 使用 useMemo 來記憶化昂貴計算 const expensiveValue = useMemo(() => { console.log('執行昂貴計算'); return count * 2; }, [count]); return ( <div> <p>Count: {count}</p> <p>Expensive Value: {expensiveValue}</p> <button onClick={() => setCount(count + 1)}>增加</button> </div> ); }; export default ExpensiveComponent;
在這個範例中,
expensiveValue
只在count
發生變化時才會重新計算,這樣可以避免在每次渲染時都執行昂貴的計算。注意事項
- 請記得只在真正需要優化性能的情況下使用
useMemo
。如果你的計算並不會在每次渲染時都被調用,那麼優化可能並不會帶來顯著的效果。- 請注意
useMemo
返回的是記憶化的值,而不是函數,這與useCallback
有所不同。希望這能幫助你更好地理解和使用
useMemo
Hook!如果你有任何進一步的問題,請隨時告訴我。
所以可以理解為
useCallback ⇒ 記住function,只在依賴改變時重新生成function
useMemo ⇒ 記住變數計算結果,只在依賴改變的時候重新運算並更新記憶的變數內容
在這次的對話中,我們深入探討了 React 中的一些重要概念和技術,特別是針對初學者的前端工程師。以下是我們討論的主要內容:
- React Hook 概述:我們介紹了 React Hook 的基本概念,它們是一種用於在函數組件中管理狀態和邏輯的工具,並且與類別組件有所不同。
- 常見的 React Hook:我們列出了一些常見的 React Hook,包括
useState
、useEffect
、useContext
、useReducer
等,並解釋了它們的使用時機和使用目的。- 自定義 Hook:我們討論了自定義 Hook,這是一種用於封裝和重用邏輯的方式,並提供了多個實際範例,例如用於監聽滑鼠位置、處理表單、切換主題等。
- 性能優化與 Hook:我們深入研究了
useMemo
和useCallback
這兩個 Hook,它們用於優化性能,避免不必要的重新渲染和計算。總結來說,React Hook 是一個強大的工具,可以讓前端工程師更容易地管理狀態和邏輯,並且提供了許多方式來優化應用程序的性能。對於初學者來說,詳細了解每個 Hook 的使用時機和使用方式,以及如何創建自定義 Hook,都是學習 React 的重要一步。
如果你有更多問題或需要進一步的協助,請隨時向我提問。祝你在學習 React 的過程中取得成功!
耶…… 我覺得再塞useRef就內容太太太多了
useCallback和useMemo可以單獨一篇
自定義hook其實也可以單獨一篇
全部塞一起還要放useRef真的太多了….
本次的對話連結:
https://chat.openai.com/share/1b527d56-24a2-405c-8636-636ba1eea374
下一個章節! useRef!
我是漫步在hook之間的前端工程師一宵三筵,我們明天見!