React Hooks 入門記錄

什么是 Hooks

以往,React 組件都是通過 class 的形式來編寫夭咬,只有無狀態(tài)組件才可以用函數(shù)來編寫啃炸。Hooks 就允許我們在函數(shù)組件中使用預(yù)定義函數(shù),來標(biāo)記狀態(tài)和組件生命周期皱埠,這使得所有組件都可以使用函數(shù)來編寫肮帐。

類組件的劣勢

    1. 狀態(tài)邏輯難復(fù)用
      缺少復(fù)用機制
      渲染屬性和高階組件導(dǎo)致層級冗余
    1. 趨向復(fù)雜難以維護
      生命周期函數(shù)混雜不相干邏輯
      想干邏輯分散在不同生命周期
    1. this指向困擾
      內(nèi)聯(lián)函數(shù)過度創(chuàng)建新句柄
      類成員函數(shù)不能保證this

優(yōu)化類組件的三大問題

  • 函數(shù)組件無 this 問題
  • 自定義 Hook 方便復(fù)用狀態(tài)邏輯
  • 副作用的關(guān)注點分離

Hooks 使用法則

官方文檔: https://reactjs.org/docs/hooks-rules.html

  • 僅在頂層調(diào)用 Hooks 函數(shù)

整個 Hooks 函數(shù)都依賴調(diào)用順序,這樣 React 才能在不同的渲染周期中把相同的邏輯關(guān)聯(lián)起來边器,一旦 Hooks 函數(shù)不在頂層調(diào)用训枢,那么很有可能在組件的不同渲染周期中,他們的調(diào)用順序發(fā)生了變化忘巧,進而導(dǎo)致變量混亂等錯誤恒界,為了盡量規(guī)避這些問題,所以盡量把 Hooks 放在最頂層砚嘴。

  • 僅在函數(shù)組件及 函數(shù)組件 中調(diào)用 Hooks 函數(shù)十酣,不能放在普通函數(shù)及類組件中

Hooks 函數(shù)必須是use開頭

Hooks 常見問題

生命周期函數(shù)如何映射到 Hooks ?

getDerivedStateFromError() 這個處理錯誤的函數(shù)际长,暫時無法在 Hooks 中實現(xiàn)耸采,由此看出 Hooks 暫時無法代替類組件

function App () {
  useEffect(() => {
    // componentDidMount
    return () => {
      // componentWillUnmount
    }
  }, [])
  let renderCouter = useRef(0) // 通過 ref 來保持渲染計數(shù)
  renderCouter.current++

  useEffect(() => {
    if (renderCouter > 1) {
      // componentDidUpdate
    }
  })
}

類實例成員變量如何映射到 Hooks ?

使用 useRef(0),同步到 current 中

Hooks 中如何獲取歷史 props 和 state

使用 useRef(0)工育,同步到 current 中

如何強制更新一個 Hooks 組件

創(chuàng)建一個不參與渲染的 state 的值虾宇,通過修改這個 state 的值來實現(xiàn)強制渲染

function Counter () {
  const [updater, setUpdater] = useState(0)
  function forceUpdate () {
    setUpdater(updater => updater + 1)
  }
}

使用 eslint-plugin-react-hooks 防止代碼出錯

1.安裝

npm install eslint-plugin-react-hooks -D

2.在package.json 中配置

// package.json
...
"eslintConfig": {
    "extends": "react-app",
    "plugins": [
        "react-hooks"
    ],
    "rules": {
        "react-hooks/rules-of-hooks": "error"
    }
},
...

簡單使用 useState

使用傳統(tǒng)類組件

import React, { Component } from 'react'
class ClassComponent extends Component {
  state = {
    count: 0
  }
  render () {
    const { count } = this.state
    return (
      <button onClick={() => {this.setState({ count: count + 1 })}}>
        ClassComponent Click Count ({count})
      </button>
    )
  }
}

使用 Hooks

import React, { Component, useState } from 'react'
function HooksComponent () {
  // useState 傳入 count 的默認(rèn)值
  // count 獲取值
  // setCount 設(shè)置值
  const [count, setCount] = useState(0)
  return (
    <button onClick={ () => { setCount(count + 1)} }>
        HooksComponent Click Count ({count})
    </button>
  )
}

通過 props 設(shè)置默認(rèn)值

import React, { Component, useState } from 'react'
function HooksComponetDefault (props) {
  // 使用 props 設(shè)置默認(rèn)值,useState 傳入的函數(shù)只會被調(diào)用一次
  const [count, setCount] = useState(() => {
    return props.defaultCount || 0
  })
  return (
    <button onClick={ () => { setCount(count + 1)} }>
        HooksComponent Click Count ({count})
    </button>
  )
}

