備註:爲了保證的可讀性,本文采用意譯而非直譯。react
在這個 React鉤子 教程中,你將學習如何使用 React鉤子,它們是什麼,以及咱們爲何這樣作!json
學習如下內容,你應該基本瞭解設計模式
這裏默認你已經配置好 React 開發環境,咱們試着安裝數組
npx create-react-app exploring-hooks
假設你已經在你的項目中使用了 React,讓咱們快速回顧一下:promise
React 是一個用於構建用戶界面的庫,其優勢之一是庫自己會向開發人員強加嚴格的數據流。你還記得 jQuery 嗎?使用 jQuery,不太可能清楚地構建項目,更不用說定義數據應如何在 UI 中展現。這很難跟蹤哪些功能正在改變哪一個 UI 視圖。閉包
這一樣適用於普通的 JavaScript:即便有自我解釋和實踐,也能夠提出一個結構良好的項目(考慮模塊模式),好的跟蹤狀態和功能之間的相互做用(請參閱 Redux )。app
React 在某種程度上緩解了什麼問題呢:經過強制執行清晰的結構(容器 和 功能組件) 和 嚴格的數據流(組件對狀態 和 道具更改 作出反應),如今比之前更容易建立合理的 UI 視圖邏輯。異步
所以,React 理論上是,一個 UI 能夠 「 響應 」 以響應狀態變化。到目前爲止,表達這種流程的基本形式是 ES6 課程。考慮如下示例:從React.Component 擴展的 ES6 類,具備內部狀態:async
import React, { Component } from "react" export default class Button extends Component { constructor() { super(); this.state = { buttonText: "Click me, please" } this.handleClick = this.handleClick.bind(this) } handleClick() { this.setState(() => { return { buttonText: "Thanks, been clicked!" } }); } render() { const { buttonText } = this.state return <button onClick = {this.handleClick}>{buttonText}</button> } }
從上面的代碼中能夠看出,當單擊按鈕時,組件的內部狀態會被 setState 改變。按鈕依次響應並更改獲取更新的文本。函數
因爲 類字段,刪除構造函數能夠表示更簡潔的組件版本:
import React, { Component } from "react" export default class Button extends Component { state = { buttonText: "Click me, please" } handleClick = () => { this.setState(() => { return { buttonText: "Thanks, been clicked!" } }) }; render() { const { buttonText } = this.state return <button onClick={this.handleClick}>{buttonText}</button> } }
那麼咱們如今有什麼選擇來管理 React 中的內部狀態,是由於再也不須要 setState 和 類 了嗎?
第一個也是最重要的 React 鉤子:useState。useState 是 react 暴露的函數。將在文件頂部引入它:
import React, { useState } from "react"
經過在代碼中導入 useState,在 React 組件中保存某種狀態的意圖。更重要的是,React 組件再也不是 ES6 類。它能夠是一個純粹而簡單的 JavaScript 函數。
導入 useState 後,將選擇一個包含兩個變量的數組,這些變量不在 useState 中,代碼應該放在 React 組件中:
const [buttonText, setButtonText] = useState("Click me, please")
若是對這種語法感到困惑,其實這是 ES6 解構。上面的名字能夠是你想要的任何東西,對 React 來講可有可無。
因此前面的例子,一個帶有鉤子的按鈕組件會變成:
import React, { useState } from "react" export default function Button() { const [buttonText, setButtonText] = useState("Click me, please") return ( <button onClick={() => setButtonText("Thanks, been clicked!")}> {buttonText} </button> ) }
要在 onClick 處理程序中調用 setButtonText 狀態更新程序,可使用箭頭函數。但若是你更喜歡使用常見的功能,你能夠:
import React, { useState } from "react" export default function Button() { const [buttonText, setButtonText] = useState("Click me, please") function handleClick() { return setButtonText("Thanks, been clicked!") } return <button onClick={handleClick}>{buttonText}</button> }
除了有特殊要求外,我更喜歡常規功能而不是箭頭函數的方式。可讀性提升了不少。此外,當我編寫代碼時,我老是認爲下一個開發人員將保留代碼。這裏的代碼應該是可讀更強的。
在 React 中獲取數據!你還記得 componentDidMount 嗎?你能夠在 componentDidMount 中點擊提取(url)。如下是如何從 API 獲取數據以及如何展現爲一個列表:
import React, { Component } from "react" export default class DataLoader extends Component { state = { data: [] } componentDidMount() { fetch("http://localhost:3001/links/") .then(response => response.json()) .then(data => this.setState(() => { return { data } }) ) } render() { return ( <div> <ul> {this.state.data.map(el => ( <li key={el.id}>{el.title}</li> ))} </ul> </div> ) } }
你甚至能夠在 componentDidMount 中使用 async / await,但有一些注意事項。可是在項目中的大多數的異步邏輯都存在 React 組件以外。上面的代碼還有一些缺點。
渲染列表他是固定的,但使用渲染道具,咱們能夠輕鬆地將子項做爲函數傳遞。重構的組件以下所示:
import React, { Component } from "react" export default class DataLoader extends Component { state = { data: [] } componentDidMount() { fetch("http://localhost:3001/links/") .then(response => response.json()) .then(data => this.setState(() => { return { data } }) ) } render() { return this.props.render(this.state.data) } }
而且你將經過從外部提供渲染道具來使用當前該組件:
<DataLoader render={data => { return ( <div> <ul> {data.map(el => ( <li key={el.id}>{el.title}</li> ))} </ul> </div> ) }} />
我認爲使用 React 鉤子獲取數據不該該與 useState 有什麼不一樣。工做中看文檔給了我一個提示:useEffect 多是正確的一個工具。
當看到:「 useEffect 與 React 類中的 componentDidMount,componentDidUpdate 和 componentWillUnmount 具備相同的用途時,統一爲單個 API 」。
並且我可使用 setData(從 useState 中提取的任意函數)代替調用 this.setState:
import React, { useState, useEffect } from "react" export default function DataLoader() { const [data, setData] = useState([]) useEffect(() => { fetch("http://localhost:3001/links/") .then(response => response.json()) .then(data => setData(data)); }); return ( <div> <ul> {data.map(el => ( <li key={el.id}>{el.title}</li> ))} </ul> </div> ) }
在這一點上,我想「 可能出現什麼問題? 」,這是在控制檯中看到的:
這顯然是個人錯,由於我已經知道發生了什麼:
「 useEffect 與 componentDidMount,componentDidUpdate 和 componentWillUnmount 具備相同的用途
componentDidUpdate!componentDidUpdate 是一個生命週期方法,每當組件得到新的道具或狀態發生變化時運行。
這就是訣竅。若是你像我同樣調用 useEffect,你會看到無限循環。要解決這個「 bug 」,你須要傳遞一個空數組做爲 useEffect 的第二個參數:
// useEffect(() => { fetch("http://localhost:3001/links/") .then(response => response.json()) .then(data => setData(data)) }, []); // << super important array //
固然!可是這樣作沒有意義。咱們的 DataLoader 組件將會變爲:
import React, { useState, useEffect } from "react" export default function DataLoader(props) { const [data, setData] = useState([]) useEffect(() => { fetch("http://localhost:3001/links/") .then(response => response.json()) .then(data => setData(data)) }, []); // << super important array return props.render(data) }
而且你將經過從外部提供渲染道具來消耗組件,就像咱們在前面的示例中所作的那樣。
但一樣,這種重構沒有意義,由於 React 鉤子的誕生是有緣由的:在組件之間共享邏輯,咱們將在下一節中看到一個例子。
咱們能夠將咱們的邏輯封裝在 React 鉤子中,而後在咱們須要引入該鉤子時,而不是 HO C和 渲染道具。在咱們的示例中,咱們能夠建立用於獲取數據的自定義掛鉤。
根據 React 文檔,自定義鉤子是一個 JavaScript 函數,其名稱以「 use 」開頭。比提及來容易。讓咱們建立一個 useFetch :
// useFetch.js import { useState, useEffect } from "react" export default function useFetch(url) { const [data, setData] = useState([]) useEffect(() => { fetch(url) .then(response => response.json()) .then(data => setData(data)) }, []); return data }
這就是你如何使用自定義鉤子:
import React from "react" import useFetch from "./useFetch" export default function DataLoader(props) { const data = useFetch("http://localhost:3001/links/") return ( <div> <ul> {data.map(el => ( <li key={el.id}>{el.title}</li> ))} </ul> </div> ) }
這就是使鉤子如此吸引人的緣由:最後咱們有一個 有趣的、標準化的、乾淨 的方式來封裝和共享邏輯。
當你在使用 useEffect 時想在鉤子裏嘗試 async / await。讓咱們看看咱們的自定義:
// useFetch.js import { useState, useEffect } from "react" export default function useFetch(url) { const [data, setData] = useState([]) useEffect(() => { fetch(url) .then(response => response.json()) .then(data => setData(data)) }, []) return data }
對於 重構異步 / 等待 最天然的事情你可能會:
// useFetch.js import { useState, useEffect } from "react" export default function useFetch(url) { const [data, setData] = useState([]) useEffect(async () => { const response = await fetch(url) const data = await response.json() setData(data) }, []) return data }
而後打開控制檯,React 正在提示着:
「警告:不能返回任何內容。
事實證實不能從 useEffect 返回一個 Promise。JavaScript 異步函數老是返回一個 promise,而 useEffect 應該只返回另外一個函數,該函數用於清除效果。也就是說,若是你要在 useEffect 中啓動 setInterval,你將返回一個函數(咱們有一個閉包)來清除間隔。
所以,爲了使 React,咱們能夠像這樣重寫咱們的異步邏輯:
// useFetch.js import { useState, useEffect } from "react" export default function useFetch(url) { const [data, setData] = useState([]) async function getData() { const response = await fetch(url) const data = await response.json() setData(data) } useEffect(() => { getData() }, []) return data }
這裏,你的自定義鉤子將再次運行。
React hooks 是庫的一個很好補充。他們於 2018年10月 做爲 RFC 發佈,很快就遇上了 React 16.8。將 React 鉤子想象爲生活在 React 組件以外的封裝狀態。
React 鉤子使渲染道具和 HOC 幾乎過期,併爲共享邏輯提供了更好的人體工程學。使用 React 鉤子,你能夠在 React 組件之間重用常見的邏輯片斷。
React 附帶一堆預約義的鉤子。最重要的是 useState 和 useEffect。useState 能夠在 React 組件中使用本地狀態,而無需使用 ES6 類。
useEffec t替換了提供統一 API:componentDidMount,componentDidUpdate 和 componentWillUnmount。還有不少其餘的鉤子,我建議閱讀官方文檔以瞭解更多信息。
很容易預見React的發展方向:功能組件遍及各處!但即使如此,咱們仍是有三種方法能夠在 React 中表達組件:
在文章的開頭我說:「 使用 jQuery,幾乎不可能清楚地構建項目,更不用說定義數據應如何在 UI 中展現」。
可是你可能不須要 React 來構建用戶界面。有時我使用 vanilla JavaScript 構建項目。當我不肯定該項目將採用什麼形狀時,我用來建立一個沒有任何 JavaScript 庫的簡單原型。
在這些項目中,我依靠模塊的方式來編寫代碼。
可以正確組織和編寫你的代碼,即便使用 vanilla JavaScript 也是每一個 JavaScript 開發人員的寶貴資產。爲了更多地瞭解 JavaScript 中的模塊方式,我建議你由 Todd Motto 掌握模塊方式和 Addy Osmani 的 JavaScript 設計模式。
另外一方面,跟蹤 UI 中的狀態變化確實很難。對於這種工做,我最喜歡的是 Redux,甚至可使用 vanilla JavaScript。