hooks 系列三:useEffect

快來加入咱們吧!

"小和山的菜鳥們",爲前端開發者提供技術相關資訊以及系列基礎文章。爲更好的用戶體驗,請您移至咱們官網小和山的菜鳥們 ( xhs-rookies.com/ ) 進行學習,及時獲取最新文章。前端

"Code tailor" ,若是您對咱們文章感興趣、或是想提一些建議,微信關注 「小和山的菜鳥們」 公衆號,與咱們取的聯繫,您也能夠在微信上觀看咱們的文章。每個建議或是贊同都是對咱們極大的鼓勵!react

前言

這篇文章,咱們主要目的是瞭解一下 共享狀態鉤子(useEffect) 的使用.web

useEffect

定義

Effect Hook 可讓你在函數組件中執行反作用操做,換句話說能夠完成一些相似 Class 中生命週期的功能。數組

如何使用

useEffect() 的使用以下面語句。微信

useEffect(() => {
    dosomeing(); // 執行一些反作用操做
},[]);
複製代碼

useEffect 須要參數markdown

  • 第一個參數:該 Hook 接收一個包含命令式、且可能有反作用代碼的函數。函數

  • 第二個參數:該 useEffect 在哪些 state 發生變化時,才從新執行;oop

useEffect 作了什麼學習

經過 useEffectHook,能夠告訴 React 須要在渲染後執行某些操做;而 useEffect 的第一個參數要求咱們傳遞一個函數,在 React 執行完更新 DOM 操做以後,就會執行這個函數,默認狀況下,不管是組件第一次渲染以後,仍是組件更新以後,都會執行這個函數,咱們能夠在這個要傳遞的函數中執行一些相似 Class 組件中生命週期要作的工做,好比:改變 DOM、添加訂閱、設置定時器、記錄日誌等。 要知道,在沒有這個 useEffect Hook 以前在函數組件內的這些反作用操做是不被容許的。優化

提示

若是你熟悉 React class 的生命週期函數,你能夠把 useEffect Hook 看作 componentDidMountcomponentDidUpdatecomponentWillUnmount 這三個函數的組合。

清除 effect

Class 組件中,某些反作用代碼,咱們須要在 componentWillUnmount 中清除,好比咱們的建立計時器與清除計時器,一樣的,在函數組件卸載時也須要清除 effect 建立的諸如訂閱或計時器 ID 等資源。要實現這一點,useEffect 函數需返回一個清除函數。如下就是一個建立計時器的例子:

const [count, addCount] = useState(0)
const [timer, addTimer] = useState(1000)

useEffect(() => {
  const interval = setInterval(() => {
    // 建立計時器
    addCount((val) => val + 1)
  }, timer)
  return () => {
    // 返回清除函數
    clearInterval(interval) // 清除計時器
  }
})
複製代碼

爲防止內存泄漏,清除函數會在組件卸載前執行。另外,若是組件屢次渲染(一般如此),則在執行下一個 effect 以前,上一個 effect 就已被清除。在上述示例中,意味着組件的每一次更新都會建立新的計時器。若想避免每次更新都觸發 effect 的執行,這就須要用到 useEffect 的第二個參數。

effect 的條件執行

默認狀況下,effect 會在每輪組件渲染完成後執行。這樣的話,一旦 effect 的依賴發生變化,它就會被從新建立。

然而,在某些場景下這麼作可能會矯枉過正。好比,上面計時器的例子中,咱們並不須要在每次組件更新時都建立新的計時器,而是僅須要在 source prop 改變時從新建立。

這裏就要用到 useEffect 接受的第二個參數,它是 effect 所依賴的值數組。更新後的示例以下:

const [count, addCount] = useState(0)
const [timer, addTimer] = useState(1000)

useEffect(() => {
  const interval = setInterval(() => {
    // 建立定時器
    addCount((val) => val + 1)
  }, timer)
  return () => {
    clearInterval(interval) // 清除定時器
  }
}, [count])
複製代碼

當傳入第二參數的時候,在這裏也就是當 count 改變後纔會從新建立計時器,也就是說,useEffect 會根據咱們傳入的第二參數,來決定本次更新是否執行。

注意

  • 若是你要使用此優化方式,請確保數組中包含了全部外部做用域中會發生變化且在 effect 中使用的變量,不然你的代碼會引用到先前渲染中的舊變量。
  • 若是想執行只運行一次的 effect(僅在組件掛載和卸載時執行),能夠傳遞一個空數組([])做爲第二個參數。

