再聊react hook

React Hook是React函數(shù)式組件转锈,它不僅僅有函數(shù)組件的特性,還帶有React框架的特性。所以蟹倾,官網(wǎng)文檔多次強調(diào):

只在 React 函數(shù)中調(diào)用 Hook

不要在普通的 JavaScript 函數(shù)中調(diào)用 Hook培慌。你可以:

  • ? 在 React 的函數(shù)組件中調(diào)用 Hook
  • ? 在自定義 Hook 中調(diào)用其他 Hook

1. 那么 React 中 Function Component 與 Class Component 有何不同表鳍?

Class Component:

class ProfilePage extends React.Component {
  showMessage = () => {
    alert("Followed " + this.props.user);
  };

  handleClick = () => {
    setTimeout(this.showMessage, 3000);
  };

  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}

Function Component:

function ProfilePage(props) {
  const showMessage = () => {
    alert("Followed " + props.user);
  };

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

  return <button onClick={handleClick}>Follow</button>;
}

如下父級組件的調(diào)用方式:

<ProfilePageFunction user={this.state.user} />
<ProfilePageClass user={this.state.user} />

場景:那么當(dāng)點擊按鈕后的 3 秒內(nèi)胁镐,父級修改了 this.state.user笨农,彈出的用戶名是修改前的還是修改后的呢份招?
答案:Class Component 展示的是修改后的值廓旬,F(xiàn)unction Component 展示的是修改前的值
原因this 在 Class Component 中是可變的十气,當(dāng)組件入?yún)l(fā)生變化時, this.props同步改變。而 Function Component 不存在this.props 的語法炼蛤,因此 props 總是不可變的絮识。

2. 用Hook 創(chuàng)建函數(shù)組件,會有什么變化呢浅萧?

import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
 
 function Example() {
   const [count, setCount] = useState(0);
 
   const handleAlertClick = useCallback(()=>{
     setTimeout(() => {
       alert('You clicked on: ' + count);
     }, 3000)
   }, [count]);
 
   return (
     <div>
       <p>You clicked {count} times</p>
       <button onClick={() => setCount(count + 1)}>
         增加 count
       </button>
       <button onClick={handleAlertClick}>
         顯示 count
       </button>
     </div>
   );
 }
 const rootElement = document.getElementById("root");
 ReactDOM.render(<Example />, rootElement);

場景:點擊 顯示 按鈕徘郭,在 3s 后(模擬耗時任務(wù))會出現(xiàn)彈層闪湾。在這 3s 期間快速點擊 增加 count 按鈕
結(jié)果:3s 后看到的彈層計數(shù)仍舊為 0濒憋!
原因:在 handleAlertClick 函數(shù)執(zhí)行的那個 Render 過程里裆站,count 的值可以看作常量 本姥。執(zhí)行 setCount(count + 1) 時會交由一個全新的 Render 渲染氛赐,所以不會執(zhí)行 handleAlertClick 函數(shù)艰管。

那我們用useEffect 改造一下呢?

 const [count, setCount] = useState(0);
 useEffect(()=>{
    setTimeout(() => {
      alert('count: ' + count);
    }, 3000)
  }, [count]); // 監(jiān)聽count變化

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

行為:三秒鐘內(nèi)點擊“增加 count”按鈕三次耘擂。
結(jié)果:先顯示0(初始化),然依次顯示1鸽照,2诞外,3,useEffect被觸發(fā)4次脾拆。
原因useEffect也是具有 Capture Value 的特性,每次render都是獨立快照百揭,拿到的 count 都是固化下來的常量爽哎。和上面例子不同之處在于,它監(jiān)聽了count變化器一,可以被觸發(fā)多次Render课锌。

什么是Capture Value?

每次 Render 的內(nèi)容都會形成一個快照并保留下來,因此當(dāng)狀態(tài)變更而 Rerender 時祈秕,就形成了 N 個 Render 狀態(tài)渺贤,而每個 Render 狀態(tài)都擁有自己固定不變的 Props 與 State。

3. 如何繞過 Capture Value请毛?

利用 useRef 志鞍!

useRef定義

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

ref 類型的變量通常是用來存儲 DOM 元素引用。

但在 react hooks 中仙蚜,它可以存放任何可變數(shù)據(jù)玻孟,并在所有 Render 過程中保持著唯一引用,因此所有對 ref 的賦值或取值鳍征,拿到的都只有一個最終狀態(tài),而不會在每個 Render 間存在隔離面徽。

function Example() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);

  const handleAlertClick = useCallback(
    () => {
      setTimeout(() => {
        alert("You clicked on: " + countRef.current);
      }, 3000);
    },
    [count]
  );

  return (
    <div>
      <p>You clicked {count} times</p>
      <button
        onClick={() => {
          countRef.current = count + 1;
          setCount(count + 1);
        }}
      >
        增加 count
      </button>
      <button onClick={handleAlertClick}>顯示 count</button>
    </div>
  );

行為:三秒鐘內(nèi)點擊“增加 count”按鈕三次艳丛。
結(jié)果: 先顯示0(初始化),3s 后顯示3次3趟紊。

