React使用hook


Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。

為什么會有hook

  • 在組件之間復用狀態邏輯很難,需要重新組織你的組件結構,抽象層組成的組件會形成“嵌套地獄”
  • 復雜組件變得難以理解,各生命周期交叉副作用

State Hook

import React, { useState } from 'react';        // 引入

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

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

Hook 在 class 內部是不起作用的。但你可以使用它們來取代 class 。

useState 需要哪些參數?

useState() 方法里面唯一的參數就是初始 state。不同於 class 的是,我們可以按照需要使用數字或字符串對其進行賦值,而不一定是對象。在示例中,只需使用數字來記錄用戶點擊次數,所以我們傳了 0 作為變量的初始 state。(如果我們想要在 state 中存儲兩個不同的變量,只需調用 useState() 兩次即可。)

useState 方法的返回值是什么?

返回值為:當前 state 以及更新 state 的函數。這就是我們寫 const [count, setCount] = useState() 的原因。這與 class 里面 this.state.count 和 this.setState 類似,唯一區別就是你需要成對的獲取它們。

我們聲明了一個叫 count 的 state 變量,然后把它設為 0。React 會在重復渲染時記住它當前的值,並且提供最新的值給我們的函數。我們可以通過調用 setCount 來更新當前的 count

讀取 State

當我們想在 class 中顯示當前的 count,我們讀取 this.state.count:

<p>You clicked {this.state.count} times</p>

在函數中,我們可以直接用 count:

<p>You clicked {count} times</p>

更新 State

在 class 中,我們需要調用 this.setState() 來更新 count 值:

<button onClick={() => this.setState({ count: this.state.count + 1 })}>
    Click me
</button>

在函數中,我們已經有了 setCount 和 count 變量,所以我們不需要 this:

<button onClick={() => setCount(count + 1)}>
    Click me
</button>

使用多個 state 變量

function ExampleWithManyStates() {
  // 聲明多個 state 變量
    const [age, setAge] = useState(42);
    const [fruit, setFruit] = useState('banana');
    const [todos, setTodos] = useState([{ text: '學習 Hook' }]);

在以上組件中,我們有局部變量 age,fruit 和 todos,並且我們可以單獨更新它們:

function handleOrangeClick() {
    // 和 this.setState({ fruit: 'orange' }) 類似
    setFruit('orange');
}

Effect Hook

Effect Hook 可以讓你在函數組件中執行副作用操作

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

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

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

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

可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 這三個函數的組合。

使用 class 的示例

class Example extends React.Component {
  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>
    );
  }
}

注意,在這個 class 中,我們需要在兩個生命周期函數中編寫重復的代碼。

使用 Hook 的示例

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

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

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

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

為什么在組件內部調用 useEffect?

將 useEffect 放在組件內部讓我們可以在 effect 中直接訪問 count state 變量(或其他 props)。我們不需要特殊的 API 來讀取它 —— 它已經保存函數作用域中。Hook 使用了 JavaScript 的閉包機制,而不用在 JavaScript 已經提供了解決方案的情況下,還引入特定的 React API。

useEffect 會在每次渲染后都執行嗎?

是的,默認情況下,它在第一次渲染之后和每次更新之后都會執行。

與 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 調度的 effect 不會阻塞瀏覽器更新屏幕,這讓你的應用看起來響應更快。大多數情況下,effect 不需要同步地執行。在個別情況下(例如測量布局),有單獨的 useLayoutEffect Hook 供你使用,其 API 與 useEffect 相同。

需要清除的 effect

在 React class 中,你通常會在 componentDidMount 中設置訂閱,並在 componentWillUnmount清除它。

使用 Hook 的示例

你可能認為需要單獨的 effect 來執行清除操作。但由於添加和刪除訂閱的代碼的緊密性,所以 useEffect 的設計是在同一個地方執行。如果你的 effect 返回一個函數,React 將會在執行清除操作時調用它:

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

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

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

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

為什么要在 effect 中返回一個函數?

