React Hook

現(xiàn)在編寫新的組件的時候我們有兩個選擇:class組件和函數(shù)組件。那么什么是函數(shù)組件呢?

現(xiàn)在來看一段函數(shù):

function Welcome(props) {
   return <h1>Hello, { props.name }</h1>;
}

可以看到杯道,函數(shù)組件是一個返回值是 DOM 結(jié)構(gòu)的函數(shù)。

函數(shù)組件與 class 組件的區(qū)別

  • 函數(shù)式組件沒有組件實例化的過程豹爹,無實例化過程也就不需要分配多余的內(nèi)存裆悄,從而性能得到一定的提升;

  • 函數(shù)組件本身沒有 this 的臂聋,所以使用 Ref 等模塊時與類組件也有所區(qū)別光稼;

  • 函數(shù)式組件本身沒有內(nèi)部的 state,數(shù)據(jù)依賴于 props 的傳入孩等,所以又稱為無狀態(tài)組件艾君;

  • 函數(shù)式組件本身是沒有訪問生命周期的方法。

如何使用函數(shù)式組件肄方,又可以使用 React 的強(qiáng)大功能呢冰垄?從 React 16.8 開始,React 新增 Hook 特性权她,使得可以在不編寫 class 的情況下使用 state 以及其他的 React 特性虹茶。這使得函數(shù)式組件也可以實現(xiàn) class 類組件的功能∈判剑現(xiàn)在,我們終于可以不用寫類蝴罪,也可以很方便地編寫復(fù)雜的功能了董济。

何時使用函數(shù)式組件

函數(shù)式組件相比類組件,擁有更好的性能和更簡單的職責(zé)要门,十分適合分割原本很寵大的組件虏肾。

什么是 Hook?

Hook 是可以在函數(shù)組件里“鉤入” React state 及生命周期等特性的函數(shù)。Hook 不能在 class 組件中使用欢搜,應(yīng)在函數(shù)式組件中使用封豪,使得不使用 class 也能使用 React。

Hook 使用規(guī)則

只在最頂層使用 Hook

只在函數(shù)最外層調(diào)用 Hook狂巢,不要在循環(huán)撑毛,條件或嵌套函數(shù)中調(diào)用 Hook,確边罅欤總是在 React 函數(shù)的最頂層調(diào)用藻雌。這樣就能確保 Hook 在每次渲染中按照同樣的順序被調(diào)用。

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

不要在普通的函數(shù)中調(diào)用 Hook斩个】韬迹可以在:

  • 在 React 函數(shù)組件中調(diào)用 Hook

  • 在自定義 Hook 中調(diào)用

遵循此規(guī)則,可以確保組件的狀態(tài)邏輯清晰受啥。

下面來具體介紹一下 Hook:

useState

const [state, setState] = useState(initialState);

useState 是允許在 React 函數(shù)組件中添加 state 的 Hook做个。React 會在重復(fù)渲染時保留這個 State。useState 唯一的參數(shù)是初始 State滚局,這個初始 State 參數(shù)只在第一次渲染時會被用到居暖。useState 返回一對值:當(dāng)前狀態(tài)和更新 state 的函數(shù)。

在后續(xù)的重新渲染中藤肢,useState 返回的第一個值將始終是更新后最新的 state太闺。

首先看一下state hook:

import React, { useState } from 'react';

function Example() {
  // 聲明一個叫 "count" 的 state 變量,初始值為0
  const [count, setCount] = useState(0);

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

再看一下等價的class:

import React from 'react';

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

這兩段代碼功能是一樣的嘁圈,每單擊一次按鈕將 count + 1省骂,但是函數(shù)組件相比 class 組件更簡單更直觀。

惰性初始 state
const [count, setCount] = useState(() => {
  const initialState = handleComputation(props);
  return initialState;
});

如果初始 state 需要通過復(fù)雜計算獲得最住,則可以傳入一個函數(shù)钞澳,在函數(shù)中計算并返回初始的 state,此函數(shù)只在初始渲染時被調(diào)用涨缚。

函數(shù)式更新 state
import React, { useState } from 'react';

function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: { count }
      <button onClick={ () => setCount(initialCount) }>Reset</button>
      <button onClick={ () => setCount(prevCount => prevCount - 1) }> - </button>
      <button onClick={ () => setCount(prevCount => prevCount + 1) }> + </button>
    </>
  );
}

新的 state 需要通過使用先前的 state 計算得出轧粟,可以將函數(shù)傳遞給 setCount,該函數(shù)將接收先前的 state,并返回一個更新后的值逃延。

useEffect

useEffect(didUpdate, [deps]); 

