React Hook 基礎使用

前言

本文主要記錄 Hook 中比較重要的知識點以及實際應用中遇到的問題,還有查閱相關文檔使用的一些心得html

若是文章中有出現紕漏、錯誤之處,還請看到的小夥伴多多指教,先行謝過前端

如下↓react

初識

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

雖然在學習 Hook 的過程當中瞭解 class 組件並非必須的,可是若是你想更快更容易地理解 Hook 仍是須要先了解 class 組件的,由於不管是 官網 仍是其餘大部分文檔在介紹 Hook 的時候都是經過與 class 組件對比的git

至於爲何會有 Hook,它解決了咱們開發中的什麼問題,這不在本文的記錄範圍內,感興趣的朋友能夠 參考這裏github

基本

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

function Example() {
  // 聲明一個叫 「count」 的 state 變量。
  const [count, setCount] = useState(0);
  
  useEffect(()=> {
      console.log('加載了…')
      return () => {
          console.log('解綁了…')
      }
  })

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

上面是一個簡單的 Hook,當咱們點擊按鈕的時候,頁面上的數字書遞增,同時控制檯也會打印出不一樣的信息web

最直觀的感受,咱們不再須要定義 class 類來使用 state 維護組件自身的狀態了,而是使用 useState 來生成組件的 state 和控制它的方法。一樣的沒有藉助組件的生命週期,咱們註冊在 useEffect 中的方法在首次加載和 state 更新的時候也執行了數組

在這裏,useStateuseEffect 就是 不一樣的 Hook。經過在函數中調用不一樣的 Hook ,就能夠實現使用 class 實現的一些功能瀏覽器

注意點

  • Hook 是一些能夠在函數組件裏鉤入React state 及生命週期等特性的函數。不能在 class 組件中使用
  • 只能在函數最外層調用 Hook 。不要在循環、條件判斷或者子函數中調用
  • 只能在 React 的函數組件或自定義 Hook 中調用 Hook
  • Hook 是一種複用狀態邏輯的方式,它不復用 state 自己,Hook 的每次調用都有一個徹底獨立的 state,簡單來講就是一個新的 state
  • Hook 使用了 JavaScript 的閉包機制

瞭解 Hook 的這些特性,對於咱們熟悉並使用 Hook 會有很大的幫助緩存

推薦閱讀 爲何 順序調用對 React Hook 很重要性能優化

基礎 Hook

useState

const [state, setState] = useState(initialState)

特性:

  • useState 是一個函數,返回值是一個數組(當前 state 以及更新 state 的函數), 惟一的參數就是初始 state
  • 初始渲染期間,返回的狀態 (state) 與傳入的第一個參數值相同
  • setState 函數用於更新 state。它接收一個新的 state 值並將組件的一次從新渲染加入隊列
  • initialState 只會在初識渲染中起做用,後續會被忽略,若是傳入一個函數就只會在初始渲染時被調用,若是爲空,變量的值就是 undefined
const [name, setName] = useState()

name // undefined

// 這個函數只會執行一次
const [count, setCount] =useState(()=> {
    const initialState = someExpensiveComputation(props);
    return initialState;
})
  • 更新 state 變量老是替換它而不是合併它,this.setstate()會自動合併
// 好比咱們修改 state 中的某一個屬性

const [state, setState] = useState({
    name: '遊蕩de蝌蚪',
    age: 18,
    hobby: 'play game'
})

// 使用 class
this.setstate({
    age: 17
})

// 使用 Hook
setState(prevState => {
    return {...prevState, age: 17}
})
  • 能夠屢次聲明

另外,在使用 useState 聲明變量的時候,咱們能夠單獨聲明每個變量,也能夠直接使用一個對象或數組

// 單獨聲明
const [height, setHeight] = useState(10)
const [width, setWidth] = useState(20)
const [left, setLeft] = useState(10)
const [top, setTop] = useState(10)

// 也能夠直接使用一個對象
const [state, setState] = useState({
    height: 10,
    width: 20,
    left: 10,
    top: 10
})

在實際的使用中,按照邏輯將 state 分組是最佳實踐,避免了 state 對象過於臃腫也很好地將有關聯的 state 進行了分組維護。好比,上面聲明的幾個變量咱們能夠這樣進行分類

const [box, setBox] = useState({
    height: 10,
    width: 20
})

const [position, setPosition] = useState({
    left: 10,
    top: 10
})

useEffect

useEffect(() => {
    effect
    return () => {
        cleanup
    };
}, [input])

特性:

  • 默認狀況下,它在第一次渲染以後和每次更新以後都會執行
  • 能夠把 useEffect Hook 看作 componentDidMountcomponentDidUpdatecomponentWillUnmount 這三個函數的組合
// 這個函數會在首次加載、state更新以及組件卸載的時候執行
useEffect(() => {
    console.log('方法執行了')
    return () => {
        console.log('解綁了')
    }
})
  • componentDidMountcomponentDidUpdate 不一樣,使用 useEffect 調度的 effect 不會阻塞瀏覽器更新屏幕,也就是說它是異步的
  • 可使用多個 effect 用來分離不一樣的邏輯(好比按照不一樣的用途),也就是說咱們能夠添加多個 effect
  • 能夠配置 effect 的第二個參數來設置依賴項,當這個依賴項改變的時候再去執行這個函數,從而實現性能優化,若是咱們只想讓這個函數在首次加載和卸載的時候執行,那麼能夠傳一個空數組
// 只有在 count 變化的時候,函數纔會執行
useEffect(() => {
    console.log('方法執行了')
    return () => {
        console.log('解綁了')
    }
}, [count]) 

// 只在首次加載和卸載的時候執行
useEffect(() => {
    console.log('方法執行了')
    return () => {
        console.log('解綁了')
    }
}, [])
  • 必須在依賴中包含全部 effect 中用到的組件內的值
  • 清除函數會在組件卸載前執行。若是組件屢次渲染(一般如此),則在執行下一個 effect 以前,上一個 effect 就已被清除
  • 若是隻想在更新的時候運行 effect ,那麼可使用一個可變的 ref 手動存儲一個 boolean 值來判斷
  • effect 拿到的老是定義它的那次渲染中的 propsstate

推薦閱讀 useEffect 完整指南 以及 如何在Effect中發送請求

useContext

const value = useContext(MyContext)

若是對於 context 並非十分熟悉,能夠先點擊 這裏,瞭解 context 的內容

特性:

  • 接收一個 context 對象(React.createContext 的返回值)並返回該 context 的當前值
  • useContext 的參數必須是 context 對象自己
  • 當前的 context 值由上層組件中距離當前組件最近的 <MyContext.Provider>value prop 決定
  • 調用了 useContext 的組件總會在 context 值變化時從新渲染
// 官方文檔的案例,省去中間組件
const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
}

