天天看點

React學習筆記 —— Hook

什麼是 Hook?

Hook 是一些可以讓你在函數元件裡“鈎入” React state 及生命周期等特性的函數。Hook 不能在 class 元件中使用 —— 這使得你不使用 class 也能使用 React特性。

Hook 和函數元件

函數元件又稱為“無狀态元件”

const Example = (props) => {
  // 你可以在這使用 Hook
  return <div />;
}
           
function Example(props) {
  // 你可以在這使用 Hook
  return <div />;
}
           

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

class 中的

this.setState:更新 state 變量替換

Hook中的

useState:更新 state 變量合并

State Hook

const HookStudy = () =>{
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
           

等價于

class HookStudy 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>
    );
  }
           

調用 useState 方法:定義一個 “state 變量”。示例中變量叫 count。這是一種在函數調用時儲存變量的方式。

useState 是一種新方法,它與 class 裡面的 this.state 提供的功能完全相同。一般來說,在函數退出後變量就會”消失”,而 state 中的變量會被 React 保留。

useState 需要的參數: useState() 方法裡面唯一的參數就是初始 state。不同于 class 的是,可以按照需要使用數字或字元串對其進行指派,而不一定是對象。在示例中,隻需使用數字來記錄使用者點選次數,是以傳了 0 作為變量的初始 state。(如想要在 state 中存儲兩個不同的變量,隻需調用 useState() 兩次即可。)

useState 方法的傳回值:目前 state 以及更新 state 的函數。這與 class 裡面 this.state.count 和 this.setState 類似,唯一差別就是需要成對的擷取它們。數組結構。

let fruitStateVariable = useState('banana'); // 傳回一個有兩個元素的數組
let fruit = fruitStateVariable[0]; // 數組裡的第一個值
let setFruit = fruitStateVariable[1]; // 數組裡的第二個值
           

Effect Hook

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

在 React 更新 DOM 之後運作一些額外的代碼

const HookStudy = () =>{
  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>
  );
}
           

等價于

class HookStudy 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>
    );
  }
}
           

useEffect 做了什麼: 通過使用這個 Hook,可以讓 React 元件需要在渲染後執行某些操作。React 會儲存傳遞的函數(稱為 “effect”),并且在執行 DOM 更新之後調用它。在這個 effect 中,示例設定了 document 的 title 屬性,也可以執行資料擷取或調用其他指令式的 API。

為什麼在元件内部調用 useEffect? 将 useEffect 放在元件内部可以在 effect 中直接通路 count state 變量(或其他 props)。不需要特殊的 API 來讀取它 —— 它已經儲存在函數作用域中。Hook 使用了 JavaScript 的閉包機制,而不用在 JavaScript 已經提供了解決方案的情況下,還引入特定的 React API。

useEffect 會在每次渲染後都執行嗎? 是的,預設情況下,它在第一次渲染之後和每次更新之後都會執行。不用再考慮“挂載”還是“更新”。React 保證了每次運作 effect 的同時,DOM 都已經更新完畢。

清除的 effect

在某些情況下清除工作是非常重要的,可以防止引起記憶體洩露!

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';
}
           

等價于

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }
  componentDidMount() {    ChatAPI.subscribeToFriendStatus(      this.props.friend.id,      this.handleStatusChange    );  }  componentWillUnmount() {    ChatAPI.unsubscribeFromFriendStatus(      this.props.friend.id,      this.handleStatusChange    );  }  handleStatusChange(status) {    this.setState({      isOnline: status.isOnline    });  }
  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}
           

為什麼在 effect 中傳回一個函數: 這是 effect 可選的清除機制。每個 effect 都可以傳回一個清除函數。如此可以将添加和移除訂閱的邏輯放在一起。它們都屬于 effect 的一部分。

React 何時清除 effect: React 會在元件解除安裝的時候執行清除操作。

聲明多個effect

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

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

React 将按照 effect 聲明的順序依次調用元件中的每一個 effect

跳過 Effect 進行性能優化

Effect 在 每次更新之後都會執行,因某些需求需要,可以通過傳遞數組作為 useEffect 的第二個可選參數,達到特定情況下進行更新

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

如果執行隻運作一次的 effect(僅在元件挂載和解除安裝時執行),可以傳遞一個空數組([])作為第二個參數。使 React 的 effect 不依賴于 props 或 state 中的任何值,它永遠都不需要重複執行。

(遵循依賴數組的工作方式)

自定義 Hook

自定義 Hook 必須以 “use” 開頭。這個約定非常重要。不遵循的話,由于無法判斷某個函數是否包含對其内部 Hook 的調用,React 将無法自動檢查你的 Hook 是否違反了 Hook 的規則。

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

  // ...

  return isOnline;
}
           
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
           
const friendList = [
  { id: 1, name: 'Phoebe' },
  { id: 2, name: 'Rachel' },
  { id: 3, name: 'Ross' },
];

function ChatRecipientPicker() {
  const [recipientID, setRecipientID] = useState(1);  const isRecipientOnline = useFriendStatus(recipientID);
  return (
    <>
      <Circle color={isRecipientOnline ? 'green' : 'red'} />      <select
        value={recipientID}
        onChange={e => setRecipientID(Number(e.target.value))}
      >
        {friendList.map(friend => (
          <option key={friend.id} value={friend.id}>
            {friend.name}
          </option>
        ))}
      </select>
    </>
  );
}
           

自定義 Hook 是一種重用狀态邏輯的機制(例如設定為訂閱并存儲目前值),是以每次使用自定義 Hook 時,其中的所有 state 和副作用都是完全隔離的。

自定義 Hook 是一種自然遵循 Hook 設計的約定,而并不是 React 的特性。

結合reducer方式的Hook

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }

  return [state, dispatch];
}
           

元件中使用

function Todos() {
  const [todos, dispatch] = useReducer(todosReducer, []);
  function handleAddClick(text) {
    dispatch({ type: 'add', text });
  }

  // ...
}
           

參考資料 https://react.docschina.org/docs/hooks-intro.html