原文:What are React Hooks?
作者:Robin Wieruch
譯者:博軒
React Hooks
於 2018年10月的React Conf 中引入,作爲在 React
函數組件中使用狀態和生命週期的一種方法。雖然函數組件之前被稱爲 無狀態組件(FSC) ,但是 React Hooks
的出現,使得這些函數組件可以使用狀態。因此,現在許多人將它們視爲功能組件。
在這篇文章中,我會解釋這些 Hooks
背後的動機,React
會發生什麼改變,爲什麼我們不應該恐慌,以及如何在函數式組件中使用常見的 React Hooks
,比如 state 和 生命週期。
譯註:長文預警 🤓
爲什麼使用 React Hooks
React Hooks
是由 React
團隊發明的,用於在函數組件中引入狀態管理和生命週期方法。如果我們希望一個 React
函數組件可以擁有 狀態管理和生命週期方法,我不需要再去將一個 React
函數組件重構成一個 React
類組件。React Hooks
讓我們可以僅使用函數組件就可以完成一個 React 應用
。
不必要的組件重構
以前,只有 React
類組件可以使用 本地狀態管理 和 生命週期方法 。後者對於在 React
類組件中引入副作用(如監聽DOM事件,異步加載數據)至關重要。
import React from 'react';
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button
onClick={() =>
this.setState({ count: this.state.count + 1 })
}
>
Click me
</button>
</div>
);
}
}
export default Counter;
只有在您不需要狀態或生命週期方法時,纔會考慮使用 React
無狀態組件(FSC)。而且因爲 React
函數組件更輕便(更優雅),人們已經使用了大量的函數組件。每次,當這些 函數組件 需要狀態或生命週期方法時,都需要將 React
函數組件升級成 React
類組件(反之亦然)。
import React, { useState } from 'react';
// how to use the state hook in a React function component
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Counter;
有了 Hooks
,就沒有必要進行這種重構。狀態或生命週期方法在 React
函數組件中變得可用。這也讓 無狀態組件
到 功能組件
的重塑變得切實可行。
副作用邏輯
在 React
類組件中,副作用主要在生命週期方法中引入 (例如 componentDidMount
,componentDidUpdate
,componentWillUnmount
) 。副作用可能是在 React
中獲取數據,或 與 Browser API
進行交互 。通常這些副作用會伴隨着設置和清理階段。例如,如果您忘記了去刪除監聽器,就會遇到一些 React
性能問題 。
// side-effects in a React class component
class MyComponent extends Component {
// setup phase
componentDidMount() {
// add listener for feature 1
// add listener for feature 2
}
// clean up phase
componentWillUnmount() {
// remove listener for feature 1
// remove listener for feature 2
}
...
}
// side-effects in React function component with React Hooks
function MyComponent() {
useEffect(() => {
// add listener for feature 1 (setup)
// return function to remove listener for feature 1 (clean up)
});
useEffect(() => {
// add listener for feature 2 (setup)
// return function to remove listener for feature 2 (clean up)
});
...
}
現在,如果您在 React
類組件的生命週期方法中引入多個副作用,所有副作用將按生命週期方法分組,而不會按副作用的功能來分組。這就是 React Hooks
帶來的改變,將每個副作用通過一個鉤子函數進行封裝,而每個鉤子函數都會處理各自的副作用,並提供這個副作用的設置和清理。稍後您將在本篇教程中看到如何在 React Hook
中添加和刪除監聽器來實現這一點。
React
抽象地獄
在 React
中可以通過 高階組件 和 Render Props 來實現抽象和可重用性。還有 React Context
及其Provider
和消費者組件 所提供的另一個層次的抽象。React
中的所有這些高級模式都使用了所謂的包裝組件。對於正在創建更大的 React
應用程序的開發人員來說,以下組件的實現應該並不陌生。
import { compose } from 'recompose';
import { withRouter } from 'react-router-dom';
function App({ history, state, dispatch }) {
return (
<ThemeContext.Consumer>
{theme =>
<Content theme={theme}>
...
</Content>
}
</ThemeContext.Consumer>
);
}
export default compose(
withRouter,
withReducer(reducer, initialState)
)(App);
Sophie Alpert 將之稱爲 React
中的 “包裝地獄” 。您不僅可以在組件實現時看到它,還可以在瀏覽器中檢查組件時看到它。由於使用 Render Props
組件(包括 React's Context
提供的消費者組件)和高階組件,很容易產生了幾十個包裝組件。由於所有抽象邏輯都被其他 React
組件所隱藏,我們的應用變成了一棵沒有可讀性的組件樹🌲。而那些可見的組件也很難在瀏覽器的 DOM
中進行跟蹤。那麼,如果這些抽象的邏輯在函數組件中被一些封裝好的副作用所代替,這些額外的組件將不再需要。
function App() {
const theme = useTheme();
const history = useRouter();
const [state, dispatch] = useReducer(reducer, initialState);
return (
<Content theme={theme}>
...
</Content>
);
}
export default App;
這就是 React Hooks
的魅力所在。所有副作用都可以直接在組件中使用,業務組件爲了使用這些副作用,也不需要再引入其他組件所爲容器。容器組件消失,邏輯只存在於作爲函數的 React Hooks
中。
Andrew Clark 已經在他的鵝妹子嚶高階組件庫:recompose 中發表了一篇關於贊成 React Hooks
的聲明。
混亂的 JavaScript
類
JavaScript
很好地混合了兩個世界:面向對象編程(OOP) 和 函數式編程(FP)。React
將許多的開發者帶到了這兩個世界。一方面,React
(和 Redux
)向人們介紹了 函數編程(FP) 的功能組合,一些函數式編程的通用編程概念(例如高階函數,JavaScript
內置方法,如 map
,reduce
,filter
),以及一些函數式編程的術語,如 不變性
和 副作用
。React
本身沒有真正介紹這些東西,因爲它們是語言或編程範式的特性,但它們在 React
中被大量使用,這使得每個 React
開發人員都會自動成爲一個更好的 JavaScript
開發人員 。
另一方面,在 React
中,可以使用 JavaScript
類作爲定義 React
組件的一種方法。類只是聲明,而組件的實際用法是它的實例。它會創建一個類實例,而類實例的 this
對象可以用於調用類的方法(例如 setState
,forceUpdate
,以及其他自定義類方法)。但是,對於不是來自 OOP 背景的 React
初學者來說,學習曲線會更陡峭。這就是爲什麼類綁定,this
對象和繼承可能令人困惑的原因。對於初學者來說,這一直是 React
最令人困惑的事情,我的 React書 中也只有幾章在講這方面的知識。
// I THOUGHT WE ARE USING A CLASS. WHY IS IT EXTENDING FROM SOMETHING?
class Counter extends Component {
// WAIT ... THIS WORKS???
state = { value: 0 };
// I THOUGH IT'S THIS WAY, BUT WHY DO I NEED PROPS HERE?
// constructor(props) {
// SUPER???
// super(props);
//
// this.state = {
// value: 0,
// };
// }
// WHY DO I HAVE TO USE AN ARROW FUNCTION???
onIncrement = () => {
this.setState(state => ({
value: state.value + 1
}));
};
// SHOULDN'T IT BE this.onDecrement = this.onDecrement.bind(this); in the constructor???
// WHAT'S this.onDecrement = this.onDecrement.bind(this); DOING ANYWAY?
onDecrement = () => {
this.setState(state => ({
value: state.value - 1
}));
};
render() {
return (
<div>
{this.state.value}
{/* WHY IS EVERYTHING AVAILABLE ON "THIS"??? */}
<button onClick={this.onIncrement}>+</button>
<button onClick={this.onDecrement}>-</button>
</div>
)
}
}
現在,有許多人在爭論 React
不應該移除 JavaScript classes
, 儘管他們不理解這些類的概念。畢竟,這些概念都來自於語言自身。但是,引入 Hooks API
的假設之一就是讓初學者在第一次編寫 React
應用時可以不用使用類組件,從而提供更加平滑的學習曲線。
React Hooks
會發生什麼變化?
每次引入新功能時,人們都會關注它。一些人對這一變化感到欣喜若狂,而另外一些人對這一變化感到擔憂。我聽說 React Hooks
最常見的問題是:
- 一切都變了!莫名的感到恐慌......
-
React
像Angular
一樣變得臃腫! -
Hooks
沒有用,classes
很好 - 這是魔法!
讓我在這裏解決這些問題:
一切都在變化?
React Hooks
將改變我們將來編寫 React
應用程序的方式。但是,目前沒有任何變化。您仍然可以使用本地狀態和生命週期方法編寫類組件,並部署高級模式,例如高階組件或 Render Props
組件。沒有人會把這些知識從你身邊帶走。瞭解我如何將所有開源項目從舊版本升級到 React 16.6
。這些項目都沒有問題。他們正在使用 HOC
,Render Props
,甚至是較爲古老的 context API
(如果我錯了,請糾正我)。這些年來我所學到的一切仍然有效。React
團隊確保 React
保持向後兼容。它與 React 16.7
保持相同。
React
像 Angular
一樣變得臃腫
React
作爲一個第三方庫,其 API
總是會給人一種短小精幹的感覺 。現在是這樣,將來也是如此。但是,考慮到幾年前基於組件所構建的應用,在升級的時候不被其他更加先進的庫所取代, React
引入了有利於舊 API
的變更。如果 React
是9012年發佈的,也許他的功能只會保留函數組件和 Hooks
。但 React
幾年前就已經發布,它在發佈新的特性的同時,還需要兼容之前的版本。可能會在幾年棄用類組件和其生命週期方法,轉而使用 React
函數組件和 Hooks
,但目前,React
團隊仍將 React
類組件保留在了他們的工具庫中。畢竟, React
團隊希望利用新的特性 Hooks
來陪伴 React
贏得一場馬拉松,而不是一場短跑。顯然,React Hooks
爲 React
添加了另一個 API
,但它有利於在未來簡化 React
的新 API
。我喜歡這種轉變,而不是擁有 "React 2",讓一切都不同。
譯註:這是在吐槽 Angular
嘛?😂
Hooks
沒有用,classes
很好 ?
想象一下,你將從零開始學習 React
,你會被介紹給 React with Hooks
。也許 create-react-app
不會以 React
類組件開始,而是使用 React
函數組件。您需要了解組件的所有內容都是 React Hooks
。它們可以管理狀態和副作用,因此您只需要知道 state hook
和 effect hook
。這是 React
類組件之前爲包含的一切。React
初學者學習 React
會更簡單,而不需要 JavaScript
類(繼承,this
,bindings
,super
,...)帶來的所有其他開銷。想象一下 React Hooks
作爲一種編寫 React
組件的新方法 - 這是一種全新的思維方式。我自己是一個持懷疑態度的人,但是一旦我用 React Hooks
寫了幾個更簡單的場景,我確信這是最簡單的 React
寫作方式,同樣也學習 React
最簡單的方式。作爲一個做了很多 React workshops
的人,我認爲它消除了那些令 React
初學者所沮喪的 classes
。
這是魔法?
衆所周知,React
是用 JavaScript
來實現的。當有人問我:“我爲什麼要學習 React
?”時,最好的解釋就是:編寫 React
應用程序會讓你成爲一個更好的 JavaScript
開發人員 。無論未來是否會使用新的庫,每個人都可以在使用 React
的過程中,磨練他們的 JavaScript
技能和一些通用的編程技巧。這是 Redux
經常做的事情,同樣也是 React
做的事情:流行,但是沒有魔力,它只是普通的 JavaScript
。現在 React Hooks
出現了,在以前經常使用的純函數組件中引入了一些有狀態的東西,同時還引入了一些不容易讓人接受的規則,這使得很多人都不明白底層發生了什麼。但是這樣考慮一下:在React
中的一個函數組件不僅僅是一個函數。您仍然必須將 React
作爲庫導入到源代碼文件中。它對你的函數做了一些事情,使得函數在 React
的世界中變成了函數組件。此函數組件的實現始終是隱式的。如何在將 React Hooks
引入之前使用函數實現的函數組件呢?人們也接受了它,即使它有點像是一個魔法。現在,唯一改變的東西(也許它之前已經是這樣)是這些函數組件帶有一個額外的隱藏對象,可以跟蹤的 Hooks
。引用 [Dan Abramov
](https://twitter.com/dan_abramov) 在他的關於 Hooks
文章的話:“也許你想知道 React
在哪裏爲 Hooks
保持狀態。答案是它保存在 React
爲類保持狀態的完全相同的位置。無論你如何定義你的組件,React
都會在內部維護一個更新隊列”。
最後,試一下這種思考方式
基於組件的解決方案(如 Angular
,Vue
和 React
)正在推動每個版本的 Web
開發的界限。它們建立在二十多年前發明的技術之上。他們的出現是爲了讓2018年的開發者更加輕鬆,而不是1998。他們瘋狂地優化自身以滿足現在和現在的需求。我們正在使用組件而不是 HTML模板
構建 Web
應用程序。雖然還沒有實現,但我想象一個未來,我們坐在一起,爲瀏覽器發明一個基於組件的標準。Angular
,Vue
和 React
只是這一運動的先鋒。
在下文中,我想通過示例深入介紹一些受歡迎的 React Hooks
,以幫助您加速瞭解這一變化。所有示例都可以在此GitHub存儲庫中找到。
React useState Hook
上文中,您已經在一個典型的計數器示例的代碼片段中看到過 useState Hook
。它用於管理函數組件中的本地狀態。讓我們在一個更詳細的例子中使用 Hook
,我們將管理一系列項目。在我的另一篇文章中,您可以瞭解有關在React中將數組作爲狀態進行管理的更多信息,但這次我們使用 React Hook
進行操作。讓我們開始吧:
import React, { useState } from 'react';
const INITIAL_LIST = [
{
id: '0',
title: 'React with RxJS for State Management Tutorial',
url:
'https://www.robinwieruch.de/react-rxjs-state-management-tutorial/',
},
{
id: '1',
title: 'A complete React with Apollo and GraphQL Tutorial',
url: 'https://www.robinwieruch.de/react-graphql-apollo-tutorial',
},
];
function App() {
const [list, setList] = useState(INITIAL_LIST);
return (
<ul>
{list.map(item => (
<li key={item.id}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
useState Hook
接受一個初始狀態作爲參數,並通過使用數組解構返回兩個可以命名的變量,您可以爲它們取需要的名字。第一個變量是實際狀態,而第二個變量是一個可以設置並返回新狀態的函數。
這個示例的下一步是從列表中刪除子元素。爲了實現它,我們爲列表中的每個子元素都設置了一個可單擊按鈕。單擊處理程序會使用一個內聯函數實現,因爲稍後會在內聯函數中使用 list
以及 setList
。 因此,您不需要將這些變量傳遞給處理程序,因爲它們已經可以從組件的外部作用域中獲得。
function App() {
const [list, setList] = useState(INITIAL_LIST);
function onRemoveItem() {
// remove item from "list"
// set the new list in state with "setList"
}
return (
<ul>
{list.map(item => (
<li key={item.id}>
<a href={item.url}>{item.title}</a>
<button type="button" onClick={onRemoveItem}>
Remove
</button>
</li>
))}
</ul>
);
}
因爲一些原因,我們需要從列表中刪除的子元素。使用高階函數,我們可以將子元素的標識符傳遞給處理函數。否則,我們將無法從列表中刪除的這些子元素。
function App() {
const [list, setList] = useState(INITIAL_LIST);
function onRemoveItem(id) {
// remove item from "list"
// set the new list in state with "setList"
}
return (
<ul>
{list.map(item => (
<li key={item.id}>
<a href={item.url}>{item.title}</a>
<button type="button" onClick={() => onRemoveItem(item.id)}>
Remove
</button>
</li>
))}
</ul>
);
}
最後,使用數組的內置方法過濾列表,刪除包含標識符的子元素。它返回一個新列表,用於設置列表的新狀態。
function App() {
const [list, setList] = useState(INITIAL_LIST);
function onRemoveItem(id) {
const newList = list.filter(item => item.id !== id);
setList(newList);
}
return (
<ul>
{list.map(item => (
<li key={item.id}>
<a href={item.url}>{item.title}</a>
<button type="button" onClick={() => onRemoveItem(item.id)}>
Remove
</button>
</li>
))}
</ul>
);
}
此時應該已經完成了。您可以根據傳遞給處理程序的標識符從列表中刪除子元素。然後,處理函數過濾列表並使用 setList
函數設置列表的新狀態。
useState Hook
爲您提供了在函數組件中管理狀態所需的一切:初始狀態,最新狀態和狀態更新功能。其他一切都是 JavaScript
。此外,您不需要像以前一樣在類組件中使用淺合併來保持 state
的更新。相反,您使用 useState
封裝一個域(例如列表),但如果您需要另一個狀態(例如計數器),則只需使用另一個 useState
封裝此域。您可以在 React
的文檔中閱讀有關 useState Hook
的更多信息。
React useEffect Hook
讓我們轉到下一個名爲 useEffect
的 Hook
。如前所述,功能組件應該能夠使用 Hook
管理狀態和副作用。上面我們已經使用 useState Hook
展示了管理狀態。現在將 useEffect Hook
用於副作用,這些副作用通常用於與 Browser
/ DOM API
或外部 API
(如數據獲取)的交互。讓我們看一下如何通過實現一個簡單的秒錶,將 useEffect Hook
和 Browser API
相結合。您可以在此 GitHub
倉庫中查看使用 React
類組件需要如何完成。
import React, { useState } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
return (
<div>
{!isOn && (
<button type="button" onClick={() => setIsOn(true)}>
Start
</button>
)}
{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}
</div>
);
}
export default App;
現在還沒有秒錶。但現在至少有條件渲染顯示 “開始” 或 “停止” 按鈕。並且由 useState hook
來管理布爾標誌的狀態。
接下來讓我們加入副作用:用 useEffect
來註冊一個 interval
定時器。定時器函數每秒會向您的瀏覽器控制檯發出一條記錄。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
setInterval(() => console.log('tick'), 1000);
});
return (
<div>
{!isOn && (
<button type="button" onClick={() => setIsOn(true)}>
Start
</button>
)}
{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}
</div>
);
}
export default App;
爲了在組件卸載的時候移除定時器(以及每次渲染更新之後),您可以在 useEffect
中返回一個函數,以便清理任何內容。對於上面的例子,當組件不再存在時,不應該留下任何內存泄漏。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
const interval = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(interval);
});
...
}
export default App;
現在,您需要在安裝組件時設置副作用,並在卸載組件時清除副作用。如果您需要記錄函數的調用次數,您會看到每次組件狀態發生變化時它都會設置一個新的定時器(例如,單擊“開始”/“停止”按鈕)。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
console.log('effect runs');
const interval = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(interval);
});
...
}
export default App;
爲了僅在組件的 mount
和 unmount
時響應 ,可以將空數組作爲第二個參數傳遞給它。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
const interval = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(interval);
}, []);
...
}
export default App;
但是,由於在每次渲染之後都會清除定時器,我們也需要在我們的業務週期中設置定時器。我們也可以告訴 effect
僅在 isOn
變量發生變化時運行。僅當數組中的一個變量發生更改時,effect
纔會在更新週期中運行。如果將數組保持爲空, effect
將僅在 mount
和 unmount
時運行,因爲沒有要檢查的變量是否再次運行副作用。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
const interval = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(interval);
}, [isOn]);
...
}
export default App;
現在,無論 isOn
布爾值是 true
還是 false
,定時器都在運行。接下來,我們希望定時器僅在秒錶啓動的時候運行。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(() => console.log('tick'), 1000);
}
return () => clearInterval(interval);
}, [isOn]);
...
}
export default App;
現在在功能組件中引入另一個狀態來跟蹤秒錶的計時器。它用於更新計時器,但僅在秒錶被激活時使用。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
const [timer, setTimer] = useState(0);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(timer + 1),
1000,
);
}
return () => clearInterval(interval);
}, [isOn]);
return (
<div>
{timer}
{!isOn && (
<button type="button" onClick={() => setIsOn(true)}>
Start
</button>
)}
{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}
</div>
);
}
export default App;
代碼中仍然存在一個錯誤。當定時器運行時,它會將 timer
每秒加 1
。但是,它始終依賴於計時器的初始狀態在累加。只有當 inOn
布爾標誌改變時,狀態纔會正常顯示。爲了在計時器在運行時,始終接收最新的狀態,您可以使用函數代替代替狀態,是的每次更新的時候都可以拿到最新的狀態。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
const [timer, setTimer] = useState(0);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(timer => timer + 1),
1000,
);
}
return () => clearInterval(interval);
}, [isOn]);
...
}
export default App;
另一種方法是,在計時器改變時,運行 effect
。然後 effect
將收到最新的計時器狀態。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
const [timer, setTimer] = useState(0);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(timer + 1),
1000,
);
}
return () => clearInterval(interval);
}, [isOn, timer]);
...
}
export default App;
這是使用瀏覽器 API
實現的秒錶效果,如果您想繼續,您也可以通過提供 “重置” 按鈕來擴展示例。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
const [timer, setTimer] = useState(0);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(timer => timer + 1),
1000,
);
}
return () => clearInterval(interval);
}, [isOn]);
const onReset = () => {
setIsOn(false);
setTimer(0);
};
return (
<div>
{timer}
{!isOn && (
<button type="button" onClick={() => setIsOn(true)}>
Start
</button>
)}
{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}
<button type="button" disabled={timer === 0} onClick={onReset}>
Reset
</button>
</div>
);
}
export default App;
That’s it. 使用 useEffect hook
來管理 React
函數組件的副作用,比如 Browser / DOM API
或其他第三方 API
的交互(例如數據獲取)。您可以在 React
官方文檔 useEffect hook
部分 獲取更多信息。
自定義 React Hooks
最後同樣重要的是,在您瞭解了兩個最常用的在函數組件中引入狀態和副作用的 Hooks
之後,我還想告訴您最後一件事:自定義 Hooks
。沒錯,您可以實現自己的自定義 React Hooks
,在您的應用程序中或提供給其他人中使用。讓我們看看接下來的示例:一個能夠檢測您的設備是在線還是離線的應用程序是如何工作的。
import React, { useState } from 'react';
function App() {
const [isOffline, setIsOffline] = useState(false);
if (isOffline) {
return <div>Sorry, you are offline ...</div>;
}
return <div>You are online!</div>;
}
export default App;
接下來,爲副作用引入 useEffect hook
。在這種情況下,effect
會添加和刪除檢查設備是聯機還是脫機的監聽器。兩個監聽器在 mount
時只設置一次,在 unmount
時清理一次(空數組作爲第二個參數)。每當調用其中一個監聽器時,它就會設置 isOffline
布爾值的狀態。
import React, { useState, useEffect } from 'react';
function App() {
const [isOffline, setIsOffline] = useState(false);
function onOffline() {
setIsOffline(true);
}
function onOnline() {
setIsOffline(false);
}
useEffect(() => {
window.addEventListener('offline', onOffline);
window.addEventListener('online', onOnline);
return () => {
window.removeEventListener('offline', onOffline);
window.removeEventListener('online', onOnline);
};
}, []);
if (isOffline) {
return <div>Sorry, you are offline ...</div>;
}
return <div>You are online!</div>;
}
export default App;
現在,讓我們將這些功能封裝在一個 Effect
中。這是一個很棒的功能,我們希望其他地方也可以重用。這就是爲什麼我們可以提取功能,作爲一個自定義 Hook
, 它遵循與其他 Hook
相同的命名約定。
import React, { useState, useEffect } from 'react';
function useOffline() {
const [isOffline, setIsOffline] = useState(false);
function onOffline() {
setIsOffline(true);
}
function onOnline() {
setIsOffline(false);
}
useEffect(() => {
window.addEventListener('offline', onOffline);
window.addEventListener('online', onOnline);
return () => {
window.removeEventListener('offline', onOffline);
window.removeEventListener('online', onOnline);
};
}, []);
return isOffline;
}
function App() {
const isOffline = useOffline();
if (isOffline) {
return <div>Sorry, you are offline ...</div>;
}
return <div>You are online!</div>;
}
export default App;
將自定義 Hook
作爲函數提取出來並不是唯一的事情。您還必須將 isOffline
狀態從自定義 Hook
中返回,以便在應用程序中向用戶顯示是否離線的消息。否則,它應該呈現正常的應用程序。這就是自定義 Hook
,它可以檢測您是在線還是離線。您可以在 React
的文檔中閱讀有關自定義 Hook
的更多信息。
可重用的 React Hooks
最棒的地方,是它有可能發展自定義 React Hooks
的生態系統,我們可以從 npm
爲任何 React
應用程序安裝 Hooks
。而且不僅僅是 React
應用程序。Vue
的作者 Evan You (尤雨溪) 也被 Hooks
迷住了!。也許我們會看到兩個生態系統之間的橋樑,可以在 Vue
和 React
之間共享 Hooks
。
如果您想深入瞭解 state hook
和 effect hook
,可以查看以下 React Hooks
教程:
查看 React
文檔中關於鉤子的官方FAQ和規則,以瞭解有關其細粒度行爲的更多信息。此外,您還可以查看所有官方提供的React Hooks。
對於仍然關注 React Hooks
的每個人:給自己一個機會。使用狀態和副作用實現幾個 React
函數組件。我們必須自己去理解它們是如何工作的,感受這種編程體驗的升級。我必須說使用它們感覺非常棒。
譯註:給我一個機會,重學 React ~
本文已經聯繫原文作者,並授權翻譯,轉載請保留原文鏈接