React Hooks 入門(2019)

備註:爲了保證的可讀性,本文采用意譯而非直譯。react

在這個 React鉤子 教程中,你將學習如何使用 React鉤子,它們是什麼,以及咱們爲何這樣作!json

圖片描述

主要內容

  1. 你將學到什麼
  2. 要求
  3. 項目設置
  4. 一開始有 setState
  5. 更新 React 中的狀態,沒有 setState
  6. 開始時有 componentDidMount(和 render props)
  7. 使用 useEffect 獲取數據
  8. 可使用 React 鉤子渲染嗎?
  9. 第一個自定義 React 鉤子
  10. 可使用 async / await 和 useEffect 嗎?
  11. 結語
  12. 附錄

一、你將學到什麼

  • 如何使用 React 鉤子
  • 如何在 React 類組件中實現相同的邏輯

. 二、要求

學習如下內容,你應該基本瞭解設計模式

  • ES6(箭頭函數、解構、類)
  • React

三、項目設置

這裏默認你已經配置好 React 開發環境,咱們試着安裝數組

npx create-react-app exploring-hooks

四、一開始有 setState

假設你已經在你的項目中使用了 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 中的內部狀態,是由於再也不須要 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>
}

除了有特殊要求外,我更喜歡常規功能而不是箭頭函數的方式。可讀性提升了不少。此外,當我編寫代碼時,我老是認爲下一個開發人員將保留代碼。這裏的代碼應該是可讀更強的。

六、開始時有 componentDidMount(和 render props)

在 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>
    )
  }}
/>

七、使用 useEffect 獲取數據

我認爲使用 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
//

八、可使用 React 鉤子渲染嗎?

固然!可是這樣作沒有意義。咱們的 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 鉤子

咱們能夠將咱們的邏輯封裝在 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 嗎?

當你在使用 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。

備註:原文連接地址 https://www.valentinog.com/bl...

相關文章
相關標籤/搜索