react hooks 初步使用(一)

一桩警、前言

那一天我二十一歲可训,在我一生的黃金時代,我有好多奢望捶枢。我想愛握截,想吃,還想在一瞬間變成天上半明半暗的云柱蟀,我覺得自己會永遠(yuǎn)生猛下去川蒙,什么也錘不了我。

<p align="right">《黃金時代》王小波</p>

二长已、關(guān)于Hooks

Hook 是 React 16.8 的新增特性畜眨。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性昼牛。Hook 的本質(zhì)還是 JavaScript 函數(shù),但是在使用它時需要遵循兩條規(guī)則

只在最頂層使用 Hook

不要在循環(huán)康聂,條件或嵌套函數(shù)中調(diào)用 Hook贰健,確保總是在你的 React 函數(shù)的最頂層以及任何 return 之前調(diào)用他們恬汁。

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

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

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

三、Hooks API

hooks的api有如下幾個氓侧,按照官方文檔劃分了一下脊另。

react+hooks.png
useState

這個算是最簡單的 hook 函數(shù)了,它的作用就是初始化一個值约巷,返回一個 和 initialState 相同的 state偎痛,以及更新 state 的函數(shù) setState

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

我們來使用一下 useState

import React, { useState } from 'react';

const Index = () => {
  // setNumber 更新number函數(shù)
  const [number, setNumber] = useState(0);

  return (
    <>
      <div>{number}</div>
      <button onClick={() => setNumber(number + 1)}>Add Number</button>
      <button onClick={() => setNumber(number - 1)}>Sub Number</button>
    </>
  );
};

export default Index;

我們可以看到 useState 初始化一個值 number 為 0 之后返回一個數(shù)組独郎,里面包含 number 本身 和 改變 number 值的函數(shù) setNumber 我們通過兩個按鈕來綁定 setNumber 來改變 number踩麦。實現(xiàn) number 值的加一減一。

useEffect

useEffect 就是用來在函數(shù)組件中模擬類組件中的生命周期函數(shù)的氓癌。接受兩個參數(shù)谓谦,第一個參數(shù)就是一個箭頭函數(shù)。箭頭函數(shù)返回一個函數(shù)贪婉,這個函數(shù)就是組件卸載的時候觸發(fā)反粥。(這里舉個??:我們要監(jiān)聽頁面的 message 的時候,那么就在 render dom 的后面監(jiān)聽谓松,在 dom remove 的時候移除監(jiān)聽就行了)

關(guān)于第二個參數(shù)[deps]

  1. 沒有第二個參數(shù)的時候星压,當(dāng) number 發(fā)生變化的時候践剂,組件會 remove 后 render鬼譬,有點像 shouldComponentUpdate生命周期。
  2. 當(dāng)?shù)诙€參數(shù)為 [] 的時候逊脯,只會執(zhí)行初始化的 render, 這個時候相當(dāng)于生命周期 componentDidMount
  3. 當(dāng)?shù)诙€參數(shù)不為空的時候优质,只有存在數(shù)組中的參數(shù)發(fā)生改變的時候,就會 remove 后 render军洼,就是更新組件巩螃。
import React, { useEffect, useState } from 'react';

const Index = () => {
  // setNumber 更新number函數(shù)
  const [number, setNumber] = useState(0);
  useEffect(() => {
    console.log('render dom');
    return () => {
      console.log('dom remove');
    };
  }, [number]);

  return (
    <>
      <div>{number}</div>
      <button onClick={() => setNumber(number + 1)}>Add Number</button>
      <button onClick={() => setNumber(number - 1)}>Sub Number</button>
    </>
  );
};

export default Index;
useContext

useContext 接收一個 context 對象(React.createContext 的返回值)并返回該 context 的當(dāng)前值。當(dāng)前的 context 值由上層組件中距離當(dāng)前組件最近的 <MyContext.Provider> 的 value prop 決定匕争。其實這里的 useContext 就和 context.Consumer 的效果差不多了避乏。這里提一下 context.Consumer 的用法

<MyContext.Consumer>
  {value =>  {/* 基于 context 值進(jìn)行渲染 */}}
</MyContext.Consumer>

看個??,定義了一個主題對象甘桑,里面有兩個主題 light 和 dark拍皮,我們用 ThemeContext 接收一個context 對象, 使用 const theme = useContext(ThemeContext); 去訂閱它歹叮,而返回的 theme 值是距離當(dāng)前組件最近的 <ThemeContext.Provider value={themes.dark}> 的value prop。所以下面渲染的theme就是 dark主題铆帽。

import React, { useContext } from 'react';

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

const ThemeContext = React.createContext();