const ThemeContext = React.createContext(themes.light);

function ThemedButton() {
  const theme = useContext(ThemeContext);

  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  )
}

其餘 Hook

useReducer

useState 的替代方案,若是熟悉 Redux ,那麼理解 Reducer 就很容易了

  • 接收一個形如 (state, action) => newStatereducer,並返回當前的 state 以及與其配套的 dispatch 方法
const initialState = {count: 0};

// action 用來表示觸發的行爲
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
  • reducer 本質是一個純函數,每次只返回一個值,那個值能夠是數字,字符串,對象,數組或者對象,可是它老是一個值
  • React 會確保 dispatch 函數的標識是穩定的,而且不會在組件從新渲染時改變
  • useReducer 還能給那些會觸發深更新的組件作性能優化,由於能夠向子組件傳遞 dispatch 而不是回調函數
  • reducer 更適合去處理比較複雜的 state,來維護組件的狀態

上面示例是一種基礎初始化 state 的方式,咱們也能夠選擇 惰性初始化 的方式

// init 是一個函數,返回值就是 state
const [state, dispatch] = useReducer(reducer, initialArg, init)

若是使用這種方式初始化,state 的值就是 init(initialArg)

useRef

const refContainer = useRef(initialValue)
  • useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化爲傳入的參數(initialValue)。
  • 返回的 ref 對象在組件的整個生命週期內保持不變,能夠很方便地保存任何可變值
  • 變動 .current 屬性不會引起組件從新渲染
  • useRef() 建立的是一個普通的 Javascript 對象,而且每次渲染返回的都是同一個對象
注意點

在理解 useRef 以前,咱們必定要清楚的是對於函數組件而言,每一次狀態的改變都是會從新觸發 render。也就是說,咱們在組件狀態變化的時候拿到的值已是一個全新的數據,只是 react 幫咱們記住了以前的數據

利用 ref 對象的這個特性,咱們能夠實現:

  • 好比以前說過的 只在更新時運行 effect
  • 獲取上一輪的 propsstate
  • 訪問子組件變得很容易

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
)

函數組件的每一次調用都會執行內部的全部邏輯,帶來較大的性能損耗,因此 useCallback 出現了,它的做用就是解決性能問題。須要使用到緩存函數的地方,都是 useCallback 的應用場景

最多見的場景就是父組件傳遞給子組件的函數,props 中的某個依賴項不發生變化的狀況下,使用 useCallback 使子組件沒必要執行更新

將內聯函數和依賴項數組做爲參數傳入 useCallback,該回調函數僅在某個依賴項改變時纔會更新,從而實現性能優化

  • 會在組件第一次渲染和依賴項更新的時候執行
  • 返回一個緩存的函數
  • 不要在這個函數內部執行與渲染無關的操做,諸如反作用這類的操做
  • 若是沒有提供依賴項數組,useCallback 在每次渲染是都會執行;若是是一個空數組,那麼就只會在首次渲染的時候計算一次

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])

useCallback 同樣,做爲性能優化的方式存在。與 useCallback 不一樣的是:useMemo 返回的是一個緩存的值

  • 會在組件第一次渲染和依賴項更新的時候去從新計算緩存的值
  • 返回一個緩存的值
  • 不要在這個函數內部執行與渲染無關的操做,諸如反作用這類的操做
  • 若是沒有提供依賴項數組,useMemo 在每次渲染時都會計算新的值,若是是一個空數組,那麼就只會在首次渲染的時候計算一次

自定義 Hook

經過自定義 Hook,能夠將組件邏輯提取到可重用的函數中

hook 以前,咱們通常會使用 render props 和 高階函數來共享組件之間的狀態邏輯

如今,咱們可使用自定義 Hook 的方式來實現一樣的功能,並且可使邏輯條理更加清晰

  • 自定義 Hook 是一個函數,其名稱以 use 開頭,函數內部能夠調用其餘的 Hook
  • 不須要具備特殊的標識。咱們能夠自由的決定它的參數是什麼,以及它應該返回什麼
  • 自定義 Hook 是一種重用狀態邏輯的機制,並不會共享數據
function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  return isOnline;
}

後記

以上主要介紹了 Hook 的一些重要特性以及常常會使用到的 Hook,文章大部份內容以及示例都來自官網,而後就是本身整理總結的一點東西,知識點的整合

本文全部的示例,均可以在 這裏 找到

感興趣的小夥伴能夠 點擊這裏 ,也能夠掃描下方二維碼關注個人微信公衆號,查看更多前端小片斷,歡迎 star 關注

image

相關文章
相關標籤/搜索