使用 Effect Hooks

常見副作用:

  • 綁定事件
  • 網(wǎng)絡(luò)請求
  • 訪問DoM

常見的副作用時機:

  • Mount之后(componentDidMount)
  • Update之后(componentDidUpdate)
  • Unmount之前(componentWillUnmount)

以上的時機在傳統(tǒng)類組件中如绸,是在生命周期中解決嘱朽,在 hooks 中的解決方案:useEffect()

關(guān)于 useEffect()

userEffect()標(biāo)準(zhǔn)上是在組件渲染(render)之后調(diào)用旭贬,并且根據(jù)自定義狀態(tài)來決定是否調(diào)用,函數(shù)組件第一次渲染后的調(diào)用搪泳,就相當(dāng)于componentDidMount()稀轨,后面的調(diào)用都相當(dāng)于componentDidUpdate()

userEffect()可以返回一個回調(diào)函數(shù)岸军,這個回調(diào)函數(shù)的執(zhí)行時機跟userEffect()執(zhí)行時機掛鉤奋刽。這個回調(diào)函數(shù)的主要作用是 清除上一次副作用所遺留下來的狀態(tài)

useEffect() 的參數(shù)

【第一個參數(shù)】第一個參數(shù)為一個函數(shù),返回值為回調(diào)函數(shù)凛膏⊙蠲回調(diào)函數(shù)在視圖被銷毀的時候觸發(fā),1.組件重渲染猖毫,2.組件被卸載

【第二個參數(shù)】如果不填,則每次渲染后都會執(zhí)行须喂。第二個參數(shù)為一個數(shù)組吁断,只有數(shù)組的每一項都不變的情況下,userEffect()才不會執(zhí)行坞生,因此仔役,傳一個空數(shù)組的話,該 useEffect 就只會在第一次調(diào)用一次

簡單使用

function HooksCompinentUseEffect () {
  const [ count, setCount ] = useState(0)
  const [ size, setSize ] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
  })
  const onWindowResize = () => {
    setSize({
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight
    })
  }

  // useEffect 第一個參數(shù)為一個函數(shù)
  // 這里類似 componentDidount 和 componentDidUpdate
  useEffect(() => {
    document.title = count
  })

  // useEffect 第一個參數(shù)為一個函數(shù)是己,第二個參數(shù)為一個數(shù)組又兵,只有數(shù)組的每一項都不變的情況下,userEffect()才不會執(zhí)行
  // 這里類似 componentDidMount 和 componentWillUnmount
  useEffect(() => {
    window.addEventListener('resize', onWindowResize, false)
    // 回調(diào)函數(shù)在視圖被銷毀的時候觸發(fā)卒废,1.組件重渲染沛厨,2.組件被卸載
    return () => {
      window.removeEventListener('resize', onWindowResize, false)
    }
  },  [])
  return (
    <div>
      <button onClick={() => { setCount(count + 1)} }>
        HooksCompinentUseEffect Click Count ({count})
      </button>
      W: {size.width} | H: {size.height}
    </div> 
  )
}

Hooks環(huán)境下的Context

不要濫用 context,因為會破壞組件的獨立性

import React, { useState, createContext, useContext } from 'react'
// 使用 context
const CountContext = createContext()
function HooksContextProvider () {
  const [ count, setCount ] = useState(0)
  return (
    <div>
      <button onClick={() => { setCount(count + 1) }}>HooksContextProvider {count}</button>
      {/* value 中設(shè)置的是需要共享的值 */}
      <CountContext.Provider value={count}>
        <HooksContextConsumer />
      </CountContext.Provider>
    </div>
  )
}
// 使用 useContext 調(diào)用摔认,參數(shù)為創(chuàng)建的 Provider
function HooksContextConsumer () {
  const count = useContext(CountContext)
  return (
    <div>
      HooksContextConsumer: <b>{count}</b>
    </div>
  )
}

使用 useMemo 和 memo

useMemo 和 memo 的區(qū)別逆皮,memo針對的是一個組件的渲染是否重復(fù)執(zhí)行,useMemo定義了一段函數(shù)是否重復(fù)執(zhí)行参袱。本質(zhì)都是利用同樣的算法电谣,來判斷依賴是否發(fā)生改變,進而決定是否觸發(fā)特定邏輯抹蚀,同樣都用來做性能優(yōu)化剿牺。

useMemo的參數(shù)

第一個參數(shù)為要執(zhí)行的邏輯函數(shù)

第二個參數(shù)為這個函數(shù)鎖依賴的變量組成的數(shù)組,如果不傳則 useMemo 的邏輯每次都執(zhí)行环壤,如果傳入空數(shù)組就執(zhí)行執(zhí)行一次

與 useEffect 的差異