function Index() {
  return (
    // 這里傳入light主題
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

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

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

export default Index;
useReducer

useReducer useState 的替代方案,它接收一個形如 (state, action) => newState 的 reducer,(和 redux 一樣)在某些場景下咆耿,useReducer 會比 useState 更適用,例如 state 邏輯較復(fù)雜且包含多個子值以下是用 reducer 重寫 useState 一節(jié)的計數(shù)器示例爹橱。

import React, { useReducer } from 'react';

function reducer(state, action) {
  console.log(state, action);
  switch (action.type) {
    case 'add':
      return { count: state.count + 1 };
    case 'sub':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Index() {
  //   return <h1>1</h1>;
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      <h1>count:{state.count}</h1>
      <button onClick={() => dispatch({ type: 'add' })}>+</button>
      <button onClick={() => dispatch({ type: 'sub' })}>-</button>
    </>
  );
}

export default Index;
useCallback

useCallback(fn, deps) 相當(dāng)于 useMemo(() => fn, deps)萨螺。可以看出它是用來緩存函數(shù)的愧驱,下面是我mentor在reviewcode的時候提出的優(yōu)化建議 (標(biāo)注cr的)

const TreeDemo = () => {
  // cr: 函數(shù)作為子組件的props時慰技,得考慮性能,用useCallback;
  //   const onSelect = (selectedKeys) => {
  //     console.log('selected', selectedKeys);
  //   };

  //   const onCheck = (checkedKeys) => {
  //     console.log('onCheck', checkedKeys);
  //   };

  //reviewcode
  const onSelect = useCallback(selectedKeys => {
    console.log('selected', selectedKeys);
  });

  const onCheck = useCallback(checkedKeys => {
    console.log('onCheck', checkedKeys);
  });

  // cr: 默認(rèn)值用狀態(tài)去控制组砚,如果有外部傳入的值惹盼,優(yōu)先使用外部的值
  return (
    <Tree
      checkable
      //默認(rèn)展開指定的樹節(jié)點
      defaultExpandedKeys={['0-0', '0-0-0', '0-0-1']}
      //默認(rèn)選中的樹節(jié)點
      defaultSelectedKeys={['0-0']}
      //默認(rèn)選中復(fù)選框的樹節(jié)點
      defaultCheckedKeys={['0-0-1', '0-0-0-1']}
      onSelect={onSelect}
      onCheck={onCheck}
      treeData={treeData}
    />
  );
};
useMemo

useMemo 用來緩存數(shù)據(jù),實例如下 sum是個需要計算的參數(shù)惫确,當(dāng)我們沒使用memo做緩存的時候手报,在更新rank的時候也會重新計算 sum的值,這顯然是不合理的改化,我們只需要在更新count的時候才會去重新計算sum的值掩蛤。這種優(yōu)化有助于避免在每次渲染時都進(jìn)行高開銷的計算。

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

function Index() {
  const [count, setCount] = useState(100);
  const [rank, setRank] = useState(1);

  let sum = useMemo(() => {
    console.log('calculate');
    let sum = 0;
    for (let i = 0; i <= count; i++) {
      sum += i;
    }
    return sum;
  }, [count]);

  return (
    <>
      <h1>{rank}</h1>
      <button onClick={() => setRank(rank + 1)}>Add Rank</button>
      <h5>{sum}</h5>
      <button onClick={() => setCount(count + 1)}>Add Count</button>
    </>
  );
}

export default Index;
useRef

useRef 用來獲取元素DOM節(jié)點陈肛,這里還有其他的方法

  1. 直接在節(jié)點中獲取ref={node => this.node = node}
  2. 使用React.createRef()API
import React, { useRef } from 'react';

function Index() {
  const node = useRef(null);
  const getNode = () => {
    //   打印節(jié)點
    console.log(node.current);
  };
  return (
    <div>
      {/* 注意這里要綁定 */}
      <h1 ref={node}>Node</h1>
      <button onClick={() => getNode()}>Get Node</button>
    </div>
  );
}

export default Index;
useImperativeHandle

useImperativeHandle 可以讓你在使用 ref 時自定義暴露給父組件的實例值,下面我們看個父組件調(diào)用子組件的focus函數(shù)

import React, { forwardRef, useImperativeHandle, useRef } from 'react';

function Input(props, ref) {
  console.log(props, ref);
  const inputRef = useRef(null);


  useImperativeHandle(
    ref,
    () => {
      const handleFn = {
        focus() {
          inputRef.current.focus();
        },
      };
      return handleFn;
    },
    []
  );

  return <input type='text' ref={inputRef} />;
}

const UseInput = forwardRef(Input);

function Index() {
  const current = useRef(null);
  //   console.log(current);
  const handleClick = () => {
    console.log(current);
    const { focus } = current.current;
    focus();
  };

  return (
    <div>
      <UseInput ref={current} />
      <button onClick={handleClick}>獲取焦點</button>
    </div>
  );
}

export default Index;

在上述的示例中揍鸟,React 會將 <UseInput ref={current}> 元素的 current 作為第二個參數(shù)傳遞給 React.forwardRef 函數(shù)中的渲染函數(shù) Input。Input會將 current 傳遞給 useImperativeHandle 的第一個參數(shù) ref句旱。注意:這里就沒將current傳遞給給<input>了阳藻,而是用了inputRef來取<input>的DOM實例在useImperativeHandle里面操作,我的理解就是相當(dāng)于官方不推薦讓我們直接操作 input谈撒。 而是使用了 useImperativeHandle 來代理操作而已腥泥。由 useImperativeHandle 暴露出方法供我們使用。

useLayoutEffect

其函數(shù)簽名與 useEffect 相同啃匿,但它會在所有的 DOM 變更之后同步調(diào)用 effect蛔外。可以使用它來讀取 DOM 布局并同步觸發(fā)重渲染溯乒。在瀏覽器執(zhí)行繪制之前夹厌,useLayoutEffect 內(nèi)部的更新計劃將被同步刷新。也就是說useLayoutEffect回調(diào)執(zhí)行在瀏覽器繪制之前,也就是 useLayoutEffect 回調(diào)函數(shù)代碼可能會阻止瀏覽器渲染裆悄。

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

function Index() {
  const [color, setColor] = useState(0);

  useLayoutEffect(() => {
    console.log('render');
    if (color === 0) {
      setColor(color + 1);
    }
  }, [color]);

  const colorStyle = {
    color: color ? 'yellow' : 'red',
  };
  return (
    <>
      <div style={colorStyle}>color text {color}</div>
      <button onClick={() => setColor(0)}>Click</button>
    </>
  );
}

export default Index;

從例子我們可以看到矛纹,當(dāng)點擊Click按鈕的時候,視圖會一直顯示黃色光稼,因為useLayoutEffect回調(diào)阻塞了瀏覽器的渲染或南。只有當(dāng)回調(diào)代碼執(zhí)行完了之后逻住,也就是setColor(color + 1)執(zhí)行后,瀏覽器才會渲染迎献,看到的視圖也就一直是黃色了瞎访,當(dāng)我們把useLayoutEffect換成useEffect的時候,我們快速點擊Click按鈕的時候就會出現(xiàn)閃爍現(xiàn)象吁恍,這是因為useEffect不會阻塞瀏覽器的渲染扒秸,點擊按鈕執(zhí)行 serColor(0)后,視圖就直接更新了變?yōu)榧t色冀瓦。然后useEffect回調(diào)執(zhí)行setColor(color + 1)之后伴奥,再變?yōu)辄S色,所以就會出現(xiàn)閃爍現(xiàn)象翼闽。

useDebugValue
useDebugValue(value)

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

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

  // ...

  // 在開發(fā)者工具中的這個 Hook 旁邊顯示標(biāo)簽
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

提示

我們不推薦你向每個自定義 Hook 添加 debug 值拾徙。當(dāng)它作為共享庫的一部分時才最有價值。

useTransition

這個API處于試驗階段感局,等正式更新后再打算尼啡。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市询微,隨后出現(xiàn)的幾起案子崖瞭,更是在濱河造成了極大的恐慌,老刑警劉巖撑毛,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件书聚,死亡現(xiàn)場離奇詭異,居然都是意外死亡藻雌,警方通過查閱死者的電腦和手機(jī)雌续,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胯杭,“玉大人驯杜,你說我怎么就攤上這事∏复荩” “怎么了艇肴?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長叁温。 經(jīng)常有香客問我,道長核畴,這世上最難降的妖魔是什么膝但? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮谤草,結(jié)果婚禮上跟束,老公的妹妹穿的比我還像新娘莺奸。我一直安慰自己,他們只是感情好冀宴,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布灭贷。 她就那樣靜靜地躺著,像睡著了一般略贮。 火紅的嫁衣襯著肌膚如雪甚疟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天逃延,我揣著相機(jī)與錄音览妖,去河邊找鬼。 笑死揽祥,一個胖子當(dāng)著我的面吹牛讽膏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拄丰,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼府树,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了料按?” 一聲冷哼從身側(cè)響起挺尾,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎站绪,沒想到半個月后遭铺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡恢准,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年魂挂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馁筐。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡涂召,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出敏沉,到底是詐尸還是另有隱情果正,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布盟迟,位于F島的核電站秋泳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏攒菠。R本人自食惡果不足惜迫皱,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辖众。 院中可真熱鬧卓起,春花似錦和敬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至奕筐,卻和暖如春舱痘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背救欧。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工衰粹, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人笆怠。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓铝耻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蹬刷。 傳聞我的和親對象是個殘疾皇子瓢捉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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