useEffect 給函數(shù)組件增加了操作副作用的能力览妖,也可看作是 componentDidMount,componentDidUpdate 和 componentWillUnmount 這三個函數(shù)的組合揽祥。第二個參數(shù)用來控制如何渲染讽膏,當(dāng)?shù)诙€參數(shù)存在時,只有當(dāng)該值改變時才會觸發(fā)渲染拄丰。

默認(rèn)情況下府树,effect 將在每輪渲染結(jié)束后執(zhí)行,加上第二個參數(shù)可以讓它在只有某些值改變的時候才執(zhí)行料按。

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

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

  // 與 componentDidMount 和 componentDidUpdate 作用相同
  useEffect(() => {
    // 更新 document 的 title
    document.title = `You clicked ${count} times`;
  }, [count]); // 僅在 count更改時更新

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

每次渲染都執(zhí)行 effect 可能會導(dǎo)致性能問題奄侠,在class組件中可以通過 componentDidUpdate 中添加 prevProps 或 prevState 的比較邏輯解決;在 useEffect 中可以通過第二個可選參數(shù)來控制渲染载矿,僅當(dāng) count 改變的時候才會執(zhí)行 effect垄潮,否則會跳過這個 effect,實現(xiàn)性能的優(yōu)化闷盔。

相同的功能使用 class 再來實現(xiàn)一下:

import React from 'react';

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

在以上的示例中弯洗,需要在兩個生命周期函數(shù)中編寫相同的代碼,但是 React 的 class 組件并沒有提供這樣的方法逢勾。

需要清除的 effect

實際項目當(dāng)中牡整,有一些副作用是需要清除的。例如訂閱外部數(shù)據(jù)源溺拱,定時器等逃贝。這種情況下,清除工作是非常重要的迫摔,可以防止引起內(nèi)存泄露沐扳。

首先看一下 class 組件:

import React from 'react';

class FriendStatusWithCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0, isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }
  // ...

可以看到:設(shè)置 document.title 的邏輯被分割到 componentDidMount 和 componentDidUpdate 中,訂閱邏輯被分割到 componentDidMount 和 componentWillUnmount 中句占,componentDidMount 同時包含了兩個不同的功能迫皱。

現(xiàn)在再來看看 effect 的實現(xiàn):

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

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  }, [count]);

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  }, [props]);
  // ...
}
每個 effect 都可以返回一個清除函數(shù),可以將添加和移除訂閱的邏輯放在一起辖众。在每次渲染的時候 React 會對前一個 effect 進(jìn)行清除再執(zhí)行當(dāng)前的 effect。

Hook 允許我們使用多個 Effect 實現(xiàn)關(guān)注點分離和敬。React 將按照 effect 聲明的順序依次調(diào)用組件中的 effect凹炸。

注意: 如果想執(zhí)行只運行一次的 effect(僅在組件掛載和卸載時執(zhí)行),可以傳遞一個空數(shù)組([ ])作為第二個參數(shù)昼弟,說明 effect 不依賴于 props 或 state 中的任何值啤它,它也不需要重復(fù)執(zhí)行。

useContext

const value = useContext(MyContext);

接收一個 context 對象(React.CreateContext 的返回值)并返回該 context 的當(dāng)前值。當(dāng)組件上層最近的 MyContext.Provider 更新時变骡,該 Hook 會觸發(fā)重渲染离赫,并使用最新傳遞給 MyContext provider 的 context value 值。即使祖先組件使用 React.memo 或 shouldComponentUpdate塌碌,也會在組件本身使用 useContext 時重新渲染渊胸。

調(diào)用了 useContext 的組件總會在 context 值變化時重新渲染。

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

useCallback

useCallback(function, [deps])

useCallback 的作用是返回一個記憶化的回調(diào)函數(shù)台妆。第一個參數(shù)是一個回調(diào)函數(shù)翎猛,第二個參數(shù)是依賴項數(shù)組,在依賴項改變時會觸發(fā)更新接剩。

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

把內(nèi)聯(lián)回調(diào)函數(shù)及依賴項數(shù)組作為參數(shù)傳入 useCallback切厘,它將返回該回調(diào)函數(shù)的記憶版本,該回調(diào)函數(shù)僅在某個依賴項改變時才會更新懊缺。

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);

接收 (state, action) => newState 的 reducer疫稿,返回當(dāng)前的 state 以及配套的 dispatch 方法。適用于 state 邏輯較復(fù)雜且包含多個子值鹃两,或者下一個 state 依賴于之前的 state 等的場景遗座。

指定初始的 state -- 將初始 state 作為第二個參數(shù)傳入
const [state, dispatch] = useReducer(
    reducer,
    {count: initialCount}
  );
