深入學(xué)習(xí) React Hooks

React Hooks

Hook 是 16.8新增特性刑顺。

hooks 優(yōu)勢(shì)

  • 能優(yōu)化類組件的三大問題
  • 能在無需修改組件結(jié)構(gòu)的情況下復(fù)用狀態(tài)(自定義Hooks)
  • 能將組件中想關(guān)聯(lián)的部分成更小的函數(shù),(訂閱數(shù)據(jù)或請(qǐng)求數(shù)據(jù))
  • 副作用的關(guān)注分離: 副作用致那些沒有發(fā)生在數(shù)據(jù)向視圖轉(zhuǎn)換過程中的邏輯,如ajax請(qǐng)求冠绢、訪問原生dom元素、本地?cái)?shù)據(jù)持久化緩存、綁定、解綁時(shí)間抖坪、添加訂閱、設(shè)置定時(shí)器闷叉、記錄日志等擦俐。以往這些副作用在類組件聲明周期函數(shù)值。useEffect在全部渲染完成之后才會(huì)執(zhí)行握侧。useLayoutEffect會(huì)在瀏覽器layout之后捌肴,painting之前執(zhí)行。

useState

useState 通過在函數(shù)組件里調(diào)用它來給組件添加一些內(nèi)部 state藕咏。React 會(huì)在重復(fù)渲染時(shí)保留這個(gè) state。useState 會(huì)返回一對(duì)值:當(dāng)前狀態(tài)和一個(gè)讓你更新它的函數(shù)秽五,你可以在事件處理函數(shù)中或其他一些地方調(diào)用這個(gè)函數(shù)

useState唯一的參數(shù)是初始state孽查。

function useState<s>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<s>>];
const [count, setCount] = useState(0)
  • 惰性初始 state
    initialState 參數(shù)只會(huì)在組件的初始渲染中起作用,后續(xù)渲染時(shí)會(huì)被忽略坦喘。如果初始 state 需要通過復(fù)雜計(jì)算獲得盲再,則可以傳入一個(gè)函數(shù)西设,在函數(shù)中計(jì)算并返回初始的 state,此函數(shù)只在初始渲染時(shí)被調(diào)用:
const [count, setCount] = useState(()=>{
    // 這里只在初始化的時(shí)候會(huì)被執(zhí)行
    return 0
})
  • 函數(shù)式更新
    如果新的 state 需要通過使用先前的 state 計(jì)算得出答朋,那么可以將函數(shù)傳遞給 setState贷揽。該函數(shù)將接收先前的 state,并返回一個(gè)更新后的值梦碗。下面的計(jì)數(shù)器組件示例展示了 setState 的兩種用法

useEffect

Effect Hook 可以讓你能夠在 Function 組件中執(zhí)行副作用(side effects)禽绪。 Function Component沒有Class Component生命周期的概念,只有一個(gè)狀態(tài)洪规。

import { useState, useEffect } from "react";

// 底層 Hooks, 返回布爾值:是否在線
function useFriendStatusBoolean(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

// 上層 Hooks印屁,根據(jù)在線狀態(tài)返回字符串:Loading... or Online or Offline
function useFriendStatusString(props) {
  const isOnline = useFriendStatusBoolean(props.friend.id);

  if (isOnline === null) {
    return "Loading...";
  }
  return isOnline ? "Online" : "Offline";
}

// 使用了底層 Hooks 的 UI
function FriendListItem(props) {
  const isOnline = useFriendStatusBoolean(props.friend.id);

  return (
    <li>{props.friend.name}</li>
  );
}

// 使用了上層 Hooks 的 UI
function FriendListStatus(props) {
  const status = useFriendStatusString(props);

  return <li>{status}</li>;
}

這個(gè)例子中,有兩個(gè) Hooks:useFriendStatusBoolean 與 useFriendStatusString, useFriendStatusString 是利用 useFriendStatusBoolean 生成的新 Hook斩例,這兩個(gè) Hook 可以給不同的 UI:FriendListItem雄人、FriendListStatus 使用,而因?yàn)閮蓚€(gè) Hooks 數(shù)據(jù)是聯(lián)動(dòng)的念赶,因此兩個(gè) UI 的狀態(tài)也是聯(lián)動(dòng)的础钠。

