React Hooks的使用總結

Hook使用規則

  1. 確保在React函數頂層使用Hooks,不能在循環、條件、嵌套函數中調用:當單個組件中有多個Hook時,React通過Hooks的調用順序來保證Hooks的狀態正確
  2. 只在函數組件或自定義Hook中調用

常用Hooks

useState

  1. 在函數組件內部使用useState,可以使該組件變成有狀態組件。這樣在不使用class組件情況下也可以使用state及其他特性
  2. 使用方法
const [state, setState] = useState(initialState);

參數:狀態初始值,可以是變量或函數

  • 變量:初始渲染期間,state與initialState的值相同
  • 函數:如果初始參數需要通過復雜的計算得到,可以傳入一個函數,返回initialState。當初始狀態需要通過高性能操作后才能獲得時,使用函數獲取初始值可以提高性能:該函數只在初始渲染時被調用,在后續組件渲染中不會再調用

返回值:包含兩個元素的數組,可解構

  • state:狀態值
  • setState:狀態更新函數。調用后,重新渲染組件,狀態更新到最新
  1. 惰性初始stateinitialState只會在組件的初始渲染中起作用,后續渲染時會被忽略
  2. 函數式更新:新的state依賴于先前的state時,可以為setState方法傳入一個函數,函數接收先前的state,返回更新后的值
  3. 跳過state更新:若為setState傳入當前state,React將跳過子組件的渲染及effect的執行
  4. 在多個useState調用中,渲染之間的調用順序必須相同
  5. 函數式更新可以防止合并更新問題
function Bulb(props) {
  const [bulbState, setBulbState] = useState(false);
  const switchBulb = () => {
    setTimeout(() => {
      setBulbState(!bulbState);
      // setBulbState(bulbState => !bulbState);
    }, 3000);
  };
  return (
    <>
      <div className={bulbState ? "bulbOn" : "bulbOff"}>我是燈泡</div>
      <button onClick={switchBulb}>開關</button>
    </>
  );
}
  • 使用普通更新:由于更新函數在setTimeout中調用,在3s內多次點擊時,多次state更新會合并成一次,只改變一次state的狀態
setBulbState(!bulbState);
  • 使用函數式更新:在3s內多次(n)點擊時,每次的更新函數確保收到的是最新的狀態,所以會在3s后切換n次狀態
setBulbState(bulbState => !bulbState);

useEffect

  1. useEffect用于執行函數組件中的副作用(Side Effects)。副作用一般包含請求數據、事件處理、訂閱、改變DOM等操作。React要求函數組件必須是一個純函數,其中不能包含副作用。因此,React Hooks提供useEffect,以便在函數組件中執行副作用操作
  2. useEffect默認在每次渲染結束后執行
  3. 使用方法
useEffect(() => {
  // Todo
}, [xxx]);

參數:包含命令式、可能有副作用的函數。第二個參數可選,為副作用依賴值的數組

  • 當無第二個參數時:每次組件渲染完成后都會執行
  • 當第二個參數為[]時:空數組表示effect中沒有依賴props或state中的值,因此僅執行一次(僅在組件掛載和卸載時執行)。類似于class組件中的componentDidMountcomponentWillUnmount
  • 當第二個參數為變量數組時:當數組中的變量發生變化時,執行effect。如果數組中包含多個變量,當該次渲染的數組與上一次渲染的數組中的元素都相等時,會跳過本次effect的執行,但即使只有一個元素發生變化,也會執行。類似于componentDidUpdate中對prevPropsprevState的判斷
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 僅在 count 更改時更新

注意:

  1. 每次渲染都執行effect可能會導致性能問題,利用useEffect的第二個參數可以跳過 Effect 的執行,從而進行性能優化
  2. 數組中包含的是所有外部作用域中會隨時間變化并且在effect中使用的變量
  1. 兩種副作用
  • 不需要清除的effect:在渲染完成后執行的一些額外的代碼,執行后即可忽略。如發送網絡請求,手動變更 DOM,記錄日志等
  • 需要清除的effect:如訂閱外部數據源、定時器操作等,effect執行后要進行清除操作,以防止內存泄露
  1. 清除effect:不需要單獨的effect來執行清除操作,只需在effect中返回一個清除函數,React將會在執行清除操作時調用該函數
    清除時機:React會在組件卸載時清理effect,由于effect默認每次渲染時都會執行,所以React會在調用一個新的 effect 之前對前一個 effect 進行清理
useEffect(() => {
  // Todo
  return () => {
    // 清除effect操作
  }
});
  1. useEffect會在瀏覽器繪制后、新的渲染前執行,因此不會阻止瀏覽器的頁面渲染

useContext

  1. Context為組件樹提供數據共享的方法,數據無需層層傳遞,即不需要該數據的中間層組件無需作為“快遞員”傳遞數據,避免props層層傳遞過于繁瑣、組件關系維護困難。具體見文檔
// 創建一個Context上下文
// defaultValue:初始共享值
const MyContext = React.createContext(defaultValue);