惰性初始化 -- 將 init 函數(shù)作為第三個參數(shù)傳入
function init(initialCount) { 
  return {count: initialCount};
}
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':      
      return init(action.payload);    
    default:
      throw new Error();
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);  
return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'reset', payload: initialCount})}>        
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

useMemo

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

useMemo 的作用是返回一個記憶化的值。傳入 useMemo 的函數(shù)會在渲染期間執(zhí)行怔毛,不要在函數(shù)內(nèi)部執(zhí)行與渲染無關(guān)的操作员萍,注意:副作用的操作屬于 useEffect 的范疇。

如果沒有提供依賴項數(shù)組拣度,useMemo 會在每次渲染時計算新的值碎绎,只有將依賴項數(shù)組作為參數(shù)傳給 useMemo,它會在某個依賴項改變時才重新計算值抗果,這有助于避免每次渲染時都進(jìn)行高開銷的計算筋帖。

useLayoutEffect

useLayoutEffect(didUpdate, [deps]);

在所有的 DOM 變更之后同步調(diào)用 effect。使用它讀取 DOM 布局并同步觸發(fā)重渲染冤馏。在瀏覽器執(zhí)行繪制前日麸, useLayoutEffect 內(nèi)部的DOM 變更同步執(zhí)行,才不會感覺到視覺上的不一致逮光。

注意:盡可能使用標(biāo)準(zhǔn)的 useEffect 以避免阻塞視覺更新代箭。

useRef

const refContainer = useRef(initialValue);

useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化為傳入的參數(shù)(initialValue)涕刚,返回的 ref 對象在組件的整個生命周期內(nèi)保持不變嗡综。它可以很方便地保存任何可變值,會在每次渲染時返回同一個 ref 對象杜漠。

注意:ref 對象內(nèi)容發(fā)生變化時极景,useRef 不會通知察净,變更 .current 屬性不會引發(fā)組件重新渲染。想要在 React 綁定或解綁 DOM 節(jié)點的 ref 時運行某些代碼盼樟,應(yīng)使用回調(diào) ref 實現(xiàn)氢卡。

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已掛載到 DOM 上的文本輸入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle 可以在使用 ref 時自定義暴露給父組件的實例值,應(yīng)與 forwardRef 一起使用晨缴。

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

父組件使用 inputRef.current.focus() 來調(diào)用译秦。

useDebugValue

useDebugValue(value, formatFunction)

useDebugValue 用于在 React 開發(fā)者工具里顯示自定義 hook 的標(biāo)簽。

格式化 debug 值
useDebugValue(date, date => date.toDateString());

useDebugValue 接受格式化函數(shù)作為可選的第二個參數(shù)喜庞。該函數(shù)接受 debug 值作為參數(shù)诀浪,并會返回一個格式化的顯示值。

自定義 Hook

自定義 Hook 是一個函數(shù)延都,可以將組件邏輯提取到可重用的函數(shù)雷猪。名稱以" use "開頭,函數(shù)內(nèi)部可以調(diào)用其他的 Hook晰房。

現(xiàn)在來定義一個 useFriendStatus Hook:

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

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

  return isOnline;
}

這里 useFriendStatus 作用是訂閱好友的在線狀態(tài)求摇。

將 useFriendStatus 在不同的組件中使用:

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

現(xiàn)在很好地實現(xiàn)了好友在線狀態(tài)的復(fù)用,只需 const isOnline = useFriendStatus(props.friend.id); 就可知道好友的狀態(tài)了殊者。

自定義 Hook 是一種重用狀態(tài)邏輯的機(jī)制与境,每次使用自定義 Hook 時,其中所有的 state 和副作用都是完全隔離的猖吴。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末摔刁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子海蔽,更是在濱河造成了極大的恐慌共屈,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件党窜,死亡現(xiàn)場離奇詭異拗引,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)幌衣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門矾削,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人豁护,你說我怎么就攤上這事哼凯。” “怎么了楚里?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵挡逼,是天一觀的道長。 經(jīng)常有香客問我腻豌,道長家坎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任吝梅,我火速辦了婚禮虱疏,結(jié)果婚禮上炸茧,老公的妹妹穿的比我還像新娘肪虎。我一直安慰自己,他們只是感情好芯勘,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布右冻。 她就那樣靜靜地躺著装蓬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纱扭。 梳的紋絲不亂的頭發(fā)上牍帚,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機(jī)與錄音乳蛾,去河邊找鬼暗赶。 笑死,一個胖子當(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
  • 我被黑心中介騙來泰國打工绵脯, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留佳励,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓蛆挫,卻偏偏與公主長得像赃承,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子悴侵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354

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