兩個函數(shù)的調(diào)用時機不同晒来,useEffect() 執(zhí)行的是副作用,所以一定是在渲染完成之后運行镐捧,而useMemo() 是需要有返回值潜索,返回值直接參與渲染臭增,因為 useMemo() 是在渲染期間完成的。兩者存在一前一后的區(qū)別

useMemo 簡單使用

import React, { useState, useMemo } from 'react'
// 使用 memo
function HookMemoParent () {
  const [ count, setCount ] = useState(0)
  // 第一個參數(shù)為要執(zhí)行的邏輯函數(shù)
  // 第二個參數(shù)為這個函數(shù)鎖依賴的變量組成的數(shù)組竹习,如果不傳則 useMemo 的邏輯每次都執(zhí)行誊抛,如果傳入空數(shù)組就執(zhí)行執(zhí)行一次
  // 當(dāng)?shù)诙€參數(shù)發(fā)生變化時,就會觸發(fā)邏輯整陌,跟數(shù)組是什么值無關(guān)
  const doubleCount = useMemo(() => {
    return count * 2
  }, [count])

  // 根據(jù)第二個參數(shù)拗窃,count < 3 時,保持不變泌辫,不會重新計算
  // 當(dāng)數(shù)組中的 bool 值發(fā)生改變時随夸,就會重新渲染,false => true => false震放,所以 boolCount 會重新渲染兩次
  const boolCount = useMemo(() => {
    return count * 3
  }, [count === 3])

  return (
    <div>
      <button onClick={() => {setCount(count + 1)}}>HookMemoParent: {count}宾毒,doubleCount: {doubleCount}, boolCount: {boolCount}</button>
      <HooksMemoChild count={doubleCount}></HooksMemoChild>
    </div>
  )
}
function HooksMemoChild (props) {
  return (
    <div>{props.count}</div>
  )
}

useMemo 和 useCallback

當(dāng)需要用 useMemo 返回一個函數(shù)時,可以使用 useCallback 代替殿遂,可以替代上一層函數(shù)诈铛。使用 useCallback 不能解決阻止創(chuàng)建新的函數(shù),因為每次組件的函數(shù)執(zhí)行都會創(chuàng)建新的函數(shù)墨礁,但是創(chuàng)建的這個函數(shù)不一定能夠被返回幢竹,很可能會被直接棄用。useCallback解決的是傳入子組件的函數(shù)參數(shù)過渡變化恩静,導(dǎo)致子組件過渡渲染的問題焕毫。實際上 useCallback 只是 useMemo 的一種簡寫。

【特別提醒】使用 useMemo 和 useCallback 時驶乾,當(dāng)依賴變化時邑飒,useMemo 和 useCallback 一定重新執(zhí)行。但是轻掩,當(dāng)依賴沒變化時幸乒,不能保證它們一定不執(zhí)行,也可能重新執(zhí)行唇牧,這是考慮內(nèi)存優(yōu)化的結(jié)果罕扎,react 官方的文檔中也沒有打包票一定不執(zhí)行。所以丐重,useMemo 和 useCallback 可以作為一種錦上添花的方案腔召,不可以過渡依賴它們是否重新執(zhí)行。

useCallback的參數(shù)

第一個參數(shù)為要執(zhí)行的邏輯函數(shù)

第二個參數(shù)為這個函數(shù)鎖依賴的變量組成的數(shù)組扮惦,如果不傳則 useMemo 的邏輯每次都執(zhí)行臀蛛,如果傳入空數(shù)組就執(zhí)行執(zhí)行一次

useMemo(() => {
  return () => { console.log("click") }
}, [])
// 上下兩者等價
useCallback(() => {
  console.log("click")
}, [])

使用 useRef

  • 獲取子組件或者DOM節(jié)點的句柄
  • 獲取跨越渲染周期的任意數(shù)據(jù)

useRef 和 useState 區(qū)別

state 的賦值會觸發(fā)組件重渲染,但是 ref 不會

【特別用法】

如果在組件中,需要使用上次渲染的數(shù)據(jù)浊仆,可以使用useRef客峭,同步到 current 中

