React Hooks 用法總結


1. useState: 狀態鈎子

基礎用法

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

返回一個 state,以及更新 state 的函數。

在初始渲染期間,返回的狀態 (state) 與傳入的第一個參數 (initialState) 值相同。

setState 函數用於更新 state。它接收一個新的 state 值並將組件的一次重新渲染加入隊列。

函數式更新

如果新的 state 需要通過使用先前的 state 計算得出,那么可以將函數傳遞給 setState。該函數將接收先前的 state,並返回一個更新后的值。下面的計數器組件示例展示了 setState 的兩種用法:

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>
    </>
  );
}

function Parent() {
  return <Counter initialCount={1} />;
}

export default Parent;

注意

與 class 組件中的 setState 方法不同,useState 不會自動合並更新對象。你可以用函數式的 setState 結合展開運算符來達到合並更新對象的效果。

setState(prevState => { 
  return {...prevState, ...updatedValues};
});

惰性初始 state

initialState 參數只會在組件的初始渲染中起作用,后續渲染時會被忽略。如果初始 state 需要通過復雜計算獲得,則可以傳入一個函數,在函數中計算並返回初始的 state,此函數只在初始渲染時被調用:

const getInitState = () => {
    const initialState = someExpensiveComputation(props);
    return initialState;
}

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

2. useEffect():副作用鈎子

用法

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

useEffect(()  =>  {
  // Async Action
}, [dependencies])
  • 上面用法中,useEffect()接受兩個參數。第一個參數是一個函數,異步操作的代碼放在里面。第二個參數是一個數組,用於給出 Effect 的依賴項,只要這個數組發生變化,useEffect()就會執行。
  • 第二個參數可以省略,這時每次組件渲染時,就會執行useEffect()。
  • 如果想執行只運行一次的 effect(僅在組件掛載和卸載時執行),可以傳遞一個空數組([])作為第二個參數。這就告訴 React 你的 effect 不依賴於 props 或 state 中的任何值,所以它永遠都不需要重復執行。

舉例

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

  useEffect(() => {
    setLoading(true); 
    fetch(`/api/url`) 
      .then(data => {
        setPerson(data);
        setLoading(false);
      });
  }, [personId])

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

  return (
      <div>
        <p>{person.name}</p> 
      </div>
    )
}

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

3. useContext():共享狀態鈎子

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

現在有兩個組件 Navbar 和 Messages,我們希望它們之間共享狀態。

<div className="App">
  <Navbar/>
  <Messages/>
</div>

第一步就是使用 React Context API,在組件外部建立一個 Context。

const AppContext = React.createContext({});

組件封裝代碼如下。

<AppContext.Provider value={{
  username: 'superawesome'
}}>
  <div className="App">
    <Navbar/>
    <Messages/>
  </div>
</AppContext.Provider>

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

Navbar 組件的代碼如下。

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

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

Message 組件的代碼也類似。

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

  return (
    <div className="messages">
      <h1>Messages</h1>
      <p>1 message for {username}</p>
      <p className="message">useContext is awesome!</p>
    </div>
  )
}

4. useReducer():action 鈎子

用法

useReducer 可以作為 useState 的替代方案。它接收一個形如 (state, action) => newState 的 reducer,並返回當前的 state 以及與其配套的 dispatch 方法。

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

以下是用 reducer 重寫 useState 一節的計數器示例:

const initialState = {count: 0};

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

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

惰性初始化

可以選擇惰性地創建初始 state。為此,需要將 init 函數作為 useReducer 的第三個參數傳入,這樣初始 state 將被設置為 init(initialArg)。

這么做可以將用於計算 state 的邏輯提取到 reducer 外部,這也為將來對重置 state 的 action 做處理提供了便利:

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>
    </>
  );
}

5. useMemo()

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

把“創建”函數和依賴項數組作為參數傳入 useMemo,它僅會在某個依賴項改變時才重新計算 memoized 值。這種優化有助於避免在每次渲染時都進行高開銷的計算。

例如:

const Child = memo(({ data }) => {
  console.log("子組件 render...", data.name);

  return (
    <div>
      <div>child</div>
      <div>{data.name}</div>
    </div>
  );
});

const Parent = () => {
  console.log("父組件 render...");
  const [count, setCount] = useState(0);
  const [name, setName] = useState("haha");

  const data = {
    name,
  }; 

  return (
    <div>
      <div>{count}</div>
      <button onClick={() => setCount(count + 1)}> 更新計數+1 </button>
      <Child data={data} />
    </div>
  );
};
export default Parent;

當點擊按鈕更新count的時候,Parent組件會render,一旦render, 執行到這一行代碼:

  const data = {
    name,
  }; 

這一行代碼會生成有新的內存地址的對象,那么就算帶着memo的Child組件,也會跟着重新render, 盡管最后其實Child使用到的值沒有改變。

這樣Child就多余render了,造成性能浪費,於是useMemo 作為一個有着暫存能力的鈎子,就派上用場了。

const Child = memo(({ data }) => {
  console.log("子組件 render...", data.name);

  return (
    <div>
      <div>child</div>
      <div>{data.name}</div>
    </div>
  );
});

const Parent = () => {
  console.log("父組件 render...");
  const [count, setCount] = useState(0);
  const [name, setName] = useState("haha");
  
  const data = useMemo(() => {
    return {
      name,
    };
  }, [name]);

  return (
    <div>
      <div>{count}</div>
      <button onClick={() => setCount(count + 1)}> 更新計數+1 </button>
      <Child data={data} />
    </div>
  );
};

當執行到代碼:

  const data = useMemo(() => {
    return {
      name,
    };
  }, [name]);

會先根據[name]里面的name值判斷一下,因為useMemo 作為一個有着暫存能力的,暫存了上一次的name結果。如果name值沒有變化,那么data就不會重新賦值,沒有新的對象,就沒有新的內存地址,所以 Child 就不會重新render了。

6. useCallback

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

useCallback 和 useMemo 都可緩存函數的引用或值,但是從更細的使用角度來說 useCallback 緩存函數的引用,useMemo 緩存計算數據的值。

useCallback(fn, deps) 相當於 useMemo(() => fn, deps)

7. useRef

const refContainer = useRef(initialValue);

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

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>
    </>
  );
}

參考:http://www.ruanyifeng.com/blog/2019/09/react-hooks.html; https://juejin.im/post/5e53d9116fb9a07c9070da44#heading-9


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM