本文內容大部分參考了 overreacted.io 博客一文,同時結合 React Hook 官方 文章,整理並概括一些筆記和輸出我的的一些理解
官方介紹:Hook 是 React 16.8 的新增特性。它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性。
基礎 Hookjavascript
useStatehtml
// 傳入初始值,做爲 state const [state, setState] = useState(initialState) // `惰性初始 state`;傳入函數,由函數計算出的值做爲 state // 此函數只在初始渲染時被調用 const [state, setState] = useState(() => { const initialState = someExpensiveComputation(props) return initialState })
useEffectjava
清除 effect
一般,組件卸載時須要清除 effect 建立的諸如訂閱或計時器 ID 等資源。要實現這一點,useEffect 函數需返回一個清除函數。如下就是一個建立訂閱的例子:react
useEffect(() => { const subscription = props.source.subscribe() return () => { // 清除訂閱 subscription.unsubscribe() } }, [依賴])
額外的 Hookios
React 沒有提供將可複用性行爲「附加」到組件的途徑(例如,把組件鏈接到 store)。若是你使用過 React 一段時間,你也許會熟悉一些解決此類問題的方案,好比 render props 和 高階組件。可是這類方案須要從新組織你的組件結構,這可能會很麻煩,使你的代碼難以理解。若是你在 React DevTools 中觀察過 React 應用,你會發現由 providers,consumers,高階組件,render props 等其餘抽象層組成的組件會造成「嵌套地獄」。儘管咱們能夠在 DevTools 過濾掉它們,但這說明了一個更深層次的問題:React 須要爲共享狀態邏輯提供更好的原生途徑。
你可使用 Hook 從組件中提取狀態邏輯,使得這些邏輯能夠單獨測試並複用。Hook 使你在無需修改組件結構的狀況下複用狀態邏輯。 這使得在組件間或社區內共享 Hook 變得更便捷。ajax
咱們常常維護一些組件,組件起初很簡單,可是逐漸會被狀態邏輯和反作用充斥。每一個生命週期經常包含一些不相關的邏輯。例如,組件經常在 componentDidMount 和 componentDidUpdate 中獲取數據。可是,同一個 componentDidMount 中可能也包含不少其它的邏輯,如設置事件監聽,而以後需在 componentWillUnmount 中清除。相互關聯且須要對照修改的代碼被進行了拆分,而徹底不相關的代碼卻在同一個方法中組合在一塊兒。如此很容易產生 bug,而且致使邏輯不一致。
在多數狀況下,不可能將組件拆分爲更小的粒度,由於狀態邏輯無處不在。這也給測試帶來了必定挑戰。同時,這也是不少人將 React 與狀態管理庫結合使用的緣由之一。可是,這每每會引入了不少抽象概念,須要你在不一樣的文件之間來回切換,使得複用變得更加困難。npm
爲了解決這個問題,Hook 將組件中相互關聯的部分拆分紅更小的函數(好比設置訂閱或請求數據),而並不是強制按照生命週期劃分。你還可使用 reducer 來管理組件的內部狀態,使其更加可預測。json
下面的代碼能夠直觀的體現出來,在某些場景下,使用 hook 來實現對應的功能,能夠節省大部分的代碼axios
對比 Class
組件來講,清除反作用要簡單的多,以下代碼,在 useEffect
hook 裏面返回一個函數,當咱們的函數組件卸載的時候,就會自動執行這個函數,從而來清除反作用。想一想咱們在 Class
組件裏面須要在 componentWillUnmount
生命週期裏面去編寫對應的代碼。windows
對比二者咱們發現,使用 useEffect
的方式,可以將掛載和卸載的邏輯更加緊密的耦合在一塊兒,從而減小 BUG 的發生
useEffect(() => { const id = setInterval(() => { setCount(count => count + 1) }, 1000) return () => clearInterval(id) }, []) // 好比給 windows 掛載監聽函數 useEffect(() => { window.addEventListener('reszie', handleRezie) return () => { window.removeEventListener('resize', handleRezie) } }, [])
不要在普通的 JavaScript 函數中調用 Hook。你能夠
使用 useEffect 完成反作用操做,賦值給 useEffect 的函數會在組件渲染到屏幕以後;牢記這句話。
仔細觀察以下代碼,當函數組件裏面,有多個 effect
的時候,默認的 effect
將在每次 UI render 以後被調用。當咱們經過 useEffect
的第二個數組類型參數,指明當前 effect
的依賴,就能避免不相關的執行開銷了。
經過啓用 eslint-plugin-react-hooks 插件,來強制提醒咱們在使用 effect
的時候,申明所須要的依賴
{ "plugins": [ // ... "react-hooks" ], "rules": { // ... "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn" } }
const CounterHook = () => { const [count, setCount] = useState(0) const [name, setName] = useState('heaven') useEffect(() => { document.title = `counterWithHook ${count}` }, [count]) useEffect(() => { console.log('you name is', name) }, [name]) return ( <div> <h3>Counter with Hook</h3> <p>You click {count} times</p> <button onClick={e => setCount(count => count + 1)}>Click me</button> <p> <input placeholder="輸入姓名" onChange={e => setName(e.target.value)} /> <br /> your name is {name} </p> </div> ) }
對於 useEffect
內部方法,一旦引用外部的函數,那麼這個時候須要注意了:
須要把 useEffect 內部引用到的方式,聲明爲當前 effect 的依賴
在下圖的代碼中,咱們能夠看到,在 effect 函數內部,引入外部的函數,咱們的 eslint-plugin-react-hooks 插件會自動提示咱們須要把對應的函數做爲依賴添加進去
不規範示例:這裏在安裝了插件的狀況下,會自動提示咱們將 fetchData
函數移入 effect 內部
const getFetchUrl = () => { return `https://hn.algolia.com/api/v1/search?query=${query}` } const fetchData = async () => { return axios.get(getFetchUrl()) } useEffect(() => { fetchData().then(resp => { console.log(resp) setData(resp.data) }) }, [])
正確的寫法:
useEffect(() => { const getFetchUrl = () => { return `https://hn.algolia.com/api/v1/search?query=${query}` } const fetchData = async () => { return axios.get(getFetchUrl()) } fetchData().then(resp => { console.log(resp) setData(resp.data) }) }, [query])
每一次渲染都有它本身的 Props and State
每一次渲染都有它本身的事件處理函數
每次渲染都有它本身的 Effects
運行以下代碼以後,在咱們點擊 Show alert
按鈕以後,而後點擊 Click me
按鈕,alert
輸出的永遠是在點擊的那個時刻的 count;
換句話來講;在 hooks 組件裏面,每一次的渲染,都至關於記錄當前次的『快照』
import React, { useEffect, useState } from 'react' const Counter = () => { const [count, setCount] = useState(0) const handleAlertClick = () => { setTimeout(() => { alert(`Yout clicked me: ${count}`) }, 3000) } useEffect(() => { setTimeout(() => { console.log(`Yout clicked ${count} times`) }, 3000) }) return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> <button onClick={handleAlertClick}>Show alert</button> </div> ) } export default Counter
經過自定義 Hook,能夠將組件邏輯提取到可重用的函數中。
當咱們想在兩個函數之間共享邏輯時,咱們會把它提取到第三個函數中。而組件和 Hook 都是函數,因此也一樣適用這種方式。
自定義 Hook 是一個函數,其名稱以 「use」 開頭,函數內部能夠調用其餘的 Hook。
useService.js
自定義的一個 server hook,該 hook 封裝了 ajax
請求中的 { loading, error, response }
三個基礎邏輯;有了這個 hook
咱們就能很輕鬆的在每次網絡請求裏面去處理各類異常邏輯了;詳細用法看文章最後的 Table 分頁操做實例
import { useEffect, useRef, useState, useCallback } from 'react' import { isEqual } from 'lodash' const useService = (service, params) => { const prevParams = useRef(null) const [callback, { loading, error, response }] = useServiceCallback(service) useEffect(() => { if (!isEqual(prevParams.current, params)) { prevParams.current = params callback(params) } }) return { loading, error, response } } const useServiceCallback = service => { const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [response, setResponse] = useState(null) // 使用 useCallback,來判斷 service 是否改變 const callback = useCallback( params => { setLoading(true) setError(null) service(params) .then(response => { console.log(response) setLoading(false) setResponse(response) }) .catch(error => { setLoading(false) setError(error) }) }, [service] ) return [callback, { loading, error, response }] }
以下代碼,使用 hook
的方式來實現表格的分頁,數據請求操做,
使用 hook
實現一個簡易版的跑馬燈抽獎邏輯