利用 useEffect 代替一些生命周期

useEffect 會(huì)在每次渲染后都執(zhí)行嗎? 是的叉谜,默認(rèn)情況下旗吁,它在第一次渲染之后和每次更新之后都會(huì)執(zhí)行。

  • 用useEffect模擬componentDidMount生命周期
import { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  },[]); // [] 空的依賴 只會(huì)執(zhí)行一次
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
  • 用useEffect模擬componentDidUpdate
import React, { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  }); // 每次渲染都會(huì)更新 

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
  • 用useEffect模擬ComponentWillUnmount
useEffect(
  () => {
    const subscription = props.id.subscribe();
    return () => {
      subscription.unsubscribe();
    }; //  這是 effect 可選的清除機(jī)制正罢。每個(gè) effect 都可以返回一個(gè)清除函數(shù)阵漏。如此可以將添加和移除訂閱的邏輯放在一起。它們都屬于 effect 的一部分翻具。
  },
  [props.id],
);

程序一開始執(zhí)行履怯,就執(zhí)行了useEffect中的函數(shù),之后的每次render都會(huì)執(zhí)行useEffect裆泳,但是因?yàn)橛?code>props.id的存在叹洲,所以只有在props.id改變的時(shí)候,useEffect中的函數(shù)才會(huì)執(zhí)行。

React 何時(shí)清除 effect工禾? React 會(huì)在組件卸載的時(shí)候執(zhí)行清除操作运提。正如之前學(xué)到的,effect 在每次渲染的時(shí)候都會(huì)執(zhí)行闻葵。這就是為什么 React 會(huì)在執(zhí)行當(dāng)前 effect 之前對(duì)上一個(gè) effect 進(jìn)行清除民泵。

每一次渲染都有它自己的 Props and State

先討論一下渲染(rendering)

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

我們的組件第一次渲染的時(shí)候,從useState()拿到count的初始值0槽畔。當(dāng)我們調(diào)用setCount(1)栈妆,React會(huì)再次渲染組件,這一次count是1

// During first render
function Counter() {
  const count = 0; // Returned by useState()
  // ...
  <p>You clicked {count} times</p>
  // ...
}

// After a click, our function is called again

function Counter() {
  const count = 1; // Returned by useState()
  // ...
  <p>You clicked {count} times</p>
  // ...
}

// After another click, our function is called again

function Counter() {
  const count = 2; // Returned by useState()
  // ...
  <p>You clicked {count} times</p>
  // ...
}

當(dāng)我們更新狀態(tài)的時(shí)候,React會(huì)重新渲染組件鳞尔。每一次渲染都能拿到獨(dú)立的count 狀態(tài)嬉橙,這個(gè)狀態(tài)值是函數(shù)中的一個(gè)常量。

當(dāng)setCount的時(shí)候寥假,React會(huì)帶著一個(gè)不同的count值再次調(diào)用組件市框。然后,React會(huì)更新DOM以保持和渲染輸出一致糕韧。

這里關(guān)鍵的點(diǎn)在于任意一次渲染中的count常量都不會(huì)隨著時(shí)間改變枫振。渲染輸出會(huì)變是因?yàn)槲覀兊慕M件被一次次調(diào)用,而每一次調(diào)用引起的渲染中兔沃,它包含的count值獨(dú)立于其他渲染蒋得。

每一次渲染都有它自己的事件處理函數(shù)

在任意一次渲染中,props和state是始終保持不變的乒疏。如果props和state在不同的渲染中是相互獨(dú)立的额衙,那么使用到它們的任何值也是獨(dú)立的(包括事件處理函數(shù))。它們都“屬于”一次特定的渲染怕吴。即便是事件處理中的異步函數(shù)調(diào)用“看到”的也是這次渲染中的count值窍侧。

