React 新特性 Hooks 講解及實例(二)

本文是 React 系列的第二篇前端

  1. React 新特性講解及實例(一)

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!react

什麼是 Hooks

Hook 是 React 16.8 的新增特性。它可讓你在不編寫 類組件 的狀況下使用 state 以及其餘的 React 特性。git

類組件的不足

狀態邏輯複用難github

  • 缺乏複用機制
  • 渲染屬性和高階組件致使層級冗餘

趨向複雜難以維護npm

  • 生命週期函數混雜不相干邏輯
  • 相干邏輯分散在不一樣生命週期

this 指向困擾segmentfault

  • 內聯函數過分建立新句柄
  • 類成員函數不能保證 this

Hooks 優點

優化類組件的三大問題數組

  • 函數組件無 this 問題
  • 自定義 Hook 方便複用狀態邏輯
  • 反作用的關注點分離

使用 State Hook

import React, {Component} from 'react'

class App extends Component {
  state = {
    count: 0
  };
  render() {
    const {count} = this.state;
    return (
      <button type="button"
        onClick={() => {
          this.setState({
            count: count + 1
          })
        }}
      >Click({count})</button>
    )
  }
}
export default App;

以上代碼很好理解,點擊按鈕讓 count 值加 1dom

接下來咱們使用 useState 來實現上述功能。函數

import React, {useState} from 'react'

function App () {
  const [count, setCount] = useState(0)
  return (
    <button type="button"
      onClick={() => {setCount(count + 1) }}
    >Click({count})</button>
  )
}

在這裏,useState 就是一個 Hook。經過在函數組件裏調用它來給組件添加一些內部 state,React 會在重複渲染時保留這個 state學習

useState 會返回一對值:當前狀態和一個讓你更新它的函數。你能夠在事件處理函數中或其餘一些地方調用這個函數。它相似 class 組件的 this.setState,可是它不會把新的 state 和舊的 state 進行合併。useState 惟一的參數就是初始 state

useState 讓代碼看起來簡潔了,可是咱們可能會對組件中,直接調用 useState 返回的狀態會有些懵。既然 userState 沒有傳入任何的環境參數,它怎麼知道要返回的的是 count 的呢,並且仍是這個組件的 count 不是其它組件的 count

初淺的理解: useState 確實不知道咱們要返回的 count,但其實也不須要知道,它只要返回一個變量就好了。數組解構的語法讓咱們在調用 useState 時能夠給 state 變量取不一樣的名字。

useState 怎麼知道要返回當前組件的 state?

由於 JavaScript 是單線程的。在 useState 被調用時,它只能在惟一一個組件的上下文中。

有人可能會問,若是組件內有多個 usreState,那 useState 怎麼知道哪一次調用返回哪個 state 呢?

這個就是按照第一次運行的次序來順序來返回的。

接着上面的例子咱們在聲明一個 useState:

...
const [count, setScount] = useState(0)
const [name, setName] = useState('小智')
...

而後咱們就能夠判定,之後APP組件每次渲染的時候,useState 第一次調用必定是返回 count,第二次調用必定是返回 name。不信的話來作個實驗:

let id = 0

function App () {
  let name,setName;
  let count,setCount;
  
  id += 1;
  if (id & 1) {
    // 奇數
    [count, setCount] = useState(0)
    [name, setName] = useState('小智')
  } else {
    // 偶數
    [name, setName] = useState('小智')
    [count, setCount] = useState(0)
  }

  return (
    <button type="button"
      onClick={() => {setCount(count + 1) }}
    >
      Click({count}), name ({name})
    </button>
  )
}

首先在外部聲明一個 id,當 id爲奇數和偶數的時候分別讓 useState 調用方式相反,運行會看到有趣的現象。

圖片描述

當前版本若是寫的順序不一致就會報錯。

會發現 countname 的取值串了。咱們但願給 count 加 1,如今卻給 name 加了 1,說明 setCount 函數也串成了 setName 函數。

爲了防止咱們使用 useState 不當,React 提供了一個 ESlint 插件幫助咱們檢查。

優化點

經過上述咱們知道 useState 有個默認值,由於是默認值,因此在不一樣的渲染週期去傳入不一樣的值是沒有意義的,只有第一次傳入的纔有效。以下所示:

...
const defaultCount = props.defaultCount || 0
const [count, setCount] = useState(defaultCount)
...

state 的默認值是基於 props,在 APP 組件每次渲染的時候 const defaultCount = props.defaultCount || 0 都會運行一次,若是它複雜度比較高的話,那麼浪費的資料確定是可觀的。

useState 支持傳入函數,來延遲初始化:

const [count, setCount] = useState(() => {
  return props.defaultCount || 0
})

使用 Effect Hook

Effect Hook 可讓你在函數組件中執行反作用操做。數據獲取,設置訂閱以及手動更改 React 組件中的 DOM 都屬於反作用。無論你知不知道這些操做,或是"反作用"這個名字,應該都在組件中使用過它們。

反作用的時機

  • Mount 以後 對應 componentDidMount
  • Update 以後 對應 componentDidUpdate
  • Unmount 以前 對應 componentWillUnmount

如今使用 useEffect 就能夠覆蓋上述的狀況。

爲何一個 useEffect 就能涵蓋 Mount,Update,Unmount 等場景呢。

useEffect 標準上是在組件每次渲染以後調用,而且會根據自定義狀態來決定是否調用仍是不調用。

第一次調用就至關於componentDidMount,後面的調用至關於 componentDidUpdateuseEffect 還能夠返回另外一個回調函數,這個函數的執行時機很重要。做用是清除上一次反作用遺留下來的狀態。

