你應該知道的Hooks知識

Hooks

HooksReact16.8 的新增特性绷耍,能夠在不寫 class 的情況下使用 state 以及其他特性型豁。

動機

  • 在組件之間復用狀態(tài)邏輯很難
  • 復雜組件變得難以理解
  • 難以理解的 class

Hooks 規(guī)則

  • 只有在最頂層使用 Hooks不要再循環(huán)/條件/嵌套函數(shù)中使用`
  • 只有在 React 函數(shù)中調用 Hooks

函數(shù)組件和類組件的不同

函數(shù)組件能夠捕獲到當前渲染的所用的值材泄。

點擊查看示例

對于類組件來說卷拘,雖然 props是一個不可變的數(shù)據(jù)看蚜,但是 this是一個可變的數(shù)據(jù)结窘,在我們渲染組件的時候 this 發(fā)生了改變,所以 this.props 發(fā)生了改變底哗,因此在 this.showMessage 中會拿到最新的 props 值岁诉。

對于函數(shù)組件來說捕獲了渲染所使用的值,當我們使用 hooks 時跋选,這種特性也同樣的試用于 state 上涕癣。

點擊查看示例

const showMessage = () => {
    alert("寫入:" + message);
};

const handleSendClick = () => {
    setTimeout(showMessage, 3000);
};

const handleMessageChange = (e) => {
    setMessage(e.target.value);
};

如果我們想跳出'函數(shù)組件捕獲當前渲染的所用值‘這個特性,我們可以采用 ref 來追蹤某些數(shù)據(jù)前标。通ref.current可以獲取到最新的值

const showMessage = () => {
    alert("寫入:" + ref.current);
};

const handleSendClick = () => {
    setTimeout(showMessage, 3000);
};

const handleMessageChange = (e) => {
    setMessage(e.target.value);
    ref.current = e.target.value;
};

useEffect

useEffect 能夠在函數(shù)組件中執(zhí)行副作用操作(數(shù)據(jù)獲取/涉及訂閱)坠韩,其實可以把 useEffect 看作是 componentDidMount / componentDidUpdate / componentWillUnMount 的組合

  • 第一個參數(shù)是一個 callback,返回 destory炼列。destory 作為下一個 callback 執(zhí)行前調用只搁,用于清除上一次 callback 產(chǎn)生的副作用
  • 第二個參數(shù)是依賴項,一個數(shù)組俭尖,可以有多個依賴項氢惋。依賴項改變,執(zhí)行上一個 callback 返回的 destory稽犁,和執(zhí)行新的 effect 第一個參數(shù) callback

對于 useEffect 的執(zhí)行焰望,React 處理邏輯是采用異步調用的,對于每一個 effectcallback 會像 setTimeout 回調函數(shù)一樣已亥,放到任務隊列里面熊赖,等到主線程執(zhí)行完畢才會執(zhí)行。所以 effect 的回調函數(shù)不會阻塞瀏覽器繪制視圖

  1. 相關的生命周期替換方案
  • componentDidMount 替代方案
React.useEffect(()=>{
    //請求數(shù)據(jù)虑椎,事件監(jiān)聽震鹉,操縱DOM
},[]) //dep=[],只有在初始化執(zhí)行
/* 
  因為useEffect會捕獲props和state捆姜,
  所以即使是在回調函數(shù)中我們拿到的還是最初的props和state
*/
  • componentDidUnmount 替代方案
React.useEffect(()=>{
    /* 請求數(shù)據(jù) 传趾, 事件監(jiān)聽 , 操縱dom 娇未, 增加定時器墨缘,延時器 */
    return function componentWillUnmount(){
        /* 解除事件監(jiān)聽器 ,清除定時器,延時器 */
    }
},[])/* 切記 dep = [] */

//useEffect第一個函數(shù)的返回值可以作為componentWillUnmount使用
  • componentWillReceiveProps 替代方案
    其實兩者的執(zhí)行時機是完全不同的镊讼,一個在 render 階段宽涌,一個在 commit 階段,useEffect 會初始化執(zhí)行一次蝶棋,但是 componentWillReceiveProps 只會在 props 變化時執(zhí)行更新
React.useEffect(()=>{
    console.log('props變化:componentWillReceiveProps')
},[ props ])
  • componentDidUpdate 替代方案
    useEffectcomponentDidUpdate 在執(zhí)行時期雖然有點差別卸亮,useEffect 是異步執(zhí)行,componentDidUpdate 是同步執(zhí)行 玩裙,但都是在 commit 階段
React.useEffect(()=>{
    console.log('組件更新完成:componentDidUpdate ')     
}) //沒有dep依賴項兼贸,沒有第二個參數(shù),那么每一次執(zhí)行函數(shù)組件吃溅,都會執(zhí)行該 effect溶诞。
  1. useEffect 中[]需要處理什么

React 官網(wǎng) FAQ這樣說:

只有當函數(shù)(以及它所調用的函數(shù))不引用 propsstate 以及由它們衍生而來的值時决侈,你才能放心地把它們從依賴列表中省略螺垢,使用 eslint-plugin-react-hooks 幫助我們的代碼做一個校驗

點擊查看詳細示例

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

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return <h1>{count}</h1>;
}
//只會做一次更新,然后定時器不再轉動
  1. 是否應該把函數(shù)當做 effect 的依賴
const loadResourceCatalog = async () => {
    if (!templateType) return
    const reqApi = templateType === TEMPLATE_TYPE.STANDARD ? 'listCatalog' : 'getCodeManageCatalog'
    const res: any = await API[reqApi]()
    if (!res.success) return
    setCatalog(res.data)
}

useEffect(() => {
    loadResourceCatalog();
}, [])
//在函數(shù)loadResourceCatalog中使用了templateType這樣的一個state
//在開發(fā)的過程中可能會忘記函數(shù)loadResourceCatalog依賴templateType值

第一個簡單的解法赖歌,對于某些只在 useEffect 中使用的函數(shù)枉圃,直接定義在 effect 中,以至于能夠直接依賴某些 state

useEffect(() => {
    const loadResourceCatalog = async () => {
        if (!templateType) return
        const reqApi = templateType === TEMPLATE_TYPE.STANDARD ? 'listCatalog' : 'getCodeManageCatalog'
        const res: any = await API[reqApi]()
        if (!res.success) return
        setCatalog(res.data)
    }
    loadResourceCatalog();
}, [templateType])

假如我們需要在很多地方用到我們定義的函數(shù)庐冯,不能夠把定義放到當前的 effect 中孽亲,并且將函數(shù)放到了第二個的依賴參數(shù)中,那這個代碼將就進入死循環(huán)展父。因為函數(shù)在每一次渲染中都返回一個新的引用

const Template = () => {
    const getStandardTemplateList = async () => {
        const res: any = await API.getStandardTemplateList()
      if (!res.success) return;
        const { data } = res;
        setCascaderOptions(data);
        getDefaultOption(data[0])
    }
    useEffect(()=>{
        getStandardTemplateList()
    }, [])
}

針對這種情況返劲,如果當前函數(shù)沒有引用任何組件內(nèi)的任何值,可以將該函數(shù)提取到組件外面去定義犯祠,這樣就不會組件每次 render 時不會再次改變函數(shù)引用旭等。

const getStandardTemplateList = async () => {
    const res: any = await API.getStandardTemplateList()
  if (!res.success) return;
    const { data } = res;
    setCascaderOptions(data);
    getDefaultOption(data[0])
}

const Template = () => {
    useEffect(()=>{
        getStandardTemplateList()
    }, [])
}

如果說當前函數(shù)中引用了組件內(nèi)的一些狀態(tài)值,可以采用 useCallBack 對當前函數(shù)進行包裹

const loadResourceCatalog = useCallback(async () => {
    if (!templateType) return
    const reqApi = templateType === TEMPLATE_TYPE.STANDARD ? 'listCatalog' : 'getCodeManageCatalog'
    const res: any = await API[reqApi]()
    if (!res.success) return
    setCatalog(res.data)
}, [templateType])

useEffect(() => {
    loadResourceCatalog();
}, [loadResourceCatalog])
//通過useCallback的包裹衡载,如果templateType保持不變,那么loadResourceCatalog也會保持不變隙袁,所以useEffect也不會重新運行
//如果templateType改變痰娱,那么loadResourceCatalog也會改變,所以useEffect也會重新運行

useCallback

React 官網(wǎng)定義

useCallback(fn, deps)

返回一個 memoized 回調函數(shù)菩收,該回調函數(shù)僅在某個依賴項改變時才會更新

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

const CallBackTest = () => {
  const [count, setCount] = useState(0);
  const [total, setTotal] = useState(0);
  const handleCount = () => setCount(count + 1);
  //const handleCount = useCallback(() => setCount(count + 1), [count]);
  const handleTotal = () => setTotal(total + 1);

  return (
    <div>
      <div>Count is {count}</div>
      <div>Total is {total}</div>
      

      <div>
        <Child onClick={handleCount} label="Increment Count" />
        <Child onClick={handleTotal} label="Increment Total" />
      </div>
    </div>
  );
};

const Child = React.memo(({ onClick, label }) => {
  console.log(`${label} Child Render`);
  return <button onClick={onClick}>{label}</button>;
});

export default CallBackTest;

點擊查看詳細示例

React.memo 是通過記憶組件渲染結果的方式來提高性能梨睁,memoreact16.6 引入的新屬性,通過淺比較(源碼通過 Object.is 方法比較)當前依賴的 props 和下一個 props 是否相同來決定是否重新渲染娜饵;如果使用過類組件方式坡贺,就能知道 memo 其實就相當于 class 組件中的 React.PureComponent,區(qū)別就在于 memo 用于函數(shù)組件。useCallbackReact.memo 一定要結合使用才能有效果遍坟。

使用場景

  • 作為 props拳亿,傳遞給子組件,為避免子元素不必要的渲染愿伴,需要配合 React.Memo 使用肺魁,否則無意義
  • 作為 useEffect 的依賴項,需要進行比較的時候才需要加上 useCallback

useMemo

React 官網(wǎng)定義

返回一個 memoized

僅會在某個依賴項改變時才重新計算 memoized 值隔节,這種優(yōu)化有助于避免在每次渲染時都進行高開銷的計算 useCallback(fn, deps) 相當于useMemo(() => fn, deps)鹅经,對于實現(xiàn)上,基本上是和 useCallback 相似怎诫,只是略微有些不同

使用場景

  • 避免在每次渲染時都進行高開銷的計算

兩個 hooks 內(nèi)置于 React 都有特別的原因:

1.引用相等

當在 React 函數(shù)組件中定義一個對象時瘾晃,它跟上次定義的相同對象,引用是不一樣的(即使它具有所有相同值和相同屬性)

  • 依賴列表
  • React.memo

大多數(shù)時候幻妓,你不需要考慮去優(yōu)化不必要的重新渲染蹦误,因為優(yōu)化總會帶來成本。

  1. 昂貴的計算
    計算成本很高的同步計算值的函數(shù)

總結

本文介紹了 hooks 產(chǎn)生動機涌哲、函數(shù)組件和類組件的區(qū)別以及 useEffect / useCallback / useMemo 等內(nèi)容胖缤。重點介紹了 useEffect 的生命周期替換方案以及是否把函數(shù)作為 useEffect 的第二參數(shù)。

參考鏈接

When to useMemo and useCallback

How to fetch data with React Hooks

A Complete Guide to useEffect

How Are Function Components Different from Classes?

useCallback阀圾、useMemo 分析 & 差別

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哪廓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子初烘,更是在濱河造成了極大的恐慌涡真,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肾筐,死亡現(xiàn)場離奇詭異哆料,居然都是意外死亡,警方通過查閱死者的電腦和手機吗铐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門晌涕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人兵睛,你說我怎么就攤上這事苦丁。” “怎么了镊逝?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵壮啊,是天一觀的道長。 經(jīng)常有香客問我撑蒜,道長歹啼,這世上最難降的妖魔是什么玄渗? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮狸眼,結果婚禮上藤树,老公的妹妹穿的比我還像新娘。我一直安慰自己份企,他們只是感情好也榄,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著司志,像睡著了一般甜紫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骂远,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天囚霸,我揣著相機與錄音,去河邊找鬼激才。 笑死拓型,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的瘸恼。 我是一名探鬼主播劣挫,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼东帅!你這毒婦竟也來了压固?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤靠闭,失蹤者是張志新(化名)和其女友劉穎帐我,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愧膀,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡拦键,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了檩淋。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芬为。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蟀悦,靈堂內(nèi)的尸體忽然破棺而出碳柱,到底是詐尸還是另有隱情,我是刑警寧澤熬芜,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站福稳,受9級特大地震影響涎拉,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一鼓拧、第九天 我趴在偏房一處隱蔽的房頂上張望半火。 院中可真熱鬧,春花似錦季俩、人聲如沸钮糖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽店归。三九已至,卻和暖如春酪我,著一層夾襖步出監(jiān)牢的瞬間消痛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工都哭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留秩伞,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓欺矫,卻偏偏與公主長得像纱新,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子穆趴,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

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