對比類組件

相信你如今已經對 useEffect 有所瞭解了,那咱們用一個簡單的例子,來感覺一下在函數組件中使用 HookClass 組件的區別。

有一個簡單的計時器,默認以 1000ms 的時間間隔計數,點擊加按鈕後,計時時間間隔增長,計時器計數變慢。

類組件實現

首先咱們要在類組件中聲明一個變量 count 來計數,一個變量 timer 來控制計數時間間隔,每隔 timer 個時間, count 加一,而後添加一個按鈕,每次點擊時, timer 增長 1000ms。同時 count 增長速度變慢。

class Example extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0,
      timer: 1000,
    }
  }

  componentDidMount() {
    this.interval = setInterval(() => {
      // 建立定時器
      this.setState({
        count: this.state.count + 1,
      })
    }, this.state.timer)
  }
  componentDidUpdate() {
    clearInterval(this.interval) // 先清除上一個定時器
    this.interval = setInterval(() => {
      // 建立以 更新後的timer 計時間隔定時器
      this.setState({
        count: this.state.count + 1,
      })
    }, this.state.timer)
  }
  componentWillUnmount() {
    clearInterval(this.interval) // 卸載時清除定時器
  }

  render() {
    return (
      <div> <p>當前count值爲:{this.state.count}</p> <p>當前計數時間間隔爲:{this.state.timer}ms</p> <button onClick={() => this.setState({ timer: this.state.timer + 1000 })}>Click me</button> </div>
    )
  }
}
複製代碼

咱們能夠發現,在這個 class 中,咱們須要在兩個生命週期函數中編寫重複的代碼。還要在組件卸載時清除計時器,大量的重複代碼,顯得很是繁瑣,那麼在函數組件中使用 Effect Hook 會是怎樣的呢?

使用 Effect Hook 實現

import React, { useState, useEffect } from 'react'

function Example() {
  const [count, addCount] = useState(0)
  const [timer, addTimer] = useState(1000)

  useEffect(() => {
    const interval = setInterval(() => {
      addCount((val) => val + 1)
    }, timer)
    return () => {
      clearInterval(interval)
    }
  }, [count])

  return (
    <div> <p>當前count的值爲:{count}</p> <p>當前計數時間間隔爲: {timer}ms</p> <button onClick={() => addTimer((val) => val + 1000)}>Click me</button> </div>
  )
}
複製代碼

看了這個例子以後,你是否是很驚奇的發現,對比 Class 組件的 constructor 函數沒有了,寫在三個生命週期中的函數在函數組件被 useEffect 一個傢伙解決了!是否是看起來很精簡。相信你有下面幾個問題:

  • useEffect 作了什麼?

    經過使用這個 Hook,你能夠告訴 React 組件須要在渲染後執行某些操做。React 會保存你傳遞的函數(咱們將它稱之爲 「effect」),而且在執行 DOM 更新以後調用它。

  • 爲何在組件內部調用 useEffect

    useEffect 放在組件內部讓咱們能夠在 effect 中直接訪問 count state 變量(或其餘 props)。

  • useEffect 會在每次渲染後都執行嗎?

    是的,默認狀況下,它在第一次渲染以後每次更新以後都會執行(你能夠利用前面提到的第二個參數來控制渲染條件)。並且,React 保證了每次運行 effect 的同時,DOM 都已經更新完畢。

小結

  • useEffect Hook 實際上爲咱們帶來了,在函數組件中也能處理反作用的功能,他比如componentDidMountcomponentDidUpdatecomponentWillUnmount 這三個函數的組合。

  • useEffect HookClass 函數的生命週期不一樣的一點:useEffect 在 DOM 都已經更新完畢才執行,componentDidMount 則是在組件掛在後(插入 DOM 樹中)當即調用,componentDidUpdate() 會在更新後會被當即調用。首次渲染不會執行此方法。componentWillUnmount() 會在組件卸載及銷燬以前直接調用。

  • useEffect Hook 的出現以前,組件開發中逐漸會被狀態邏輯和反作用充斥。每一個生命週期經常包含一些不相關的邏輯。而它的出現幫助咱們更好的拆分這些狀態邏輯,並不是強制按照生命週期劃分,爲開發增添了許多的可能。

下節預告

在下節中,咱們將爲你們介紹 useRefs ,敬請期待!

相關文章
相關標籤/搜索