每次渲染都有它自己的Effects


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

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

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

effect是如何讀取到最新的count 狀態(tài)值的呢等恐?

我們已經(jīng)知道count是某個(gè)特定渲染中的常量邻悬。事件處理函數(shù)“看到”的是屬于它那次特定渲染中的count狀態(tài)值。對(duì)于effects也同樣如此:

并不是count的值在“不變”的effect中發(fā)生了改變艰猬,而是effect 函數(shù)本身在每一次渲染中都不相同议经。

每一個(gè)effect版本“看到”的count值都來自于它屬于的那次渲染:

// During first render
function Counter() {
  // ...
  useEffect(
    // Effect function from first render
    () => {
      document.title = `You clicked ${0} times`;
    }
  );
  // ...
}

// After a click, our function is called again
function Counter() {
  // ...
  useEffect(
    // Effect function from second render
    () => {
      document.title = `You clicked ${1} times`;
    }
  );
  // ...
}

// After another click, our function is called again
function Counter() {
  // ...
  useEffect(
    // Effect function from third render
    () => {
      document.title = `You clicked ${2} times`;
    }
  );
  // ..
}

React會(huì)記住你提供的effect函數(shù)斧账,并且會(huì)在每次更改作用于DOM并讓瀏覽器繪制屏幕后去調(diào)用它。

所以雖然我們說的是一個(gè) effect(這里指更新document的title)煞肾,但其實(shí)每次渲染都是一個(gè)不同的函數(shù) — 并且每個(gè)effect函數(shù)“看到”的props和state都來自于它屬于的那次特定渲染咧织。

那Effect中的清理又是怎樣的呢?

  • React 先渲染新數(shù)據(jù)的UI界面
  • 瀏覽器繪制籍救∠熬睿看到新的數(shù)據(jù)的UI。
  • React 清除舊數(shù)據(jù)的Effect蝙昙。
  • React 運(yùn)行新數(shù)據(jù)的Effect闪萄。

告訴 React 如何對(duì)比 Effects

React不能區(qū)分effects的不同。 在沒有調(diào)用之前是不能猜測(cè)到函數(shù)做了什么奇颠。這是為什么想要避免effects不必要的重復(fù)調(diào)用败去,所以要提供給uesEffect一個(gè)依賴數(shù)組參數(shù)。

 useEffect(() => {
    document.title = 'Hello, ' + name;
  }, [name]);

如果當(dāng)前渲染中的這些依賴項(xiàng)和上一次運(yùn)行這個(gè)effect的時(shí)候紙一樣烈拒,就會(huì)自動(dòng)跳過這次effect運(yùn)行为迈。
即使依賴數(shù)組中只有一個(gè)值在兩次渲染中不一樣三椿,我們也不能跳過effect的運(yùn)行。要同步所有葫辐!

對(duì)依賴必須誠(chéng)實(shí)

function SearchResults() {
  async function fetchData() {
    // ...
  }

  useEffect(() => {
    fetchData();
  }, []); // Is this okay? Not always -- and there's a better way to write it.

  // ...
}

如果設(shè)置了依賴項(xiàng),effect中用到的所有組件內(nèi)的值都要包含在依賴中伴郁。包括props耿战、state,函數(shù) 等組件內(nèi)任何東西。

useCallback

用來緩存函數(shù)

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

返回一個(gè) memoized 函數(shù)焊傅。 當(dāng)依賴改變的時(shí)候剂陡,會(huì)自動(dòng)執(zhí)行

把內(nèi)聯(lián)回調(diào)函數(shù)及依賴項(xiàng)數(shù)組作為參數(shù)傳入 useCallback,它將返回該回調(diào)函數(shù)的 memoized 版本狐胎,該回調(diào)函數(shù)僅在某個(gè)依賴項(xiàng)改變時(shí)才會(huì)更新鸭栖。當(dāng)你把回調(diào)函數(shù)傳遞給經(jīng)過優(yōu)化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時(shí),它將非常有用握巢。