// Provider:提供者,提供共享數據
// value即共享數據
// 當value值發生變化時,Provider中所有使用該共享值的組件都會重新渲染
<MyContext.Provider value={xxx}>  

</MyContext.Provider>

// Consumer:消費者,使用共享數據
<MyContext.Consumer>
  {value => /* 基于 context 值進行渲染*/}
</MyContext.Consumer>
  1. useContext即為函數組件提供的獲取共享數據的Hook
  2. 使用方法
const value = useContext(MyContext);

參數:通過React.createContext創建的上下文組件對象
返回值:該上下文的當前值,即距離當前組件最近<MyContext.Provider>的value

示例:

const animal = {
  dog: {
    name: 'Kiki',
    age: 1
  },
  cat: {
    name: 'Sara',
    age: 2
  }
}
// 創建上下文,傳入初始值
const PetContext = React.createContext(animal);

function App() {
  return (
    <PetContext.Provider value={animal.dog}>
      <MyPet />
    </PetContext.Provider>
  );
}

// 使用共享數據的子組件
function MyPet() {
  const pet = useContext(PetContext);
  return (
    <div>Hello, {pet.name}!</div>  // Hello, Kiki!
  );
}
  1. 使用Context的Provider組件包裹函數組件后,該函數組件才能共享上下文狀態
<PetContext.Provider value={animal.dog}>
  <MyPet />
</PetContext.Provider>
  1. 當組件依賴的(上層距離最近的)Provider的value值發生變化時,useContext會觸發重新渲染,獲取最新的value值
  2. 使用useContext相當于在函數組件中訂閱Context上下文的變化
// 在class組件中的訂閱方式:
static contextType = MyContext
// 或
<MyContext.Consumer>

useReducer

  1. useReduceruseState的替代方案,與Redux中的reducer功能類似,都是提供狀態改變功能;相對于useState來說,useReducer更適用于state邏輯復雜且包含多個子值(對象數組嵌套結構)的情景
  2. 使用方法:
const [state, dispatch] = useReducer(reducer, initialArg, init);

參數

  • reducer:一個函數,接收當前狀態state和觸發行為action,返回計算后的新的狀態newState
(state, action) => newState

action:觸發狀態改變的行為,是一個對象,包含:
type:表示行為的描述,如增、刪、改、查用戶等;
payload(可選):表示攜帶的數據,用于newState的計算

const action = {
  type: 'addUser',
  payload: {
    name: 'Bobo',  
    sex: 0    
  }
}

注意:

  • 不要直接修改state!因為它是immutable(React 使用 Object.is 來比較 state
  • 可以通過解構賦值創建newState并返回

舉個栗子:

function myReducer(state, action) {
  const {type, payload} = action
  if (type === 'addUser') {
     return [
      ...state,
      payload
     ]
  }
  if (type === 'removeUser') {
     // 移除該用戶,并把新的用戶數組返回
     // ...
  }
  // type的其他判斷...
  return state
}
  • initialArg:初始狀態值
  • init:一個方法,用于惰性初始化state,當傳該方法時,初始值就變成了 init(initialArg),可以用來重置初始狀態

返回值:包含兩個元素的數組,可解構

  • state:當前狀態值
  • dispatch:方法,用于觸發事件以更新state,參數為一個action對象,即將action中的payload作為參數,執行type操作,更改狀態
// ...
const action = {
  type: 'addUser',
  payload: {
    name: 'Bobo',  
    sex: 0    
  }
}

// ...
<button onClick={() => dispatch(action)}>點擊增加用戶</button>
  1. 跳過dispatch:若返回的state與當前的state相同,則不會進行子組件的重新渲染和執行副作用
  2. useReducer可以結合useContext執行復雜的狀態更新操作。當組件嵌套比較深時,可以將dispatch作為共享數據傳遞給子組件,這樣在子組件中就可以使用dispatch觸發事件,更改狀態。參考此處

useCallback

  1. useCallback用于緩存回調函數,減少不必要的渲染
  2. 在組件第一次渲染的時候執行,之后會在其依賴的變量發生改變時再次執行,返回新的函數
  3. 使用方法
useCallback(fn, deps)

參數

  • fn:函數
  • deps:依賴項

返回值:緩存的函數

useMemo

  1. useCallback類似,useMemo用于針對返回值進行緩存優化
  2. 在組件第一次渲染的時候執行,之后會在其依賴的變量發生改變時再次執行,返回新的值
  3. 使用方法
useMemo(() => fn, deps)

參數

  • fn:函數
  • deps:依賴項

返回值:緩存的值

useRef

  1. 先來了解一下Refs和DOM,在HTML元素或class組件上可以通過 React.createRef()創建ref引用該元素
  2. 在函數組件中引入了useRef,用于保存任何可變的值,每次都會返回相同的ref引用
  3. 使用方法
const refContainer = useRef(initialValue);

參數

  • initialValue:初始值

返回值:可變的 ref 對象,其.current屬性被初始化為傳入的參數initialValue

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378