原文: What are React Hooks?
做者:Robin Wieruch
譯者:博軒
React Hooks
於 2018年10月的React Conf 中引入,做爲在 React
函數組件中使用狀態和生命週期的一種方法。雖然函數組件以前被稱爲 無狀態組件(FSC) ,可是 React Hooks
的出現,使得這些函數組件可使用狀態。所以,如今許多人將它們視爲功能組件。javascript
在這篇文章中,我會解釋這些 Hooks
背後的動機,React
會發生什麼改變,爲何咱們不該該恐慌,以及如何在函數式組件中使用常見的 React Hooks
,好比 state 和 生命週期。html
譯註:長文預警 🤓
React Hooks
是由 React
團隊發明的,用於在函數組件中引入狀態管理和生命週期方法。若是咱們但願一個 React
函數組件能夠擁有 狀態管理和生命週期方法,我不須要再去將一個 React
函數組件重構成一個 React
類組件。React Hooks
讓咱們能夠僅使用函數組件就能夠完成一個 React 應用
。java
之前,只有 React
類組件可使用 本地狀態管理 和 生命週期方法 。後者對於在 React
類組件中引入反作用(如監聽DOM事件,異步加載數據)相當重要。react
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
類組件(反之亦然)。git
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
函數組件中變得可用。這也讓 無狀態組件
到 功能組件
的重塑變得切實可行。github
在 React
類組件中,反作用主要在生命週期方法中引入 (例如 componentDidMount
,componentDidUpdate
,componentWillUnmount
) 。反作用多是在 React
中獲取數據,或 與 Browser API
進行交互 。一般這些反作用會伴隨着設置和清理階段。例如,若是您忘記了去刪除監聽器,就會遇到一些 React
性能問題 。npm
// 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
中能夠經過 高階組件 和 Render Props 來實現抽象和可重用性。還有 React Context
及其Provider
和消費者組件 所提供的另外一個層次的抽象。React
中的全部這些高級模式都使用了所謂的包裝組件。對於正在建立更大的 React
應用程序的開發人員來講,如下組件的實現應該並不陌生。api
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
很好地混合了兩個世界:面向對象編程(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
像 Angular
同樣變得臃腫!Hooks
沒有用,classes
很好讓我在這裏解決這些問題:
React Hooks
將改變咱們未來編寫 React
應用程序的方式。可是,目前沒有任何變化。您仍然可使用本地狀態和生命週期方法編寫類組件,並部署高級模式,例如高階組件或 Render Props
組件。沒有人會把這些知識從你身邊帶走。瞭解我如何將全部開源項目從舊版本升級到 React 16.6
。這些項目都沒有問題。他們正在使用 HOC
,Render Props
,甚至是較爲古老的 context API
(若是我錯了,請糾正我)。這些年來我所學到的一切仍然有效。React
團隊確保 React
保持向後兼容。它與 React 16.7
保持相同。
React
做爲一個第三方庫,其 API
老是會給人一種短小精幹的感受 。如今是這樣,未來也是如此。可是,考慮到幾年前基於組件所構建的應用,在升級的時候不被其餘更加先進的庫所取代, React
引入了有利於舊 API
的變動。若是 React
是9012年發佈的,也許他的功能只會保留函數組件和 Hooks
。但 React
幾年前就已經發布,它在發佈新的特性的同時,還須要兼容以前的版本。可能會在幾年棄用類組件和其生命週期方法,轉而使用 React
函數組件和 Hooks
,但目前,React
團隊仍將 React
類組件保留在了他們的工具庫中。畢竟, React
團隊但願利用新的特性 Hooks
來陪伴 React
贏得一場馬拉松,而不是一場短跑。顯然,React Hooks
爲 React
添加了另外一個 API
,但它有利於在將來簡化 React
的新 API
。我喜歡這種轉變,而不是擁有 "React 2",讓一切都不一樣。
譯註:這是在吐槽
Angular
嘛?😂
想象一下,你將從零開始學習 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 在他的關於 Hooks
文章的話:
React
在哪裏爲
Hooks
保持狀態。答案是它保存在
React
爲類保持狀態的徹底相同的位置。不管你如何定義你的組件,
React
都會在內部維護一個更新隊列」
基於組件的解決方案(如 Angular
,Vue
和 React
)正在推進每一個版本的 Web
開發的界限。它們創建在二十多年前發明的技術之上。他們的出現是爲了讓2018年的開發者更加輕鬆,而不是1998。他們瘋狂地優化自身以知足如今和如今的需求。咱們正在使用組件而不是 HTML模板
構建 Web
應用程序。雖然尚未實現,但我想象一個將來,咱們坐在一塊兒,爲瀏覽器發明一個基於組件的標準。Angular
,Vue
和 React
只是這一運動的先鋒。
在下文中,我想經過示例深刻介紹一些受歡迎的 React Hooks
,以幫助您加速瞭解這一變化。全部示例均可以在此GitHub存儲庫中找到。
上文中,您已經在一個典型的計數器示例的代碼片斷中看到過 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
的更多信息。
讓咱們轉到下一個名爲 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
部分 獲取更多信息。
最後一樣重要的是,在您瞭解了兩個最經常使用的在函數組件中引入狀態和反作用的 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 ~ 本文已經聯繫原文做者,並受權翻譯,轉載請保留原文連接