網站首頁 編程語言 正文
最初,在 React 中可以使用?createClass?來創建組件,后來被類組件所取代。在 React 16.8 版本中,新增的 Hooks 功能徹底改變了我們編寫 React 程序的方式,使用 Hooks 可以編寫更簡潔、更清晰的代碼,并為創建可重用的有狀態邏輯提供了更好的模式。
許多公司和開發人員都放棄了類組件轉而使用 Hooks。而許多舊的的React 項目仍然在使用類組件。更重要的是,在類組件中有 Error Boundaries,而函數組件中是無法使用 Error Boundaries 的。
本文就來通過一些常見示例看看如何使用 React Hooks 來重構類組件。
1. 管理和更新組件狀態
狀態管理是幾乎所有 React 應用中最重要的部分,React 基于 state 和 props 渲染組件。每當它們發生變化時,組件就會重新渲染,并且 DOM 也會相應地更新。下面來看一個計數器的例子,它包含一個計數狀態以及兩個更新它的地方:
import { Component } from "react";
class ManagingStateClass extends Component {
state = {
counter: 0,
};
increment = () => {
this.setState(prevState => {
return {
counter: prevState.counter + 1,
};
});
};
decrement = () => {
this.setState(prevState => {
return {
counter: prevState.counter - 1,
};
});
};
render() {
return (
<div>
<div>Count: {this.state.counter}</div>
<div>
<button onClick={this.increment}>Increment</button>
<button onClick={this.decrement}>Decrement</button>
</div>
</div>
);
}
}
export default ManagingStateClass;
下面來使用 Hooks 實現這個計數器組件:
import { useState } from "react";
const ManagingStateHooks = () => {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1);
const decrement = () => setCounter(counter => counter - 1);
return (
<div>
<div>Count: {counter}</div>
<div>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
</div>
);
};
export default ManagingStateHooks;
該組件是一個返回 JSX 的函數,使用?useState??hook來管理計算器的狀態。它返回一個包含兩個值的數組:第一個值為狀態,第二個值為更新函數。并且使用?setCounter??來更新程序的increment和decrement函數。
2. 狀態更新后的操作
在某些情況下,我們可能需要在狀態更新時執行某些操作。在類組件中,我們通常會在componentDidUpdate?生命周期中實現該操作。
import { Component } from "react";
class StateChangesClass extends Component {
state = {
counter: 0,
};
componentDidUpdate(prevProps, prevState) {
localStorage.setItem("counter", this.state.counter);
}
increment = () => {
this.setState(prevState => {
return {
counter: prevState.counter + 1,
};
});
};
decrement = () => {
this.setState(prevState => {
return {
counter: prevState.counter - 1,
};
});
};
render() {
return (
<div>
<div>Count: {this.state.counter}</div>
<div>
<button onClick={this.increment}>Increment</button>
<button onClick={this.decrement}>Decrement</button>
</div>
</div>
);
}
}
export default StateChangesClass;
當狀態發生變化時,我們將新的計數器值保存在?localStorage??中。在函數組件中,我們可以通過使用?useEffect?hook 來實現相同的功能。
import { useState, useEffect } from "react";
const StateChangesHooks = () => {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1);
const decrement = () => setCounter(counter => counter - 1);
useEffect(() => {
localStorage.setItem("counter", counter);
}, [counter]);
return (
<div>
<div>Count: {counter}</div>
<div>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
</div>
);
};
export default StateChangesHooks;
這個?useEffect??hook 有兩個參數,第一個參數是回調函數,第二個參數是依賴數組。在組件掛載時,這個?hook??至少會執行一次。然后,僅在依賴數組內的任何值發生變化時都會觸發第一個參數傳入的回調函數。如果依賴數組為空,則回調函數只會執行一次。在上面的例子中,每當?counter??發生變化時,都會觸發將?counter??保存在?localStorage?中的回調函數。
3. 獲取數據
在類組件中,通過會在componentDidMount生命周期中初始化一個 API 請求來獲取數據。下面來看一個獲取并顯示帖子列表的組件:
import { Component } from "react";
class FetchingDataClass extends Component {
state = {
posts: [],
};
componentDidMount() {
this.fetchPosts();
}
fetchPosts = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const data = await response.json();
this.setState({
posts: data.slice(0, 10),
});
};
render() {
return (
<div>
{this.state.posts.map(post => {
return <div key={post.id}>{post.title}</div>;
})}
</div>
);
}
}
export default FetchingDataClass
有了 hooks,就可以使用useEffect?來實現上述功能。它會在第一次掛載之后執行一次,然后在任何依賴發生變化時再次觸發。useEffect??允許我們傳入一個空依賴數組作為第二個參數來確保只執行一次effect的回調函數。
import { useState, useEffect } from "react";
const FetchingDataHooks = () => {
const [posts, setPosts] = useState([]);
const fetchPosts = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const data = await response.json();
setPosts(data.slice(0, 10));
};
useEffect(() => {
fetchPosts();
}, []);
return (
<div>
{posts.map(post => {
return <div key={post.id}>{post.title}</div>;
})}
</div>
);
};
export default FetchingDataHooks;
4. 卸載組件時清理副作用
在卸載組件時清理副作用是非常重要的,否則可能會導致內存泄露。例如,在一個組件中,我們想要監聽一個事件,比如resize?或者scroll?,并根據窗口大小或滾動的位置來做一些事情。下面來看一個類組件的例子,它會監聽?resize??事件,然后更新瀏覽器窗口的寬度和高度的狀態。事件監聽器在?componentWillUnmount?生命周期中被移除。
import { Component } from "react";
class CleanupClass extends Component {
state = {
width: window.innerWidth,
height: window.innerHeight,
};
componentDidMount() {
window.addEventListener("resize", this.updateWindowSize, {
passive: true,
});
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateWindowSize, {
passive: true,
});
}
updateWindowSize = () => {
this.setState({
width: window.innerWidth,
height: window.innerHeight,
});
};
render() {
return (
<div>
Window: {this.state.width} x {this.state.height}
</div>
);
}
}
export default CleanupClass;
在?useEffect??中,我們可以在回調函數中返回一個函數來執行清理操作,卸載組件時會調用此函數。下面,首先來定義一個?updateWindowSize??函數,然后在?useEffect??中添加?resize?事件監聽器。接下來返回一個匿名箭頭函數,它將用來移除監聽器。
import { useState, useEffect } from "react";
const CleanupHooks = () => {
const [width, setWidth] = useState(window.innerWidth);
const [height, setHeight] = useState(window.innerHeight);
useEffect(() => {
const updateWindowSize = () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
};
window.addEventListener("resize", updateWindowSize, {
passive: true,
});
return () => {
window.removeEventListener("resize", this.updateWindowSize, {
passive: true,
});
};
}, []);
return (
<div>
Window: {this.state.width} x {this.state.height}
</div>
);
};
export default CleanupHooks;
5. ?防止組件重新渲染
React 非???,通常我們不必擔心過早的優化。但是,在某些情況下,優化組件并確保它們不會過于頻繁地重新渲染是很有必要的。
例如,減少類組件重新渲染的常用方法是使用?PureComponent??或者?shouldComponentUpdate??生命周期。下面例子中有兩個類組件(父組件和子組件),父組件有兩個狀態值:counter??和?fruit?。子組件只在父組件的?fruit??發生變化時重新渲染。所以,使用?shouldComponentUpdat?e 生命周期來檢查?fruit?屬性是否改變。如果相同,則子組件不會重新渲染。
父組件:
import { Component } from "react";
import PreventRerenderClass from "./PreventRerenderClass.jsx";
function randomInteger(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const fruits = ["banana", "orange", "apple", "kiwi", "mango"];
class PreventRerenderExample extends Component {
state = {
fruit: null,
counter: 0,
};
pickFruit = () => {
const fruitIdx = randomInteger(0, fruits.length - 1);
const nextFruit = fruits[fruitIdx];
this.setState({
fruit: nextFruit,
});
};
componentDidMount() {
this.pickFruit();
}
render() {
return (
<div>
<h3>
Current fruit: {this.state.fruit} | counter: {this.state.counter}
</h3>
<button onClick={this.pickFruit}>挑一個水果</button>
<button
onClick={() =>
this.setState(({ counter }) => ({
counter: counter + 1,
}))
}
>
Increment
</button>
<button
onClick={() =>
this.setState(({ counter }) => ({ counter: counter - 1 }))
}
>
Decrement
</button>
<div className="section">
<PreventRerenderClass fruit={this.state.fruit} />
</div>
</div>
);
}
}
export default PreventRerenderExample;
子組件:
import { Component } from "react";
class PreventRerenderClass extends Component {
shouldComponentUpdate(nextProps, nextState) {
return this.props.fruit !== nextProps.fruit;
}
render() {
return (
<div>
<p>Fruit: {this.props.fruit}</p>
</div>
);
}
}
export default PreventRerenderClass;
隨著 hooks 的引入,我們得到了一個新的高階組件,稱為?memo。它可用于優化性能并防止函數組件重新渲染。下面來看看它是怎么用的。
父組件:
import { useState, useEffect } from "react";
import PreventRerenderHooks from "./PreventRerenderHooks.jsx";
function randomInteger(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const fruits = ["banana", "orange", "apple", "kiwi", "mango"];
const PreventRerenderExample = () => {
const [fruit, setFruit] = useState(null);
const [counter, setCounter] = useState(0);
const pickFruit = () => {
const fruitIdx = randomInteger(0, fruits.length - 1);
const nextFruit = fruits[fruitIdx];
setFruit(nextFruit);
};
useEffect(() => {
pickFruit();
}, []);
return (
<div>
<h3>
Current fruit: {fruit} | counter: {counter}
</h3>
<button onClick={pickFruit}>挑一個水果</button>
<button onClick={() => setCounter(counter => counter + 1)}>
Increment
</button>
<button onClick={() => setCounter(counter => counter - 1)}>
Decrement
</button>
<div className="section">
<PreventRerenderHooks fruit={fruit} />
</div>
</div>
);
};
export default PreventRerenderExample;
子組件:
import { memo } from "react";
const PreventRerenderHooks = props => {
return (
<div>
<p>Fruit: {props.fruit}</p>
</div>
);
};
export default memo(PreventRerenderHooks);
PreventRerenderHooks??組件使用?memo??組件包裝,并且僅在 props 中的 fruit 發生變化時發揮重新渲染。需要注意,memo組件執行的是淺比較,因此如果需要更好地控制memo?組件何時重新渲染,可以提供自己的函數來執行?props?比較。
import { memo } from "react";
const PreventRerenderHooks = props => {
return (
<div>
<p>Fruit: {props.fruit}</p>
</div>
);
};
export default memo(PreventRerenderHooks, (prevProps, nextProps) => {
return prevProps.fruit !== nextProps.fruit
});
6. Context API
Context API 是一個很好用的工具,可以為組件層次結構中不同級別的組件提供值。可以使用 React 提供的?createContext??方法創建新的上下文。先來看一個在類組件中使用?context?的例子。
Context Provider:
import { createContext } from "react";
export const UserContext = createContext();
export const UserActionsContext = createContext();
在父組件中,向消費者提供了?UserContext??和?UserActionsContext。
import { Component, createContext } from "react";
import ContextApiClassConsumer from "./ContextApiClassConsumer.jsx";
import { UserContext, UserActionsContext } from "./userContext.js";
class ContextApiHooksProvider extends Component {
state = {
user: {
name: "Class",
},
};
setUser = user => this.setState({ user });
render() {
return (
<UserContext.Provider value={this.state.user}>
<UserActionsContext.Provider value={this.setUser}>
<ContextApiClassConsumer />
</UserActionsContext.Provider>
</UserContext.Provider>
);
}
}
export default ContextApiHooksProvider;
這里?ContextApiClassConsumer??組件就可以獲取到父組件提供的user和setUser。
Context Consumer:
import { Component } from "react";
import { UserContext, UserActionsContext } from "./userContext.js";
class ContextApiClassConsumer extends Component {
render() {
return (
<UserContext.Consumer>
{user => (
<UserActionsContext.Consumer>
{setUser => (
<div>
<input
type="text"
value={user.name}
onChange={e =>
setUser({
name: e.target.value,
})
}
/>
</div>
)}
</UserActionsContext.Consumer>
)}
</UserContext.Consumer>
);
}
}
export default ContextApiClassConsumer;
在上面的例子中,UserContext.Consumer??組件的子函數接收 user 狀態,UserActionsContext.Consumer??的子函數接收?setUser?方法。
使用 Hooks 實現和上面的代碼非常類似,但是會更簡潔。同樣,我們使用?UserContext.Provider??和?UserActionsContext.Provider??組件來提供?user??狀態和?setUser?方法。
Context Provider:
import { useState } from "react";
import ContextApiHooksConsumer from "./ContextApiHooksConsumer.jsx";
import { UserContext, UserActionsContext } from "./userContext.js";
const ContextApiHooksProvider = () => {
const [user, setUser] = useState({
name: "Hooks",
});
return (
<UserContext.Provider value={user}>
<UserActionsContext.Provider value={setUser}>
<ContextApiHooksConsumer />
</UserActionsContext.Provider>
</UserContext.Provider>
);
};
export default ContextApiHooksProvider;
在函數組件中,我們可以像在類組件中一樣使用?context?,但是,hooks 中有一種更簡潔的方法,我們可以利用?useContext??hook 來訪問?context?值。
Context Consumer:
import { useContext } from "react";
import { UserContext, UserActionsContext } from "./userContext.js";
const ContextApiHooksConsumer = () => {
const user = useContext(UserContext);
const setUser = useContext(UserActionsContext);
return (
<div>
<input
type="text"
value={user.name}
onChange={e =>
setUser({
name: e.target.value,
})
}
/>
</div>
);
};
export default ContextApiHooksConsumer;
7. 跨重新渲染保留值
在某些情況下,我們可能需要再組件中存儲一些數據。但是不希望將其存儲在狀態中,因為 UI 不以任何方式依賴這些數據。
例如,我們可能會保存一些希望稍后包含在 API 請求中的元數據。這在類組件中很容易實現,只需為類分配一個新屬性即可。
import { Component } from "react";
class PreservingValuesClass extends Component {
state = {
counter: 0,
};
componentDidMount() {
this.valueToPreserve = Math.random();
}
showValue = () => {
alert(this.valueToPreserve);
};
increment = () => this.setState(({ counter }) => ({ counter: counter + 1 }));
render() {
return (
<div>
<p>Counter: {this.state.counter}</p>
<button onClick={this.increment}>Increment</button>
<button onClick={this.showValue}>Show</button>
</div>
);
}
}
export default PreservingValuesClass;
在這個例子中,當組件被掛載時,我們在?valueToPreserve??屬性上分配了一個動態隨機數。除此之外,還有 increment 方法來強制重新渲染,但是Show按鈕時會彈窗顯示保留的值。
這在類組件中很容易實現,但是在函數組件中就沒那么簡單了。這是因為,任何時候函數組件的重新渲染都會導致函數中的所有內容重新執行。這意味著如果我們有這樣的組件:
const MyComponent = props => {
const valueToPreserve = Math.random()
// ...
}
組件每次重新渲染時都會重新調用?Math.random()?方法,因此創建的第一個值將丟失。
避免此問題的一種方法是將變量移到組件之外。但是,這是行不通的,因為如果該組件被多次使用,則該值會將被它們中的每一個覆蓋。
恰好,React 提供了一個非常適合這個用例的 hook。我們可以通過使用?useRef?hook 來保留函數組件中重新渲染的值。
import { useState, useRef, useEffect } from "react";
const PreserveValuesHooks = props => {
const valueToPreserve = useRef(null);
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1);
const showValue = () => {
alert(valueToPreserve.current);
};
useEffect(() => {
valueToPreserve.current = Math.random();
}, []);
return (
<div>
<p>Counter: {counter}</p>
<button onClick={increment}>Increment</button>
<button onClick={showValue}>Show value</button>
</div>
);
};
export default PreserveValuesHooks;
valueToPreserve 是一個初始值為?null??的?ref?。但是,它后來在?useEffect?中更改為我們想要保留的隨機數。
8. 如何向父組件傳遞狀態和方法?
盡管我們不應該經常訪問子組件的狀態和屬性,但是在某些情況下它可能會很有用。例如,我們想要重置某些組件的狀態或者訪問它的狀態。我們需要創建一個 Ref,可以在其中存儲對想要訪問的子組件的引用。在類組件中,可以使用?createRef??方法,然后將該?ref?傳遞給子組件。
父組件:
import { Component, createRef } from "react";
import ExposePropertiesClassChild from "./ExposePropertiessClassChild";
class ExposePropertiesClassParent extends Component {
constructor(props) {
super(props);
this.childRef = createRef();
}
showValues = () => {
const counter = this.childRef.current.state.counter;
const multipliedCounter = this.childRef.current.getMultipliedCounter();
alert(`
counter: ${counter}
multipliedCounter: ${multipliedCounter}
`);
};
increment = () => this.setState(({ counter }) => ({ counter: counter + 1 }));
render() {
return (
<div>
<button onClick={this.showValues}>Show</button>
<ExposePropertiesClassChild ref={this.childRef} />
</div>
);
}
}
export default ExposePropertiesClassParent;
子組件:
import { Component } from "react";
class ExposePropertiesClassChild extends Component {
state = {
counter: 0,
};
getMultipliedCounter = () => {
return this.state.counter * 2;
};
increment = () => this.setState(({ counter }) => ({ counter: counter + 1 }));
render() {
return (
<div>
<p>Counter: {this.state.counter}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
export default ExposePropertiesClassChild;
要訪問子組件的屬性,只需要在父組件中創建一個?ref?并傳遞它?,F在,讓我們看看如何使用函數組件和 hook 來實現相同的目標。
父組件:
import { useRef } from "react";
import ExposePropertiesHooksChild from "./ExposePropertiesHooksChild";
const ExposePropertiesHooksParent = props => {
const childRef = useRef(null);
const showValues = () => {
const counter = childRef.current.counter;
const multipliedCounter = childRef.current.getMultipliedCounter();
alert(`
counter: ${counter}
multipliedCounter: ${multipliedCounter}
`);
};
return (
<div>
<button onClick={showValues}>Show</button>
<ExposePropertiesHooksChild ref={childRef} />
</div>
);
};
export default ExposePropertiesHooksParent;
在父組件中,我們使用?useRef??hook 來存儲對子組件的引用。然后在 showValues 函數中訪問?childRef?的值??梢钥吹?,這里與類組件中的實現非常相似。
子組件:
import { useState, useImperativeHandle, forwardRef } from "react";
const ExposePropertiesHooksChild = (props, ref) => {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1);
useImperativeHandle(ref, () => {
return {
counter,
getMultipliedCounter: () => counter * 2,
};
});
return (
<div>
<p>Counter: {counter}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
export default forwardRef(ExposePropertiesHooksChild);
forwardRef??將從父組件傳遞的?ref??轉發到組件,而?useImperativeHandle?指定了父組件應該可以訪問的內容。
9. 小結
通過這篇文章,相信你對使用Hooks(函數組件)來重構類組件有了一定了解。Hooks 的出現使得 React 代碼更加簡潔,并且帶來了更好的狀態邏輯可重用性。在開始編寫 Hooks 之前,建議先閱讀 React Hooks 的官方文檔,因為在編寫時需要遵循某些規則,例如不要改變 Hooks 的調用順序。
原文鏈接:https://developer.51cto.com/article/714202.html
相關推薦
- 2022-11-18 Python實現常見數據格式轉換的方法詳解_python
- 2022-12-30 Golang反射獲取變量類型和值的方法詳解_Golang
- 2023-03-03 一文詳解Go?Http?Server原理_Golang
- 2022-05-07 Python?ini配置文件示例詳解_python
- 2022-08-11 Python中函數的創建及調用_python
- 2022-03-30 Python?使用和高性能技巧操作大全_python
- 2022-07-23 Python實現單向鏈表_python
- 2022-04-24 C語言字符函數中的isalnum()和iscntrl()你都知道嗎_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支