react hook - useCallback、useMemo肾请、useRef留搔、useContext

問題

事件處理函數(shù)會(huì)被重復(fù)定義
數(shù)據(jù)計(jì)算過程沒有緩存

useCallback - 緩存回調(diào)函數(shù)

function Counter() {
  const [count, setCount] = useState(0);
  const handleIncrement = () => setCount(count + 1);
  // ...
  return <button onClick={handleIncrement}>+</button>
}

每次組件狀態(tài)發(fā)生變化的時(shí)候,函數(shù)組件實(shí)際上都會(huì)重新執(zhí)行一遍铛铁。在每次執(zhí)行的時(shí)候隔显,實(shí)際上都會(huì)創(chuàng)建一個(gè)新的事件處理函數(shù) handleIncrement。這個(gè)事件處理函數(shù)中呢避归,包含了 count 這個(gè)變量的閉包荣月,以確保每次能夠得到正確的結(jié)果。
即使 count 沒有發(fā)生變化梳毙,但是函數(shù)組件因?yàn)槠渌鼱顟B(tài)發(fā)生變化而重新渲染時(shí)哺窄,這種寫法也會(huì)每次創(chuàng)建一個(gè)新的函數(shù)。創(chuàng)建一個(gè)新的事件處理函數(shù),雖然不影響結(jié)果的正確性萌业,但其實(shí)是沒必要的坷襟。因?yàn)檫@樣做不僅增加了系統(tǒng)的開銷,更重要的是:每次創(chuàng)建新函數(shù)的方式會(huì)讓接收事件處理函數(shù)的組件生年,需要重新渲染婴程。
比如這個(gè)例子中的 button 組件,接收了 handleIncrement 抱婉,并作為一個(gè)屬性档叔。如果每次都是一個(gè)新的,那么這個(gè) React 就會(huì)認(rèn)為這個(gè)組件的 props 發(fā)生了變化蒸绩,從而必須重新渲染衙四。因此,我們需要做到的是:只有當(dāng) count 發(fā)生變化時(shí)患亿,我們才需要重新定一個(gè)回調(diào)函數(shù)传蹈。而這正是 useCallback 這個(gè) Hook 的作用。

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

function Counter() {
  const [count, setCount] = useState(0);
  const handleIncrement = useCallback(
    () => setCount(count + 1),
    [count], // 只有當(dāng) count 發(fā)生變化時(shí)步藕,才會(huì)重新創(chuàng)建回調(diào)函數(shù)
  );
  // ...
  return <button onClick={handleIncrement}>+</button>
}

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

如果某個(gè)數(shù)據(jù)是通過其它數(shù)據(jù)計(jì)算得到的惦界,那么只有當(dāng)用到的數(shù)據(jù),也就是依賴的數(shù)據(jù)發(fā)生變化的時(shí)候咙冗,才應(yīng)該需要重新計(jì)算沾歪。

  • 避免重復(fù)計(jì)算。
  • 避免子組件的重復(fù)渲染乞娄。

useCallback 的功能可以用 useMemo 來實(shí)現(xiàn)

建立了一個(gè)綁定某個(gè)結(jié)果到依賴數(shù)據(jù)的關(guān)系瞬逊。只有當(dāng)依賴變了,這個(gè)結(jié)果才需要被重新得到仪或。