useCallback(fn, deps) 相當(dāng)于 useMemo(() => fn, deps)晕鹊。

useMemo

useMemo 做計(jì)算結(jié)果緩存

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

useMemo 的第一個(gè)參數(shù)就是一個(gè)函數(shù),這個(gè)函數(shù)返回的值會(huì)被緩存起來暴浦,同時(shí)這個(gè)值會(huì)作為 useMemo 的返回值溅话,第二個(gè)參數(shù)是一個(gè)數(shù)組依賴,如果數(shù)組里面的值有變化歌焦,那么就會(huì)重新去執(zhí)行第一個(gè)參數(shù)里面的函數(shù)飞几,并將函數(shù)返回的值緩存起來并作為 useMemo 的返回值 。

把“創(chuàng)建”函數(shù)和依賴項(xiàng)數(shù)組作為參數(shù)傳入useMemo, 僅會(huì)在依賴改變菜回重新計(jì)算独撇。這種優(yōu)化有助于避免在每次渲染時(shí)都進(jìn)行高開銷的計(jì)算屑墨。

useMemo 的使用場(chǎng)景主要是用來緩存計(jì)算量比較大的函數(shù)結(jié)果,可以避免不必要的重復(fù)計(jì)算纷铣。如果沒有提供依賴選項(xiàng),useMemo在每次渲染都計(jì)算新的值卵史。

useMemo 也允許你跳過一次子節(jié)點(diǎn)的昂貴的重新渲染:

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => <Child2 b= />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
}

注意這種方式在循環(huán)中是無效的关炼,因?yàn)?Hook 調(diào)用 不能 被放在循環(huán)中程腹。但你可以為列表項(xiàng)抽取一個(gè)單獨(dú)的組件,并在其中調(diào)用 useMemo儒拂。

useRef

useRef 返回一個(gè)可變的 ref 對(duì)象寸潦,其 .current 屬性被初始化為傳入的參數(shù)(initialValue)。返回的 ref 對(duì)象在組件的整個(gè)生命周期內(nèi)保持不變社痛。

useImperativeHandle

useImperativeHandle 可以讓你在使用 ref 時(shí)自定義暴露給父組件的實(shí)例值见转。在大多數(shù)情況下,應(yīng)當(dāng)避免使用 ref 這樣的命令式代碼蒜哀。useImperativeHandle 應(yīng)當(dāng)與 forwardRef 一起使用:

useEffect參考鏈接

歡迎訪問主頁(yè)斩箫,有更多文章內(nèi)容
轉(zhuǎn)載請(qǐng)注明原出處
原文鏈接地址:深入學(xué)習(xí) React Hooks

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子乘客,更是在濱河造成了極大的恐慌狐血,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件易核,死亡現(xiàn)場(chǎng)離奇詭異匈织,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)牡直,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門缀匕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人碰逸,你說我怎么就攤上這事乡小。” “怎么了饵史?”我有些...
    開封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵满钟,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我约急,道長(zhǎng)零远,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任厌蔽,我火速辦了婚禮牵辣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘奴饮。我一直安慰自己纬向,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開白布戴卜。 她就那樣靜靜地躺著逾条,像睡著了一般。 火紅的嫁衣襯著肌膚如雪投剥。 梳的紋絲不亂的頭發(fā)上师脂,一...
    開封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音江锨,去河邊找鬼吃警。 笑死,一個(gè)胖子當(dāng)著我的面吹牛啄育,可吹牛的內(nèi)容都是我干的酌心。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼挑豌,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼安券!你這毒婦竟也來了墩崩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤侯勉,失蹤者是張志新(化名)和其女友劉穎鹦筹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體址貌,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盛龄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了芳誓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡啊鸭,死狀恐怖锹淌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赠制,我是刑警寧澤赂摆,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站钟些,受9級(jí)特大地震影響烟号,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜政恍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一汪拥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧篙耗,春花似錦迫筑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蒙保,卻和暖如春辕棚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邓厕。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工逝嚎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人邑狸。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓懈糯,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親单雾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子赚哗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354