clipboard.png

好比一個組件在第三次,第五次,第七次渲染後執行了 useEffect 邏輯,那麼回調函數就會在第四次,第六次和第八次渲染以前執行。嚴格來說,是在前一次的渲染視圖清除以前。若是 useEffect 是在第一次調用的,那麼它返回的回調函數就只會在組件卸載以前調用了,也就是
componentWillUnmount

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

舉粟說明一下:

class App extends Component {
  state = {
    count: 0,
    size: {
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight
    }
  };
  onResize = () => {
    this.setState({
      size: {
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
      }
    })
  }
  componentDidMount () {
    document.title = this.state.count;
    window.addEventListener('resize', this.onResize, false)
  }
  componentWillMount () {
    window.removeEventListener('resize', this.onResize, false)
  }
  componentDidUpdate () {
    document.title = this.state.count;
  }
  render() {
    const {count, size} = this.state;
    return (
      <button type="button"
        onClick={() => {this.setState({count: count + 1})}}
      >
        Click({count})
        size: {size.width}x{size.height}
      </button>
    )
  }
}

上面主要作的就是網頁 title 顯示count 值,並監聽網頁大小的變化。這裏用到了componentDidMountcomponentDidUpdate 等反作用,由於第一次掛載咱們須要把初始值給 title, 當 count 變化時,把變化後的值給它 title,這樣 title 才能實時的更新。

注意,咱們須要在兩個生命週期函數中編寫重複的代碼。

這邊咱們容易出錯的地方就是在組件結束以後要記住銷燬事件的註冊,否則會致使資源的泄漏。如今咱們把 App 組件的反作用用 useEffect 實現。

function App (props) {
  const [count, setCount] = useState(0);

  const [size, setSize] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
  });

  const onResize = () => {
    setSize({
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
      }
    )
  }

  useEffect(() => {
    document.title = count;
  })

  useEffect(() => {
    window.addEventListener('resize', onResize, false);
    return () => {
      window.removeEventListener('resize', onResize, false)
    }
  }, [])

  return (
    <button type="button"
      onClick={() => {setCount(count + 1) }}
    >
      Click({count})
      size: {size.width}x{size.height}
    </button>
  )
}

對於上述代碼的第一個 useEffect,相比類組件,Hooks 不在關心是 mount 仍是 update。用useEffect統一在渲染後調用,就完整追蹤了 count 的值。

對於第二個 useEffect,咱們能夠經過返回一個回調函數來註銷事件的註冊。回調函數在視圖被銷燬以前觸發,銷燬的緣由有兩種:從新渲染和組件卸載

這邊有個問題,既然 useEffect 每次渲染後都執行,那咱們每次都要綁定和解綁事件嗎?固然是徹底不須要,只要使用 useEffect 第二個參數,並傳入一個空數組便可。第二個參數是一個可選的數組參數,只有數組的每一項都不變的狀況下,useEffect 纔不會執行。第一次渲染以後,useEffect 確定會執行。因爲咱們傳入的空數組,空數組與空數組是相同的,所以 useEffect 只會在第一次執行一次。

這也說明咱們把 resize 相關的邏輯放在一直寫,不在像類組件那樣分散在兩個不一樣的生命週期內。同時咱們處理 title 的邏輯與 resize 的邏輯分別在兩個 useEffect 內處理,實現關注點分離。

咱們在定義一個 useEffect,來看看經過不一樣參數,第二個參數的不一樣做用。

...
useEffect(() => {
  console.log('count:', count)
}, [count])
...

第二個參數咱們傳入 [count], 表示只有 count 的變化時,我纔打印 count 值,resize 變化不會打印。

運行效果以下:

圖片描述

第二個參數的三種形態,undefined,空數組及非空數組,咱們都經歷過了,可是我們沒有看到過回調函數的執行。

如今有一種場景就是在組件中訪問 Dom 元素,在 Dom元素上綁定事件,在上述的代碼中添加如下代碼:

...
 const onClick = () => {
  console.log('click');
 }

 useEffect(() => {
   document.querySelector('#size').addEventListener('click', onClick, false);
 },[])
 
  return (
    <div>
    ...
    <span id="size">size: {size.width}x{size.height}</span>
  </div>
)

新增一個 DOM 元素,在新的 useEffect 中監聽 span 元素的點擊事件。

運行效果:

圖片描述

假如咱們 span 元素能夠被銷燬重建,咱們看看會發生什麼狀況,改造一下代碼:

return (
  <div>
  ...
  </button>
  {
    count%2
    ? <span id="size">我是span</span>
    : <p id='size'>我是p</p>
  }

</div>

運行效果:

圖片描述

能夠看出一旦 dom 元素被替換,咱們綁定的事件就失效了,因此我們始終要追蹤這個dom 元素的最新狀態。

使用 useEffect ,最合適的方式就是使用回調函數來處理了,同時要保證每次渲染後都要從新運行,因此不能給第二次參數設置 [],改造以下:

useEffect(() => {
 document.querySelector('#size').addEventListener('click', onClick, false);
   return () => {
     document.querySelector('#size').removeEventListener('click', onClick, false);
   }
})

運行結果:

圖片描述

參考

  1. React 官方文檔
  2. 《React勁爆新特性Hooks 重構去哪兒網》

交流

  1. React 官方文檔
  2. 《React勁爆新特性Hooks 重構去哪兒網》

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

https://github.com/qq44924588...

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

clipboard.png

相關文章
相關標籤/搜索