React基礎學習-Day04
常見的鉤子函數及基礎使用方式
1.useState
useState
是 React 的一個 Hook,用于在函數組件中添加狀態。它返回一個狀態變量和一個更新該狀態的函數。與類組件的 this.state
和 this.setState
相對應,useState
讓函數組件也能擁有和管理狀態。
基本用法
以下是 useState
的基本用法示例:
import React, { useState } from 'react';const Counter = () => {// 聲明一個名為 "count" 的狀態變量,初始值為 0const [count, setCount] = useState(0);return (<div><p>Count: {count}</p><button onClick={() => setCount(count + 1)}>Increment</button></div>);
};export default Counter;
在這個示例中,useState(0)
聲明了一個名為 count
的狀態變量,并將其初始值設為 0
。setCount
是更新 count
狀態的函數。當點擊按鈕時,調用 setCount(count + 1)
將 count
增加 1,并重新渲染組件。
多個狀態
可以在同一個組件中使用多個 useState
聲明不同的狀態變量:
import React, { useState } from 'react';const UserProfile = () => {const [name, setName] = useState('Alice');const [age, setAge] = useState(25);return (<div><p>Name: {name}</p><p>Age: {age}</p><button onClick={() => setName('Bob')}>Change Name</button><button onClick={() => setAge(age + 1)}>Increase Age</button></div>);
};export default UserProfile;
在這個示例中,我們使用了兩個 useState
Hook,一個用于管理 name
狀態,另一個用于管理 age
狀態。每個狀態都有自己獨立的更新函數。
初始化狀態
useState
可以接受一個函數作為初始狀態,這個函數在初始渲染時會被調用,用于計算初始狀態。這對于需要復雜計算才能得到初始狀態的情況非常有用。
import React, { useState } from 'react';const ExpensiveComponent = () => {const [value, setValue] = useState(() => {// 模擬一個耗時的計算const initialValue = computeExpensiveValue();return initialValue;});return (<div><p>Value: {value}</p><button onClick={() => setValue(value + 1)}>Increment</button></div>);
};const computeExpensiveValue = () => {// 這里模擬一個復雜的計算console.log('Computing expensive value...');return 42;
};export default ExpensiveComponent;
在這個示例中,useState
接受一個函數,該函數只在初始渲染時被調用一次,用于計算初始狀態值。
注意事項
- 每次調用
setState
更新狀態時,React 會重新渲染組件。 - 更新狀態是異步的,因此在調用
setState
后立即讀取狀態可能不會得到最新的值。 - 可以通過函數式更新來確保狀態更新基于最新的狀態值:
const [count, setCount] = useState(0);const increment = () => {setCount(prevCount => prevCount + 1);
};
在這個示例中,setCount
接受一個函數,該函數的參數是之前的狀態值,返回的新狀態值將基于之前的狀態值進行更新。
完整示例
以下是一個使用多個狀態和函數式更新的完整示例:
import React, { useState } from 'react';const App = () => {const [count, setCount] = useState(0);const [text, setText] = useState('Hello');const increment = () => {setCount(prevCount => prevCount + 1);};const changeText = () => {setText(prevText => prevText === 'Hello' ? 'World' : 'Hello');};return (<div><p>Count: {count}</p><p>Text: {text}</p><button onClick={increment}>Increment Count</button><button onClick={changeText}>Change Text</button></div>);
};export default App;
在這個示例中,我們使用了 useState
來管理兩個獨立的狀態變量 count
和 text
,并使用函數式更新確保狀態更新基于最新的狀態值。這有助于避免狀態更新的競爭條件。
ps:useState的第一個參數用函數可以使用異步函數嗎
useState
的第一個參數是初始狀態,它可以是一個值或者是一個函數,但這個函數必須是同步的。React 不支持在 useState
的初始狀態函數中使用異步函數。useState
的初始狀態函數只會在組件的初始渲染時被調用一次,并且它是同步的。
如果你需要在組件初始化時從異步操作中獲取數據并設置狀態,你可以在 useEffect
Hook 中執行異步操作,并在操作完成后更新狀態。
以下是一個如何使用 useEffect
和 useState
來處理異步操作的示例:
import React, { useState, useEffect } from 'react';const FetchDataComponent = () => {const [data, setData] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {const fetchData = async () => {try {const response = await fetch('https://api.example.com/data');if (!response.ok) {throw new Error('Network response was not ok');}const result = await response.json();setData(result);} catch (error) {setError(error);} finally {setLoading(false);}};fetchData();}, []); // 空數組意味著這個 effect 只會在組件掛載和卸載時執行一次if (loading) {return <div>Loading...</div>;}if (error) {return <div>Error: {error.message}</div>;}return (<div><h1>Fetched Data:</h1><pre>{JSON.stringify(data, null, 2)}</pre></div>);
};export default FetchDataComponent;
在這個示例中:
useState
用于管理組件的狀態,包括data
、loading
和error
。useEffect
用于執行異步數據獲取操作。當組件首次掛載時,這個 effect 會運行,觸發fetchData
異步函數。fetchData
異步函數使用fetch
從 API 獲取數據。如果成功,數據將被存儲在data
狀態中,并且loading
狀態被設置為false
。如果出現錯誤,error
狀態將被設置,并且loading
狀態同樣被設置為false
。
這樣,雖然不能在 useState
的初始狀態函數中使用異步函數,但可以通過 useEffect
來實現異步數據獲取和狀態管理。
2.useEffect
useEffect
是 React 的一個 Hook,用于在函數組件中執行副作用操作。副作用操作包括數據獲取、訂閱、手動 DOM 操作以及在組件更新或卸載時執行清理任務。useEffect
可以看作是 componentDidMount
、componentDidUpdate
和 componentWillUnmount
的組合。
基本用法
以下是 useEffect
的基本用法示例:
import React, { useState, useEffect } from 'react';const ExampleComponent = () => {const [count, setCount] = useState(0);// useEffect 中的函數會在組件渲染后執行useEffect(() => {document.title = `Count: ${count}`;}, [count]); // 依賴項數組return (<div><p>Count: {count}</p><button onClick={() => setCount(count + 1)}>Increment</button></div>);
};export default ExampleComponent;
在這個示例中,useEffect
中的函數會在組件每次渲染后執行。依賴項數組 [count]
指定了只有當 count
狀態發生變化時,useEffect
才會重新運行。
依賴項數組
useEffect
的第二個參數是依賴項數組,指定了哪些狀態或屬性的變化會觸發 useEffect
的重新運行:
- 空數組
[]
:僅在組件掛載和卸載時運行一次。 - 未指定依賴項數組:在每次組件渲染后都會運行。
- 特定依賴項:當依賴項發生變化時才會運行。
清理副作用
如果 useEffect
返回一個函數,這個函數會在組件卸載時或在下一次執行副作用前被調用,用于清理副作用。例如,清理訂閱或取消定時器:
import React, { useState, useEffect } from 'react';const TimerComponent = () => {const [seconds, setSeconds] = useState(0);useEffect(() => {const interval = setInterval(() => {setSeconds(prevSeconds => prevSeconds + 1);}, 1000);// 返回一個清理函數,在組件卸載時清除定時器return () => clearInterval(interval);}, []); // 空數組意味著這個 effect 只會在組件掛載和卸載時執行一次return (<div><p>Seconds: {seconds}</p></div>);
};export default TimerComponent;
在這個示例中,useEffect
設置了一個定時器,每秒更新一次 seconds
狀態。在 useEffect
中返回的清理函數會在組件卸載時被調用,以清除定時器。
數據獲取示例
以下是一個使用 useEffect
執行數據獲取操作的示例:
import React, { useState, useEffect } from 'react';const FetchDataComponent = () => {const [data, setData] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {const fetchData = async () => {try {const response = await fetch('https://api.example.com/data');if (!response.ok) {throw new Error('Network response was not ok');}const result = await response.json();setData(result);} catch (error) {setError(error);} finally {setLoading(false);}};fetchData();}, []); // 空數組意味著這個 effect 只會在組件掛載和卸載時執行一次if (loading) {return <div>Loading...</div>;}if (error) {return <div>Error: {error.message}</div>;}return (<div><h1>Fetched Data:</h1><pre>{JSON.stringify(data, null, 2)}</pre></div>);
};export default FetchDataComponent;
在這個示例中,useEffect
用于執行異步數據獲取操作。組件掛載時,fetchData
函數會運行,并在數據獲取成功后更新 data
狀態。如果出現錯誤,error
狀態會被更新。
總結
useEffect
是一個功能強大的 Hook,用于在函數組件中管理副作用操作。通過合理使用依賴項數組和清理函數,useEffect
可以幫助我們在組件的生命周期中執行和管理各種副作用。
3.useContext
useContext
是 React 的一個 Hook,用于在函數組件中讀取和使用 React 上下文(Context)。上下文允許您在組件樹中傳遞數據,而不必手動逐層傳遞 props。
基本用法
以下是 useContext
的基本用法示例:
假設我們有一個上下文對象 ThemeContext
:
import React, { createContext, useContext, useState } from 'react';// 創建一個上下文對象
const ThemeContext = createContext();// 上下文的提供者組件
const ThemeProvider = ({ children }) => {const [theme, setTheme] = useState('light');const toggleTheme = () => {setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));};return (<ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>);
};// 使用上下文的消費者組件
const ThemeConsumerComponent = () => {const { theme, toggleTheme } = useContext(ThemeContext);return (<div><p>Current Theme: {theme}</p><button onClick={toggleTheme}>Toggle Theme</button></div>);
};// 在組件樹中使用 ThemeProvider 包裹所有需要訪問 theme 上下文的組件
const App = () => {return (<ThemeProvider><ThemeConsumerComponent /></ThemeProvider>);
};export default App;
在這個示例中:
ThemeContext
是一個上下文對象,通過createContext()
創建。ThemeProvider
是一個提供者組件,使用ThemeContext.Provider
包裝其子組件,并通過value
屬性傳遞theme
和toggleTheme
函數。ThemeConsumerComponent
是一個消費者組件,使用useContext(ThemeContext)
來訂閱ThemeContext
上下文。它從上下文中讀取theme
和toggleTheme
,并在按鈕點擊時切換主題。
注意事項
- 使用
createContext()
創建上下文對象。 - 使用
Provider
組件包裹子組件,并通過value
屬性傳遞數據。 - 在需要訪問上下文數據的組件中使用
useContext(ThemeContext)
。
多個上下文
您可以在應用程序中使用多個上下文。每個上下文對象都應該有自己的提供者和消費者組件。React 會確保正確的數據傳遞和更新。
總結
useContext
Hook 提供了一種輕松地在函數組件中使用 React 上下文的方式。它使得組件可以更簡潔地訂閱和使用跨組件樹的數據。通過 useContext
,您可以避免 props drilling,提高組件的可重用性和可維護性。
4.useReducer
一、介紹useReducer的重要性和優勢
useReducer是 React 中的一個 Hook,用于管理應用狀態。它提供了一種更簡潔、更易于理解的方式來處理復雜的狀態邏輯。
重要性:
狀態管理:在 React 應用中,狀態管理是至關重要的。useReducer允許開發者以更清晰和集中的方式管理應用的狀態。
復雜狀態邏輯:對于涉及多個狀態變量和復雜的更新邏輯的場景,使用useReducer可以更好地組織和維護代碼。
可預測的狀態更新:useReducer使用函數來更新狀態,這使得狀態更新更加可預測和易于理解。
更好的代碼可讀性:通過使用useReducer,可以將狀態更新邏輯拆分為獨立的函數,提高代碼的可讀性和可維護性。
優勢:
簡化代碼:相比使用多個 useState 鉤子來管理復雜的狀態,useReducer 可以減少代碼的冗余。
更好的性能:useReducer在某些情況下可以提供更好的性能,尤其是在處理大量狀態更新時。
狀態的合并:useReducer支持合并多個更新操作,從而減少不必要的重新渲染。
清晰的狀態更新邏輯:使用useReducer可以將狀態更新邏輯放在一個單獨的函數中,使其更加清晰和易于理解。
總之,useReducer對于處理復雜的狀態邏輯和更好地組織狀態更新非常有用。它提供了一種更簡潔、可預測和可讀性更高的方式來管理應用狀態。在需要處理復雜狀態的情況下,推薦使用useReducer。
二、useReducer的基本概念
解釋useReducer的定義和作用
useReducer是React Hooks中的一個函數,它用于在React應用程序中實現狀態管理。useReducer函數接收兩個參數:一個是reducer函數,另一個是初始狀態。reducer函數接收兩個參數:一個是當前狀態,另一個是action對象。action對象通常包含一個type屬性,表示要進行的操作,以及可能的其他屬性。
useReducer函數返回一個數組,數組的第一個元素是當前狀態,第二個元素是一個函數,該函數用于更新狀態。當組件需要更新狀態時,它將調用該函數,并將新狀態作為參數傳遞給它。該函數將使用reducer函數來計算新狀態,并將其返回給組件。
下面是一個簡單的示例,演示如何使用useReducer來管理計數器狀態:
import React, { useReducer } from 'react';function reducer(state, action) {switch (action.type) {case 'increment':return state + 1;case 'decrement':return state - 1;default:throw new Error();}
}function Counter() {const [state, dispatch] = useReducer(reducer, 0);const handleIncrement = () => {dispatch({ type: 'increment' });};const handleDecrement = () => {dispatch({ type: 'decrement' });};return (<div><h1>{state}</h1><button onClick={handleIncrement}>+</button><button onClick={handleDecrement}>-</button></div>);
}
在這個示例中,我們定義了一個reducer函數reducer,它接收兩個參數:當前狀態state和action對象action。action對象包含一個type屬性,表示要進行的操作。然后,我們使用useReducer函數將reducer函數和初始狀態傳遞給組件。useReducer函數返回一個數組,數組的第一個元素是當前狀態,第二個元素是一個函數,用于更新狀態。
我們定義了兩個函數handleIncrement和handleDecrement,分別用于處理加1和減1操作。然后,我們將這些函數綁定到按鈕的onClick事件上,以便在按鈕被點擊時調用它們。
最后,我們將當前狀態顯示在頁面上,以便用戶可以看到計數器的值在不斷變化。當用戶點擊按鈕時,我們將調用dispatch函數,并將相應的action對象傳遞給它。dispatch函數將調用reducer函數來計算新狀態,并將新狀態返回給組件。
總之,useReducer函數在React應用程序中提供了一個簡單、高效的狀態管理解決方案,可以用于管理復雜的應用程序狀態。
與其他狀態管理方法進行比較
以下是使用表格總結的useReducer與其他狀態管理方法的比較:
方法 描述 優點 缺點
useState React內置的狀態管理方法,用于管理簡單的狀態。 簡單易用 無法處理復雜的業務邏輯
useEffect 用于在函數組件中添加副作用,如數據獲取、訂閱等。 靈活性高 需要在組件內手動處理副作用
useContext 用于在不同組件之間共享狀態,而不需要顯式地傳遞狀態。 共享狀態簡單易用 無法處理副作用
useReducer 用于管理復雜的狀態,如復雜的業務邏輯、表格數據等。 靈活性高,易于處理復雜的業務邏輯 需要手動編寫reducer函數
從表格中可以看出,useReducer方法在處理復雜的狀態上具有優勢,因為它可以方便地使用reducer函數來處理復雜的業務邏輯。同時,它也可以處理副作用,如數據獲取、訂閱等。但是,它需要手動編寫reducer函數,這可能會增加一些復雜性。而useState和useEffect方法則更適合處理簡單的狀態和管理副作用。useContext方法則更適合在不同組件之間共享狀態。
總之,選擇哪種狀態管理方法取決于具體的需求和組件的結構。在實際開發中,可以根據項目的規模和復雜度來選擇合適的狀態管理方法。
三、useReducer的使用示例
解釋useReducer的參數和返回值
useReducer 是一個 React 狀態管理方法,它的參數和返回值如下:
參數:
reducer 函數:這個函數接收兩個參數,分別是當前狀態(state)和一個 action。該函數的作用是處理傳入的狀態,并返回一個新的狀態。
initialState:狀態的初始值。
返回值:
新的 state:由 reducer 函數處理后返回的新狀態。
dispatch 函數:用于發送一個對象(action)給 reducer 函數,以更新狀態,并觸發重新渲染。
展示如何更新狀態和觸發重新渲染
以下是使用 useReducer 更新狀態和觸發重新渲染的示例:
import React, { useReducer } from 'react';const initialState = {count: 0
};function reducer(state, action) {switch (action.type) {case 'increment':return {...state,count: state.count + 1};case 'decrement':return {...state,count: state.count - 1};default:return state;}
}function MyComponent() {// 使用 useReducer 來創建狀態和更新函數const [state, dispatch] = useReducer(reducer, initialState);const handleIncrement = () => {// 發送一個 increment 類型的 action 來更新狀態dispatch({ type: 'increment' });};const handleDecrement = () => {// 發送一個 decrement 類型的 action 來更新狀態dispatch({ type: 'decrement' });};return (<div><h1>{state.count}</h1><button onClick={handleIncrement}>+</button><button onClick={handleDecrement}>-</button></div>);
}export default MyComponent;
在上述示例中,使用 useReducer 創建了一個狀態 count,初始值為 0。定義了一個 reducer 函數來處理狀態的更新。reducer 函數根據不同的 action.type 來執行相應的狀態更新操作。
通過調用 dispatch 函數并傳入一個對象作為 action,可以更新狀態。在示例中,點擊 “+” 按鈕會發送一個 increment 類型的 action,點擊 “-” 按鈕會發送一個 decrement 類型的 action。
當狀態更新后,組件會重新渲染,顯示最新的狀態值。
5.useRef
useRef
是 React 的一個 Hook,用于在函數組件中創建可變的引用對象。與 useState
不同,useRef
返回一個可變的 ref 對象,其 .current
屬性被初始化為傳入的參數(initialValue)。返回的 ref 對象在組件的整個生命周期中保持不變,不會因重新渲染而重新創建。
主要用途
-
獲取 DOM 元素的引用
import React, { useRef, useEffect } from 'react';const TextInputComponent = () => {const inputRef = useRef(null);useEffect(() => {// 在組件加載后聚焦到輸入框inputRef.current.focus();}, []);return (<div><input type="text" ref={inputRef} /><button onClick={() => inputRef.current.focus()}>Focus Input</button></div>); };export default TextInputComponent;
在這個示例中,
useRef
創建了一個inputRef
對象,并將其賦值給<input>
元素的ref
屬性。通過inputRef.current
可以訪問到真實的 DOM 元素,比如調用.focus()
方法來聚焦輸入框。 -
保存任意可變值
import React, { useRef } from 'react';const CounterComponent = () => {const countRef = useRef(0);const increment = () => {countRef.current++;console.log('Current count:', countRef.current);};return (<div><p>Count: {countRef.current}</p><button onClick={increment}>Increment</button></div>); };export default CounterComponent;
在這個示例中,
useRef
創建了一個countRef
對象,并初始化為0
。在increment
函數中,通過修改countRef.current
來更新計數器的值,而不需要觸發組件的重新渲染。 -
保存上一個 props 或 state
import React, { useRef, useEffect } from 'react';const PreviousValueComponent = ({ value }) => {const prevValueRef = useRef();useEffect(() => {prevValueRef.current = value;});const prevValue = prevValueRef.current;return (<div><p>Current Value: {value}</p><p>Previous Value: {prevValue !== undefined ? prevValue : 'N/A'}</p></div>); };export default PreviousValueComponent;
在這個示例中,通過
useRef
創建了prevValueRef
對象,用于保存value
的上一個值。通過在useEffect
中更新prevValueRef.current
,可以在組件的重新渲染中獲取到上一個value
的值。
注意事項
useRef
創建的 ref 對象在組件的整個生命周期中保持不變,不會因重新渲染而重新創建。- 修改
useRef
創建的 ref 對象的.current
屬性不會觸發組件的重新渲染。 - 可以通過
useRef
來保存和訪問 DOM 元素的引用、保存任意可變值以及保存上一個 props 或 state 的值。
6.useCallback
useCallback
是 React 的一個 Hook,它返回一個記憶化的回調函數,用于優化函數組件的性能,防止不必要的重新渲染和重新創建函數。在依賴項沒有發生變化時,useCallback
返回的函數引用保持不變,從而避免子組件因為父組件的函數變化而重新渲染。
基本用法
以下是 useCallback
的基本用法示例:
import React, { useState, useCallback } from 'react';const ChildComponent = React.memo(({ onClick }) => {console.log('Rendering ChildComponent');return <button onClick={onClick}>Click me</button>;
});const ParentComponent = () => {const [count, setCount] = useState(0);const handleClick = useCallback(() => {console.log('Button clicked');}, []);return (<div><p>Count: {count}</p><button onClick={() => setCount(count + 1)}>Increment Count</button><ChildComponent onClick={handleClick} /></div>);
};export default ParentComponent;
在這個示例中,handleClick
函數使用了 useCallback
,并且依賴項數組為空([]
),這意味著 handleClick
函數在組件的整個生命周期中只會創建一次。這樣,ChildComponent
組件不會因為 ParentComponent
重新渲染而重新渲染,因為 onClick
屬性的引用沒有變化。
依賴項數組
useCallback
的第二個參數是依賴項數組,只有當依賴項數組中的某個依賴項發生變化時,才會重新創建回調函數。例如:
import React, { useState, useCallback } from 'react';const ChildComponent = React.memo(({ onClick }) => {console.log('Rendering ChildComponent');return <button onClick={onClick}>Click me</button>;
});const ParentComponent = () => {const [count, setCount] = useState(0);const [text, setText] = useState('Hello');const handleClick = useCallback(() => {console.log('Button clicked with text:', text);}, [text]);return (<div><p>Count: {count}</p><p>Text: {text}</p><button onClick={() => setCount(count + 1)}>Increment Count</button><button onClick={() => setText(text === 'Hello' ? 'World' : 'Hello')}>Change Text</button><ChildComponent onClick={handleClick} /></div>);
};export default ParentComponent;
在這個示例中,handleClick
函數依賴于 text
狀態。每當 text
狀態發生變化時,handleClick
函數都會重新創建。這確保了 handleClick
始終使用最新的 text
值。
何時使用 useCallback
useCallback
在以下情況下特別有用:
- 傳遞回調函數給子組件,且子組件依賴于回調函數的引用。
- 回調函數在組件重新渲染時不需要重新創建。
- 優化性能,防止因函數引用變化導致的子組件不必要的重新渲染。
注意事項
useCallback
僅用于記憶化回調函數,避免不必要的函數重新創建和子組件重新渲染。- 過度使用
useCallback
可能導致代碼復雜性增加,應根據實際需要使用。 - 記憶化函數會占用內存,特別是當依賴項變化頻繁時,可能會影響性能。
示例總結
以下是一個使用 useCallback
優化回調函數的完整示例:
import React, { useState, useCallback } from 'react';const ChildComponent = React.memo(({ onClick }) => {console.log('Rendering ChildComponent');return <button onClick={onClick}>Click me</button>;
});const ParentComponent = () => {const [count, setCount] = useState(0);const handleClick = useCallback(() => {console.log('Button clicked');}, []);return (<div><p>Count: {count}</p><button onClick={() => setCount(count + 1)}>Increment Count</button><ChildComponent onClick={handleClick} /></div>);
};export default ParentComponent;
在這個示例中,handleClick
函數使用了 useCallback
,確保在父組件重新渲染時不會重新創建,從而避免了子組件的重新渲染,提高了性能。
7.useMemo
useMemo
定義
useMemo
是 React 框架中的一個重要 Hook,它的核心目的是通過緩存計算結果,避免在組件渲染時進行不必要的重復計算,從而優化性能。這意味著只有當其依賴項發生變化時,useMemo
才會重新計算這個值,否則它將重用之前的結果。
它的基本使用格式如下:
const cachedValue = useMemo(calculateValue, dependencies)
calculateValue
:這是一個用于計算我們想要緩存的值的函數。為了確保結果的穩定性和預測性,這個函數應該是一個純函數。這意味著,它在相同的輸入下總是返回相同的輸出,并且沒有任何副作用。dependencies
:這是一個數組,包含useMemo
所依賴的變量或值。當數組中的任何值發生變化時,calculateValue
函數將被重新執行。
useMemo
基礎用法
useMemo
接受兩個參數:一個函數和一個依賴項數組。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
在上面的例子中,computeExpensiveValue
是一個可能需要很長時間來計算的函數。我們只有當a
或b
改變時,才重新調用這個函數。否則,我們會使用之前緩存的值。
用一個例子來看 useMemo 的執行時機:
import React, { useMemo, useState } from "react";function filterUsers(users, searchTerm) {return users.filter((user) => user.name.includes(searchTerm));
}function useMemoDemo() {const [searchTerm, setSearchTerm] = useState("");const [isDark, setIsDark] = useState(false);const allUsers = useMemo(() => {let list = [];for (let i = 1; i <= 500; i++) {list.push({ id: i, name: `User${i}` });}return list;}, []);const useMemoCurrentUsers = useMemo(() => {console.log('with useMemo')return filterUsers(allUsers, searchTerm);}, [allUsers, searchTerm]);return (<div>{/* 每一次更改查詢框內容,都會觸發useMemo */}<inputvalue={searchTerm}onChange={(e) => setSearchTerm(e.target.value)}placeholder="Search by name..."/>{/* 每一次更改背景色,都不會觸發useMemo */}<button onClick={() => setIsDark((pre) => !pre)}>{isDark ? "Dark mode" : "Light mode"}</button><div><div><h2>With useMemo</h2><div style={{ background: isDark ? "#000" : "" }}>{useMemoCurrentUsers.map((user) => (<div key={user.id}>{user.name}</div>))}</div></div></div></div>);
}export default useMemoDemo;
在這里簡單的示例中,每次修改查詢框的內容,都會觸發searchTerm
的變化,進而觸發useMemo
重新計算;而點擊切換背景色的按鈕,因為useMemo
的依賴項沒有更新,所以不會觸發useMemo
重新計算,而是直接使用上一次計算的返回值。
React.memo
React.memo
是 React 中的一個高階組件(HOC),它通過記憶化函數組件的結果來優化性能,防止不必要的重新渲染。它的工作原理是僅在組件的 props 發生變化時才重新渲染,這對依賴大量 props 且不需要每次父組件重新渲染時都重新渲染的組件特別有用。
使用方法
以下是 React.memo
的基本用法示例:
import React from 'react';// 一個接收 props 的函數組件
const MyComponent = (props) => {console.log('Rendering MyComponent');return (<div>{props.text}</div>);
};// 使用 React.memo 包裝組件
const MemoizedComponent = React.memo(MyComponent);export default MemoizedComponent;
在這個示例中,MyComponent
只有在 text
prop 發生變化時才會重新渲染。如果父組件重新渲染,但 text
prop 保持不變,MyComponent
不會重新渲染。
自定義比較函數
默認情況下,React.memo
進行的是淺比較。如果你需要進行更深層次的比較或有復雜的 props,可以提供自定義的比較函數作為 React.memo
的第二個參數:
import React from 'react';const MyComponent = (props) => {console.log('Rendering MyComponent');return (<div>{props.text}</div>);
};const areEqual = (prevProps, nextProps) => {// 在這里進行自定義比較return prevProps.text === nextProps.text;
};const MemoizedComponent = React.memo(MyComponent, areEqual);export default MemoizedComponent;
在這個示例中,areEqual
是一個函數,它接收前一個和下一個 props 并返回 true
表示它們相等,從而防止重新渲染。如果返回 false
,組件將會重新渲染。
何時使用 React.memo
React.memo
在以下情況下特別有用:
- 組件是純函數組件,并且其輸出完全由 props 決定。
- 組件頻繁重新渲染,并且 props 保持不變。
- 你想要優化性能,避免不必要的重新渲染。
注意事項
React.memo
只適用于函數組件。- 默認情況下,它進行淺比較,對于復雜的 props 可能不夠。
- 過度使用
React.memo
可能會導致不必要的復雜性。應根據實際情況使用,重點放在那些實際受益于記憶化的組件上。
實例
以下是一個在父組件中使用 React.memo
的實際示例:
import React, { useState } from 'react';const ChildComponent = React.memo(({ text }) => {console.log('Rendering ChildComponent');return <div>{text}</div>;
});const ParentComponent = () => {const [count, setCount] = useState(0);const [text, setText] = useState('Hello');return (<div><button onClick={() => setCount(count + 1)}>Increment Count</button><button onClick={() => setText(text === 'Hello' ? 'World' : 'Hello')}>Change Text</button><ChildComponent text={text} /></div>);
};export default ParentComponent;
在這個示例中,ChildComponent
只有在 text
prop 發生變化時才會重新渲染,即使 ParentComponent
因為 count
狀態變化而重新渲染。這可以幫助在大型應用中提高性能。
8.useDeferredValue
useDeferredValue
是 React 18 引入的一個 Hook,用于延遲更新狀態值,以提升用戶界面的響應速度。在處理高優先級任務(如輸入或交互)時,可以使用 useDeferredValue
將低優先級的狀態更新推遲,以保持界面的流暢性。
基本用法
以下是 useDeferredValue
的基本用法示例:
import React, { useState, useDeferredValue, useEffect } from 'react';const SearchComponent = () => {const [query, setQuery] = useState('');const deferredQuery = useDeferredValue(query);useEffect(() => {// 模擬一個數據獲取函數const fetchData = async () => {console.log('Fetching data for:', deferredQuery);// 模擬數據獲取延遲await new Promise(resolve => setTimeout(resolve, 1000));console.log('Data fetched for:', deferredQuery);};if (deferredQuery) {fetchData();}}, [deferredQuery]);return (<div><inputtype="text"value={query}onChange={(e) => setQuery(e.target.value)}placeholder="Type your search query..."/><p>Searching for: {deferredQuery}</p></div>);
};export default SearchComponent;
在這個示例中:
useState
用于創建一個query
狀態,表示用戶輸入的查詢。useDeferredValue
用于創建一個deferredQuery
,它是query
的延遲副本。- 當用戶輸入內容時,
query
狀態立即更新,而deferredQuery
會稍后更新。 useEffect
鉤子監聽deferredQuery
的變化,并在其變化時執行數據獲取操作。
延遲更新的好處
使用 useDeferredValue
可以提高用戶界面的響應速度,特別是在處理大量數據或復雜計算時。通過將低優先級的狀態更新推遲,React 可以優先處理用戶輸入和交互,從而保持界面的流暢性。
注意事項
useDeferredValue
僅在 React 18 及更高版本中可用。useDeferredValue
適用于那些不需要立即更新的狀態值,例如搜索查詢、過濾條件等。useDeferredValue
不會阻止狀態的最終更新,只是將其延遲到更適當的時機。
示例:過濾列表
以下是一個使用 useDeferredValue
進行列表過濾的示例:
import React, { useState, useDeferredValue } from 'react';const ListComponent = ({ items }) => {const [filter, setFilter] = useState('');const deferredFilter = useDeferredValue(filter);const filteredItems = items.filter(item =>item.toLowerCase().includes(deferredFilter.toLowerCase()));return (<div><inputtype="text"value={filter}onChange={(e) => setFilter(e.target.value)}placeholder="Filter items..."/><ul>{filteredItems.map((item, index) => (<li key={index}>{item}</li>))}</ul></div>);
};export default ListComponent;
在這個示例中:
useState
用于創建一個filter
狀態,表示用戶輸入的過濾條件。useDeferredValue
用于創建一個deferredFilter
,它是filter
的延遲副本。filteredItems
列表根據deferredFilter
進行過濾,以提升用戶輸入的響應速度。
通過這種方式,可以在處理較大數據集時保持界面的流暢性,同時確保最終的過濾結果是準確的。
9.useTransition
useTransition
是 React 18 引入的一個 Hook,用于管理狀態更新的優先級。它允許你將某些狀態更新標記為“過渡”,這樣 React 就可以優先處理更高優先級的任務(如用戶輸入),而將過渡狀態的更新推遲到更適合的時間點。這對于保持用戶界面的響應速度非常有用。
基本用法
以下是 useTransition
的基本用法示例:
import React, { useState, useTransition } from 'react';const TransitionComponent = () => {const [isPending, startTransition] = useTransition();const [input, setInput] = useState('');const [list, setList] = useState([]);const handleChange = (e) => {const value = e.target.value;setInput(value);// 將狀態更新標記為過渡startTransition(() => {const newList = Array.from({ length: 20000 }, (_, index) => `${value} ${index}`);setList(newList);});};return (<div><input type="text" value={input} onChange={handleChange} />{isPending && <p>Loading...</p>}<ul>{list.map((item, index) => (<li key={index}>{item}</li>))}</ul></div>);
};export default TransitionComponent;
在這個示例中:
useTransition
返回一個布爾值isPending
和一個函數startTransition
。isPending
表示過渡狀態是否正在進行中,可以用來顯示加載指示器。startTransition
用于將某些狀態更新標記為過渡。
用法說明
-
useTransition
的語法如下:const [isPending, startTransition] = useTransition();
-
isPending
是一個布爾值,指示過渡是否正在進行。 -
startTransition
是一個函數,用于包裹過渡狀態更新的邏輯。
處理大量數據
以下是一個處理大量數據的示例,通過 useTransition
保持界面的響應速度:
import React, { useState, useTransition } from 'react';const BigDataComponent = () => {const [isPending, startTransition] = useTransition();const [query, setQuery] = useState('');const [results, setResults] = useState([]);const handleSearch = (e) => {const value = e.target.value;setQuery(value);startTransition(() => {// 模擬數據處理const filteredResults = mockData.filter(item => item.includes(value));setResults(filteredResults);});};return (<div><input type="text" value={query} onChange={handleSearch} placeholder="Search..." />{isPending && <p>Loading results...</p>}<ul>{results.map((result, index) => (<li key={index}>{result}</li>))}</ul></div>);
};const mockData = Array.from({ length: 10000 }, (_, index) => `Item ${index}`);export default BigDataComponent;
在這個示例中:
- 當用戶輸入搜索查詢時,
handleSearch
函數會立即更新query
狀態。 startTransition
用于標記數據過濾操作為過渡狀態,從而使得 React 可以優先處理用戶輸入,并推遲執行較重的數據處理任務。- 如果過渡狀態正在進行,
isPending
為true
,顯示加載指示器。
注意事項
useTransition
適用于那些可以延遲更新的狀態,例如數據過濾、分頁加載等。startTransition
包裹的狀態更新不會阻塞用戶輸入或其他高優先級任務,從而提升用戶體驗。