// 只有當(dāng) count 發(fā)生變化時(shí),才會(huì)重新創(chuàng)建回調(diào)函數(shù) );
const handleIncrement = useCallback( () => setCount(count + 1), [count], 
 const myEventHandler = useMemo(() => {
   // 返回一個(gè)函數(shù)作為緩存結(jié)果
   return () => {
     // 在這里進(jìn)行事件處理
      setCount(count + 1)
   }
 }, [count]);

useRef - 在多次渲染之間共享數(shù)據(jù)

我們可以把 useRef 看作是在函數(shù)組件之外創(chuàng)建的一個(gè)容器空間士骤。在這個(gè)容器上范删,我們可以通過唯一的 current 屬設(shè)置一個(gè)值,從而在函數(shù)組件的多次渲染之間共享這個(gè)值拷肌。


import React, { useState, useCallback, useRef } from "react";

export default function Timer() {
  // 定義 time state 用于保存計(jì)時(shí)的累積時(shí)間
  const [time, setTime] = useState(0);

  // 定義 timer 這樣一個(gè)容器用于在跨組件渲染之間保存一個(gè)變量
  const timer = useRef(null);

  // 開始計(jì)時(shí)的事件處理函數(shù)
  const handleStart = useCallback(() => {
    // 使用 current 屬性設(shè)置 ref 的值
    timer.current = window.setInterval(() => {
      setTime((time) => time + 1);
    }, 100);
  }, []);

  // 暫停計(jì)時(shí)的事件處理函數(shù)
  const handlePause = useCallback(() => {
    // 使用 clearInterval 來停止計(jì)時(shí)
    window.clearInterval(timer.current);
    timer.current = null;
  }, []);

  return (
    <div>
      {time / 10} seconds.
      <br />
      <button onClick={handleStart}>Start</button>
      <button onClick={handlePause}>Pause</button>
    </div>
  );
}

使用 useRef 保存的數(shù)據(jù)一般是和 UI 的渲染無關(guān)的到旦,因此當(dāng) ref 的值發(fā)生變化時(shí),是不會(huì)觸發(fā)組件的重新渲染的

除了存儲(chǔ)跨渲染的數(shù)據(jù)之外巨缘,useRef 還有一個(gè)重要的功能添忘,就是保存某個(gè) DOM 節(jié)點(diǎn)的引用。


function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // current 屬性指向了真實(shí)的 input 這個(gè) DOM 節(jié)點(diǎn)若锁,從而可以調(diào)用 focus 方法
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

useContext - 定義全局狀態(tài)

解決跨層次搁骑,或者同層的組件之間進(jìn)行數(shù)據(jù)共享的問題

React 提供了 Context 這樣一個(gè)機(jī)制,能夠讓所有在某個(gè)組件開始的組件樹上創(chuàng)建一個(gè) Context。這樣這個(gè)組件樹上的所有組件仲器,就都能訪問和修改這個(gè) Context 了煤率。那么在函數(shù)組件里,我們就可以使用 useContext 這樣一個(gè) Hook 來管理 Context乏冀。

定義Context

// 創(chuàng)建ThemeContext
const ThemeContext = React.createContext(themes.light);

使用Context.Provider作為根節(jié)點(diǎn)

function App() {
  // 整個(gè)應(yīng)用使用 ThemeContext.Provider 作為根組件
  return (
    // 使用 themes.dark 作為當(dāng)前 Context, themes中保存了一些主題
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}    

子組件中用useContext讀取Context.Provider提供的值

// 在 Theme Button 中使用 useContext 來獲取當(dāng)前的主題
function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{
      background: theme.background,
      color: theme.foreground
    }}>
      I am styled by theme context!
    </button>
  );
}

Context

Context 相當(dāng)于提供了一個(gè)定義 React 世界中全局變量的機(jī)制蝶糯,而全局變量則意味著兩點(diǎn):

  • 會(huì)讓調(diào)試變得困難,因?yàn)槟愫茈y跟蹤某個(gè) Context 的變化究竟是如何產(chǎn)生的辆沦。
  • 讓組件的復(fù)用變得困難昼捍,因?yàn)橐粋€(gè)組件如果使用了某個(gè) Context,它就必須確保被用到的地方一定 有這個(gè) ContextProvider 在其父組件的路徑上肢扯。

所以在 React 的開發(fā)中端三,除了像 ThemeLanguage 等一目了然的需要全局設(shè)置的變量外鹃彻,我們很少會(huì)使用 Context 來做太多數(shù)據(jù)的共享郊闯。
需要再三強(qiáng)調(diào)的是,Context 更多的是提供了一個(gè)強(qiáng)大的機(jī)制蛛株,讓 React 應(yīng)用具備定義全局的響應(yīng)式數(shù)據(jù)的能力团赁。

很多狀態(tài)管理框架,比如 Redux谨履,正是利用了 Context 的機(jī)制來提供一種更加可控的組件之間的狀 態(tài)管理機(jī)制欢摄。因此,理解 Context 的機(jī)制笋粟,也可以讓我們更好地去理解 Redux 這樣的框架實(shí)現(xiàn)的原理怀挠。

思考題

useState 其實(shí)也是能夠在組件的多次渲染之間共享數(shù)據(jù)的,那么在 useRef 的計(jì)時(shí)器例子中害捕,我們能否用 state 去保存 window.setInterval() 返回的 timer 呢绿淋?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市尝盼,隨后出現(xiàn)的幾起案子吞滞,更是在濱河造成了極大的恐慌,老刑警劉巖盾沫,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裁赠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡赴精,警方通過查閱死者的電腦和手機(jī)佩捞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蕾哟,“玉大人一忱,你說我怎么就攤上這事莲蜘。” “怎么了掀潮?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵菇夸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我仪吧,道長(zhǎng)庄新,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任薯鼠,我火速辦了婚禮择诈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘出皇。我一直安慰自己羞芍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布郊艘。 她就那樣靜靜地躺著荷科,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纱注。 梳的紋絲不亂的頭發(fā)上畏浆,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音狞贱,去河邊找鬼刻获。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瞎嬉,可吹牛的內(nèi)容都是我干的蝎毡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼氧枣,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼沐兵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起挑胸,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤痒筒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后茬贵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡移袍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年解藻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片葡盗。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡螟左,死狀恐怖啡浊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情胶背,我是刑警寧澤巷嚣,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站钳吟,受9級(jí)特大地震影響廷粒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜红且,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一坝茎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧暇番,春花似錦嗤放、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舆乔,卻和暖如春岳服,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜕煌。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工派阱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人斜纪。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓贫母,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親盒刚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子腺劣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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