常用React Hooks 方法

image.png

useState

使用狀態(tài)

const [n, setN] = React.useState(0)
const [user, setUser] = React.useState({name: 'Jack', age: 18})

  • 注意事項1: 不可局部更新

如果state是一個對象,能否部分setState?
答案是不行,因為setState不會幫我們合并屬性
那么useReducer會合并屬性嗎?也不會!
因為React認為這應該是你自己要做的事情

function App(){
    const [user, setUser] = React.useState({name: 'Jack', age: 18})
    const onClick = () =>{
        //setUser不可以局部更新,如果只改變其中一個,那么整個數(shù)據(jù)都會被覆蓋
        // setUser({
        //  name: 'Frank'
        // })
        setUser({
            ...user, //拷貝之前的所有屬性
            name: 'Frank' //這里的name覆蓋之前的name
        })
    }
    return (
        <div className='App'>
            <h1>{user.name}</h1>
            <h2>{user.age}</h2>
            <button onClick={onClick}>Click</button>
        </div>
    )
}
  • 注意事項2: 地址要變

setState(obj) 如果obj內(nèi)存地址不變,那么React就認為數(shù)據(jù)沒有變化,不會更新視圖,了解內(nèi)存地址與 桟堆概念

  • useState接受函數(shù)

const [state, setState] = useState(() => {return initialState})
該函數(shù)返回初始state,且只執(zhí)行一次

  • setState接受函數(shù)

setxxxx(i => i + 1)
如果你能接受這種形式,應該優(yōu)先使用這種形式
修改之后的狀態(tài) 會在函數(shù)組件 再次更新完之后 拿到最新的值,如果想立刻拿到最新的值,使用useRef

useRef

useRef這個hooks函數(shù),除了傳統(tǒng)的用法之外,它還可以“跨渲染周期”保存數(shù)據(jù)。

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

export default function App(props){
  const [count, setCount] = useState(0);

  const doubleCount = useMemo(() => {
    return 2 * count;
  }, [count]);

  const couterRef = useRef();

  useEffect(() => {
    document.title = `The value is ${count}`;
    console.log(couterRef.current);
  }, [count]);
  
  return (
    <>
      <button ref={couterRef} onClick={() => {setCount(count + 1)}}>Count: {count}, double: {doubleCount}</button>
    </>
  );
}

代碼中用useRef創(chuàng)建了couterRef對象,并將其賦給了button的ref屬性。這樣,通過訪問couterRef.current就可以訪問到button對應的DOM對象。

然后再來看看它保存數(shù)據(jù)的用法。

在一個組件中有什么東西可以跨渲染周期,也就是在組件被多次渲染之后依舊不變的屬性?第一個想到的應該是state。沒錯,一個組件的state可以在多次渲染之后依舊不變。但是,state的問題在于一旦修改了它就會造成組件的重新渲染。

那么這個時候就可以使用useRef來跨越渲染周期存儲數(shù)據(jù),而且對它修改也不會引起組件渲染。

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

export default function App(props){
  const [count, setCount] = useState(0);

  const doubleCount = useMemo(() => {
    return 2 * count;
  }, [count]);

  const timerID = useRef();
  
  useEffect(() => {
    timerID.current = setInterval(()=>{
        setCount(count => count + 1);
    }, 1000); 
  }, []);
  
  useEffect(()=>{
      if(count > 10){
          clearInterval(timerID.current);
      }
  });
  
  return (
    <>
      <button ref={couterRef} onClick={() => {setCount(count + 1)}}>Count: {count}, double: {doubleCount}</button>
    </>
  );
}

在上面的例子中,我用ref對象的current屬性來存儲定時器的ID,這樣便可以在多次渲染之后依舊保存定時器ID,從而能正常清除定時器。

useReducer():action 鉤子

React 本身不提供狀態(tài)管理功能,通常需要使用外部庫。這方面最常用的庫是 Redux。

Redux 的核心概念是,組件發(fā)出 action 與狀態(tài)管理器通信。狀態(tài)管理器收到 action 以后,使用 Reducer 函數(shù)算出新的狀態(tài),Reducer 函數(shù)的形式是(state, action) => newState。

useReducers()鉤子用來引入 Reducer 功能。

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

上面是useReducer()的基本用法,它接受 Reducer 函數(shù)和狀態(tài)的初始值作為參數(shù),返回一個數(shù)組。數(shù)組的第一個成員是狀態(tài)的當前值,第二個成員是發(fā)送 action 的dispatch函數(shù)。