// useRef
function HooksUseRef () {
  const [ count, setCount ] = useState(0)
  // 聲明ref
  const countRef = useRef()
  // 將定時器實例,通步在useRef中抡柿,這樣就可以防止每次渲染時重復(fù)調(diào)用
  const timer = useRef()

  // 給子組件傳遞的事件
  const bindChildClick = useCallback(() => {
    console.log('click')
    // 通過 current 獲取 DOM 節(jié)點
    console.log(countRef.current)
    // 通過 ref 調(diào)用子組件的方法
    countRef.current.speak()
  }, [countRef])

  // 使 count 自增
  useEffect(() => {
    timer.current = setInterval(() => {
      setCount(count => count + 1)
    }, 1000)
    return () => {
      cleanup
    }
  }, [])
  // 監(jiān)測 count 大于10舔琅,停止自增
  useEffect(() => {
    if (count >= 10) {
      clearInterval(timer.current)
    }
  })

  return (
    <div>
      <button onClick={() => {setCount(count + 1)}}>HooksUseRef: {count}</button>
      {/* 給類組件的ref屬性賦值 */}
      <HooksUseRefClassChild ref={countRef} count={count} onClick={bindChildClick}></HooksUseRefClassChild>
    </div>
  )
}
// 類組件獲取 ref
class HooksUseRefClassChild extends PureComponent {
  speak () {
    console.log('this is child function: ', this.props.count)
  }
  render () {
    const { props } = this
    return <b onClick={props.onClick}> HooksUseRefClassChild: {props.count}</b>
  }
}

自定義 Hooks 函數(shù)

// 自定義 hooks 函數(shù),一定要使用 use 開頭
function useCount (defaultCount) {
  const [count, setCount] = useState(defaultCount)
  const timer = useRef()
  // 使 count 自增
  useEffect(() => {
    timer.current = setInterval(() => {
      setCount(count => count + 1)
    }, 1000)
    return () => {
      cleanup
    }
  }, [])
  // 監(jiān)測 count 大于10洲劣,停止自增
  useEffect(() => {
    if (count >= 10) {
      clearInterval(timer.current)
    }
  })
  // 這里的返回值可以自定義备蚓,我參考 useState 的返回
  return [count, setCount]
}
// 自定義 hooks 函數(shù),返回 JSX
function useCountJSX (defaultCount) {
  return (
    <b>{defaultCount}</b>
  )
}
function HooksSelfFunciton () {
  const [count, setCount] = useCount(0) // 模仿原生的 useState
  const CountJSX = useCountJSX(count) // 直接返回 JSX
  return (
    <div>
      <button onClick={() => {setCount(count + 1)}}>useCount: {count}</button>
      useCountJSX: {CountJSX}
    </div>
  )
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末囱稽,一起剝皮案震驚了整個濱河市郊尝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌战惊,老刑警劉巖流昏,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吞获,居然都是意外死亡横缔,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門衫哥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人襟锐,你說我怎么就攤上這事撤逢。” “怎么了粮坞?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵蚊荣,是天一觀的道長。 經(jīng)常有香客問我莫杈,道長互例,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任筝闹,我火速辦了婚禮媳叨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘关顷。我一直安慰自己糊秆,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布议双。 她就那樣靜靜地躺著痘番,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上汞舱,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天伍纫,我揣著相機與錄音,去河邊找鬼昂芜。 笑死莹规,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的说铃。 我是一名探鬼主播访惜,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼腻扇!你這毒婦竟也來了债热?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤幼苛,失蹤者是張志新(化名)和其女友劉穎窒篱,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舶沿,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡墙杯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了括荡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片高镐。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖畸冲,靈堂內(nèi)的尸體忽然破棺而出嫉髓,到底是詐尸還是另有隱情,我是刑警寧澤邑闲,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布算行,位于F島的核電站,受9級特大地震影響苫耸,放射性物質(zhì)發(fā)生泄漏州邢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一褪子、第九天 我趴在偏房一處隱蔽的房頂上張望量淌。 院中可真熱鬧,春花似錦褐筛、人聲如沸类少。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽硫狞。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間残吩,已是汗流浹背财忽。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留泣侮,地道東北人即彪。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像活尊,于是被迫代替她去往敵國和親隶校。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 在學(xué)會使用React Hooks之前蛹锰,可以先看一下相關(guān)原理學(xué)習(xí)React Hooks 前言 在 React 的世界...
    DC_er閱讀 9,069評論 1 16
  • Hooks 是React的一次革命性升級深胳,本文將對其優(yōu)勢和API進行比較全面的解析 為什么要有hooks 在沒有h...
    smartzheng閱讀 931評論 0 5
  • 目錄 什么是 React Hooks? 為什么要創(chuàng)造 Hooks铜犬? Hooks API 一覽 Hooks 使用規(guī)則...
    一個笑點低的妹紙閱讀 1,071評論 0 2
  • Hook 是 React 16.8 的新增特性舞终。它可以讓你在不編寫 class 的情況下使用 state 以及其他...
    孤獨的小色狼閱讀 363評論 0 0
  • 你還在為該使用無狀態(tài)組件(Function)還是有狀態(tài)組件(Class)而煩惱嗎?——擁有了hooks癣猾,你再也不需...
    水落斜陽閱讀 82,318評論 11 100