4. useEffect搭配useReducer

利用 useEffect 的兄弟 useReducer 函數(shù)氮双,可以將更新與動作解耦。

const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;

useEffect(() => {
  const id = setInterval(() => {
    dispatch({ type: "tick" }); // Instead of setCount(c => c + step);
  }, 1000);
  return () => clearInterval(id);
}, [dispatch]);

這就是一個局部 “Redux”霎匈,由于更新變成了dispatch({ type: "tick" }) 所以不管更新時需要依賴多少變量戴差,在調(diào)用更新的動作里都不需要依賴任何變量。 具體更新操作在 reducer 函數(shù)里寫就可以了

5. useMemo vs useCallback

按照官網(wǎng)說法铛嘱,兩個的相同點和不同點非常明確(React教程

  • 相同點:
    • 都會在組件第一次渲染的時候執(zhí)行
    • 依賴的變量發(fā)生改變時再次執(zhí)行
    • 返回緩存的值
  • 不同點:
    • useMemo 返回緩存的變量
    • useCallback 返回緩存的函數(shù)

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

5.1 useMemo實例

export function useDocumentMap() {
  const { docState: { sideBarList = [] } = {} } = useStore()
  return useMemo(() => getDocumentMap(sideBarList), [sideBarList])
}

getDocumentMap是一個復(fù)雜計算函數(shù)暖释,sideBarList為store里存儲的值袭厂。UI顯示的時候,需要做一次model=>UI model的轉(zhuǎn)換球匕。因為這個轉(zhuǎn)換需要復(fù)雜計算纹磺,所以用useMemo做緩存,在sideBarList不變的情況下亮曹,調(diào)用useDocumentMap獲取UI model只會計算一次橄杨。

5.2 useCallback實例

import React, { useState, useCallback } from 'react';
import Button from './Button';

export default function App() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  const handleClickButton1 = () => {
    setCount1(count1 + 1);
  };

  const handleClickButton2 = useCallback(() => {
    setCount2(count2 + 1);
  }, [count2]);

  return (
    <div>
      <div>
        <Button onClickButton={handleClickButton1}>Button1</Button>
      </div>
      <div>
        <Button onClickButton={handleClickButton2}>Button2</Button>
      </div>
    </div>
  );
}

// Button組件
import React from 'react';

const Button = ({ onClickButton, children }) => {
  return (
    <>
      <button onClick={onClickButton}>{children}</button>
      <span>{Math.random()}</span>
    </>
  );
};

export default React.memo(Button);

可見Button組件只依賴onClickButtonchildren
如果點擊Button1照卦,因為count1的值發(fā)生變化式矫,導(dǎo)致容器App重新渲染,handleClickButton1變量重新被聲明(又創(chuàng)建了一個新的函數(shù)對象)役耕,從而第一個Button被再次渲染采转。而handleClickButton2呢?因為被useCallback包了一層蹄葱,且依賴count2氏义,所以,此時handleClickButton2變量不會被重新聲明图云,還是之前的函數(shù)引用地址惯悠,所以,第二個Button不會被重復(fù)渲染竣况。

React.memo 這個方法克婶,會對 props 做一個淺層比較,如果 props 沒有發(fā)生改變丹泉,則不會重新渲染此組件

5.3 總結(jié)

  • 如果有函數(shù)傳遞給子組件情萤,使用useCallback
  • 如果有值傳遞給子組件,使用useMemo
  • useMemo摹恨、useCallback 都自帶閉包筋岛,類似useEffectCapture Value能力

參考文章:
理解 React Hooks 的 Capture Value 特性
精讀《useEffect 完全指南》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市晒哄,隨后出現(xiàn)的幾起案子睁宰,更是在濱河造成了極大的恐慌,老刑警劉巖寝凌,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柒傻,死亡現(xiàn)場離奇詭異,居然都是意外死亡较木,警方通過查閱死者的電腦和手機红符,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人预侯,你說我怎么就攤上這事致开。” “怎么了雌桑?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵喇喉,是天一觀的道長。 經(jīng)常有香客問我校坑,道長拣技,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任耍目,我火速辦了婚禮膏斤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘邪驮。我一直安慰自己莫辨,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布毅访。 她就那樣靜靜地躺著沮榜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪喻粹。 梳的紋絲不亂的頭發(fā)上蟆融,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機與錄音守呜,去河邊找鬼型酥。 笑死,一個胖子當(dāng)著我的面吹牛查乒,可吹牛的內(nèi)容都是我干的弥喉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼玛迄,長吁一口氣:“原來是場噩夢啊……” “哼由境!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蓖议,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤藻肄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拒担,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡攻询,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年从撼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡低零,死狀恐怖婆翔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掏婶,我是刑警寧澤啃奴,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站雄妥,受9級特大地震影響最蕾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜老厌,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一瘟则、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枝秤,春花似錦醋拧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至薇溃,卻和暖如春菌赖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痊焊。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工盏袄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人薄啥。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓辕羽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親垄惧。 傳聞我的和親對象是個殘疾皇子刁愿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354