下面是一個計數(shù)器的例子。用于計算狀態(tài)的 Reducer 函數(shù)如下。

const myReducer = (state, action) => {
  switch(action.type)  {
    case('countUp'):
      return  {
        ...state,
        count: state.count + 1
      }
    default:
      return  state;
  }
}

組件代碼如下。

function App() {
  const [state, dispatch] = useReducer(myReducer, { count:   0 });
  return  (
    <div className="App">
      <button onClick={() => dispatch({ type: 'countUp' })}>
        +1
      </button>
      <p>Count: {state.count}</p>
    </div>
  );
}

由于 Hooks 可以提供共享狀態(tài)和 Reducer 函數(shù),所以它在這些方面可以取代 Redux。但是,它沒法提供中間件(middleware)和時間旅行(time travel),如果你需要這兩個功能,還是要用 Redux。

useEffect():副作用鉤子

useEffect()用來引入具有副作用的操作,最常見的就是向服務器請求數(shù)據(jù)。以前,放在componentDidMount里面的代碼,現(xiàn)在可以放在useEffect()。

useEffect()的用法如下。

useEffect(()  =>  {
  // Async Action
}, [dependencies])

上面用法中,useEffect()接受兩個參數(shù)。第一個參數(shù)是一個函數(shù),異步操作的代碼放在里面。第二個參數(shù)是一個數(shù)組,用于給出 Effect 的依賴項,只要這個數(shù)組發(fā)生變化,useEffect()就會執(zhí)行。第二個參數(shù)可以省略,這時每次組件渲染時,就會執(zhí)行useEffect()。

下面看一個例子。

const Person = ({ personId }) => {
  const [loading, setLoading] = useState(true);
  const [person, setPerson] = useState({});

  useEffect(() => {
    setLoading(true); 
    fetch(`https://swapi.co/api/people/${personId}/`)
      .then(response => response.json())
      .then(data => {
        setPerson(data);
        setLoading(false);
      });
  }, [personId])

  if (loading === true) {
    return <p>Loading ...</p>
  }

  return <div>
    <p>You're viewing: {person.name}</p>
    <p>Height: {person.height}</p>
    <p>Mass: {person.mass}</p>
  </div>
}

上面代碼中,每當組件參數(shù)personId發(fā)生變化,useEffect()就會執(zhí)行。組件第一次渲染時,useEffect()也會執(zhí)行。

useContext

如果需要在組件之間共享狀態(tài),可以使用useContext()。

現(xiàn)在有倆個組件Navbar和Messages,我們希望它們之間共享狀態(tài)。

<div className="test">
    <Navbar />
    <Messages />
</div>
使用方法如下:

第一步在它們的父組件上使用React的Context API,在組件外部建立一個Context。

const TestContext = React.createContext({)};

組件封裝代碼如下:

<TestContext.Provider 
    value={{
        username: 'superawesome',
    }}
>
    <div className="test">
        <Navbar />
        <Messages />
    </div>
<TestContext.Provider/>

上面的代碼中,TestContext.Provider提供了一個Context對象,這個對象是可以被子組件共享的。

Navbar組件的代碼如下:

const Navbar = () => {
    const { username } = useContext(TestContext);
    return (
        <div className="navbar">
            <p>{username}</p>
        </div>
    )
}

上面代碼中,useContext()鉤子函數(shù)用來引入Context對象,從中獲取username屬性。

Message組件的代碼也類似:

const Messages = () => {
    const { username } = useContext(TestContext);
    return (
        <div className="messages">
            <p>1 message for {username}</p>
        </div>
    )
}

整體代碼如下:

import React, { useContext } from "react";
import ReactDOM from "react-dom";

const TestContext= React.createContext({});

const Navbar = () => {
  const { username } = useContext(TestContext)

  return (
    <div className="navbar">
      <p>{username}</p>
    </div>
  )
}

const Messages = () => {
  const { username } = useContext(TestContext)

  return (
    <div className="messages">
      <p>1 message for {username}</p>
    </div>
  )
}