這是 effect 可選的清除機制。每個 effect 都可以返回一個清除函數。如此可以將添加和移除訂閱的邏輯放在一起。它們都屬於 effect 的一部分。

React 何時清除 effect?

React 會在組件卸載的時候執行清除操作。正如之前學到的,effect 在每次渲染的時候都會執行。這就是為什么 React 會在執行當前 effect 之前對上一個 effect 進行清除。

並不是必須為 effect 中返回的函數命名。這里我們將其命名為 cleanup 是為了表明此函數的目的,但其實也可以返回一個箭頭函數或者給起一個別的名字。

React.useEffect(() => {
    const handler = () => {
      const width = `calc(100% - 80)`;
      setWidth(width);
    };
    window.addEventListener('resize', handler);
    return () => window.removeEventListener('resize', handler);
}, [1]);

提示: 通過跳過 Effect 進行性能優化

在某些情況下,每次渲染后都執行清理或者執行 effect 可能會導致性能問題。在 class 組件中,我們可以通過在 componentDidUpdate 中添加對 prevProps 或 prevState 的比較邏輯解決:

如果某些特定值在兩次重渲染之間沒有發生變化,你可以通知 React 跳過對 effect 的調用,只要傳遞數組作為 useEffect 的第二個可選參數即可:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 僅在 count 更改時更新

如果 count 的值是 5,而且我們的組件重渲染的時候 count 還是等於 5,React 將對前一次渲染的 [5] 和后一次渲染的 [5] 進行比較。因為數組中的所有元素都是相等的(5 === 5),React 會跳過這個 effect,這就實現了性能的優化。

對於有清除操作的 effect 同樣適用:

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

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // 僅在 props.friend.id 發生變化時,重新訂閱

如果想執行只運行一次的 effect(僅在組件掛載和卸載時執行),可以傳遞一個空數組([])

Hook 規則

  • 只在最頂層使用 Hook
    (不要在循環,條件或嵌套函數中調用 Hook, 確保總是在你的 React 函數的最頂層調用他們。)
  • 只在 React 函數中調用 Hook
 // 🔴 在條件語句中使用 Hook 違反第一條規則
  if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
  }

ESLint 插件

npm install eslint-plugin-react-hooks --save-dev
// 你的 ESLint 配置
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // 檢查 Hook 的規則
    "react-hooks/exhaustive-deps": "warn" // 檢查 effect 的依賴
  }
}

可以在單個組件中使用多個 State Hook 或 Effect Hook

function Form() {
  // 1. Use the name state variable
  const [name, setName] = useState('Mary');

  // 2. Use an effect for persisting the form
  useEffect(function persistForm() {
    localStorage.setItem('formData', name);
  });

  // 3. Use the surname state variable
  const [surname, setSurname] = useState('Poppins');

  // 4. Use an effect for updating the title
  useEffect(function updateTitle() {
    document.title = name + ' ' + surname;
  });

  // ...
}

提取自定義 Hook

目前為止,在 React 中有兩種流行的方式來共享組件之間的狀態邏輯: render props 和高階組件,現在讓我們來看看 Hook 是如何在讓你不增加組件的情況下解決相同問題的。

當我們想在兩個函數之間共享邏輯時,我們會把它提取到第三個函數中。而組件和 Hook 都是函數,所以也同樣適用這種方式。

自定義 Hook 是一個函數,其名稱以 “use” 開頭,函數內部可以調用其他的 Hook。

注意:

  • 自定義 Hook 必須以 “use” 開頭
  • 在兩個組件中使用相同的 Hook 不會 共享 state
  • 自定義 Hook 每次調用 Hook,它都會獲取獨立的 state
// 使用該語法糖則要求為class
// @connect(({ hooks }) => ({
//   modelMsg:hooks.modelMsg,
// }))


// hook時使用該種方法引用props
export default connect(({ hooks }) => ({
  modelMsg:hooks.modelMsg,
}))(HookProps);

本文只是對hook的一些基礎進行記錄,更多屬性和方法可參考官方文檔:https://react.docschina.org/docs/hooks-intro.html


免責聲明!

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



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