function App() {
  return (
    <TestContext.Provider 
        value={{
            username: 'superawesome',
        }}
    >
        <div className="test">
            <Navbar />
            <Messages />
        </div>
    <TestContext.Provider/>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

useMemo

我們來看一個反例:

import React from 'react';
 
 
export default function WithoutMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
 
    function expensive() {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }
 
    return <div>
        <h4>{count}-{val}-{expensive()}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}

這里創(chuàng)建了兩個state,然后通過expensive函數(shù),執(zhí)行一次昂貴的計算,拿到count對應的某個值。我們可以看到:無論是修改count還是val,由于組件的重新渲染,都會觸發(fā)expensive的執(zhí)行(能夠在控制臺看到,即使修改val,也會打印);但是這里的昂貴計算只依賴于count的值,在val修改的時候,是沒有必要再次計算的。在這種情況下,我們就可以使用useMemo,只在count的值修改時,執(zhí)行expensive計算:

export default function WithMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
    const expensive = useMemo(() => {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }, [count]);
 
    return <div>
        <h4>{count}-{expensive}</h4>
        {val}
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}

上面我們可以看到,使用useMemo來執(zhí)行昂貴的計算,然后將計算值返回,并且將count作為依賴值傳遞進去。這樣,就只會在count改變的時候觸發(fā)expensive執(zhí)行,在修改val的時候,返回上一次緩存的值。

useCallback

useCallback跟useMemo比較類似,但它返回的是緩存的函數(shù)。我們看一下最簡單的用法:

const fnA = useCallback(fnB, [a])

import React, { useState, useCallback } from 'react';
 
const set = new Set();
 
export default function Callback() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        console.log(count);
    }, [count]);
    set.add(callback);
 
 
    return <div>
        <h4>{count}</h4>
        <h4>{set.size}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+</button>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}

我們可以看到,每次修改count,set.size就會+1,這說明useCallback依賴變量count,count變更時會返回新的函數(shù);而val變更時,set.size不會變,說明返回的是緩存的舊版本函數(shù)。

知道useCallback有什么樣的特點,那有什么作用呢?

使用場景是:有一個父組件,其中包含子組件,子組件接收一個函數(shù)作為props;通常而言,如果父組件更新了,子組件也會執(zhí)行更新;但是大多數(shù)場景下,更新是沒有必要的,我們可以借助useCallback來返回函數(shù),然后把這個函數(shù)作為props傳遞給子組件;這樣,子組件就能避免不必要的更新。

import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        return count;
    }, [count]);
    return <div>
        <h4>{count}</h4>
        <Child callback={callback}/>
        <div>
            <button onClick={() => setCount(count + 1)}>+</button>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}
 
function Child({ callback }) {
    const [count, setCount] = useState(() => callback());
    useEffect(() => {
        setCount(callback());
    }, [callback]);
    return <div>
        {count}
    </div>
}

不僅是上面的例子,所有依賴本地狀態(tài)或props來創(chuàng)建函數(shù),需要使用到緩存函數(shù)的地方,都是useCallback的應用場景。

多談一點
useEffect、useMemo、useCallback都是自帶閉包的。也就是說,每一次組件的渲染,其都會捕獲當前組件函數(shù)上下文中的狀態(tài)(state, props),所以每一次這三種hooks的執(zhí)行,反映的也都是當前的狀態(tài),你無法使用它們來捕獲上一次的狀態(tài)。對于這種情況,我們應該使用ref來訪問。

參考文章:
阮一峰老師:http://www.ruanyifeng.com/blog/2019/09/react-hooks.html
優(yōu)質(zhì)博客:
https://blog.csdn.net/sinat_17775997/article/details/94453167
http://www.itdecent.cn/p/1252be39c702
http://www.itdecent.cn/p/7e778adec7d1
http://www.itdecent.cn/p/d6244228a427

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • Hooks是 React v16.8 的新特性,可以在不使用類組件的情況下,使用 state 以及其他的React...
    hellomyshadow閱讀 13,652評論 0 5
  • Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他...
    孤獨的小色狼閱讀 391評論 0 0
  • 在學會使用React Hooks之前,可以先看一下相關(guān)原理學習React Hooks 前言 在 React 的世界...
    DC_er閱讀 9,182評論 1 16
  • useState 1.基本使用 等價于 2. 復雜的state 3.使用狀態(tài) 4. 注意事項 1). 如果stat...
    sweetBoy_9126閱讀 3,237評論 0 6
  • 自從Hooks出現(xiàn),函數(shù)式組件(Function Component)的功能在不斷豐富,你很可能已經(jīng)運用Hooks...
    tracyXia閱讀 11,576評論 2 7

友情鏈接更多精彩內(nèi)容