React Hooks

介紹 於react 16.8版本引入,主要功能在於讓你無需建立一個類定義,便可使用 state等react特性。

是什麼

Hook 是一些可讓你在==函數組件==裏「鉤入」 React state 及生命週期等特性的函數。Hook 不能在 class 組件中使用 —— 這使得你不使用 class 也能使用 React。html

爲何使用hooks

  1. 邏輯代碼難以在組件間複用react

    • render propsnpm

      • 須要從新組織代碼結構,使得代碼更加定製化,難以維護。
    • higher-order components
  2. 非class模式下,使用更多的react特性編程

    • Hook 將組件中相互關聯的部分拆分紅更小的函數(好比設置訂閱或請求數據)

優點

  1. 沒有破壞性改動數組

    • 向後兼容
    • 可選
  2. 函數式編程

API

useState

函數聲明

useState:< S >(initialState: S | (() => S))=> [S, Dispatch<SetStateAction< S >>];瀏覽器

  • SetStateAction< S > = S | ((prevState: S) => S);
  • 複用邏輯,不復用數據,這意味着你在多處使用使用了useState的組件,獲取的數據並非同一個,全部 state 和反作用都是徹底隔離的。
  • 調用Dispatch<SetStateAction< S >>會觸發函數組件的從新渲染。

useEffect

函數聲明

useEffect:(effect: ()=>?()=>void, deps?: DependencyList)=> void;緩存

  • 組件銷燬後,會調用effect返回的清潔函數(若是有)來取消反作用(好比訂閱,計時器等)
  • deps標識effect所依賴的值數組。若爲空數組[],則僅在第一次渲染後調用,並在卸載前銷燬。(此時更相似componentDidMountcomponentWillUnmount
  • 每次渲染後調用,==包括==第一次(React類生命週期中,componentDidMountcomponentDidUpdate的合集)性能優化

    • 爲何須要在每一次更新後執行,參考以下代碼
    componentDidMount() {
            ChatAPI.subscribeToFriendStatus(
              this.props.friend.id,
              this.handleStatusChange
            );
          }
        
          componentWillUnmount() {
            ChatAPI.unsubscribeFromFriendStatus(
              this.props.friend.id,
              this.handleStatusChange
            );
          }
          //假如在在組件展現在屏幕上時,friend.id變化了。此時組件沒有正常取消原來的訂閱邏輯,同時在取消訂閱時傳遞了錯誤的好友id,可能致使一些bug
    • 若是在class組件中
    //需添加 componentDidUpdate 來解決這個問題
    componentDidUpdate(prevProps) {
        if(prevPropsfriend.id !== this.props.friend.id){
            // 取消訂閱以前的 friend.id
            ChatAPI.unsubscribeFromFriendStatus(
              prevProps.friend.id,
              this.handleStatusChange
            );
            // 訂閱新的 friend.id
            ChatAPI.subscribeToFriendStatus(
              this.props.friend.id,
              this.handleStatusChange
            );
        }
    }
    • 使用Hook的函數組件
    function FriendStatus(props) {
      // ...
      useEffect(() => {
        // ...
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
      },[props.friend.id]);//僅在friend.id改變時更新
  • 傳遞給 useEffect 的函數在每次渲染中都會有所不一樣,這是刻意爲之的。事實上這正是咱們能夠在 effect 中獲取最新的的值,而不用擔憂其過時的緣由。每次咱們從新渲染,都會生成新的 effect,替換掉以前的。某種意義上講,effect 更像是渲染結果的一部分 —— 每一個 effect 「屬於」一次特定的渲染。閉包

    • 這裏使用匿名函數做爲參數傳遞給effect
  • useEffect會在每次瀏覽器繪製後,且下一次繪製前執行函數式編程

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

useMemo

函數聲明

useMemo<T>(factory: () => T, deps: DependencyList | undefined)=> T;

  • 保存上一次的計算結果,deps數組內依賴項改變時計算memoized
  • 傳入 useMemo 的函數會在渲染期間執行,可用於緩存子節點的渲染結果。
const Button = React.memo((props) => {
  // 你的組件
});

useCallback

函數聲明

useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;

  • useCallback(fn, deps) 至關於 useMemo(() => fn, deps)。
  • 可用於避免匿名函數形成的重複渲染。
function MyComponent(props) {
    const clickCallback = React.useCallback(() => {
        // ...
    }, []);
    // 這裏若是直接傳遞匿名函數,會形成每次渲染結果與上一次不一致
    // 在這個例子中:MyComponent會從新渲染,但button不會
    return <button onClick={clickCallback}>Click Me!</button>;
}

useRef

函數聲明

useRef<T>(initialValue: T)=> MutableRefObject<T>;

  • useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化爲傳入的參數(initialValue)

    • useRef() 和自建一個 {current: ...} 對象的惟一區別是,useRef 會在每次渲染時返回同一個 ref 對象。
    • 本質上,useRef 就像是能夠在其 .current 屬性中保存一個可變值的「盒子」。因爲每個函數組件都是一個閉包,useRef實現了一套穿透閉包的邏輯。
  • 若是你將 ref 對象以 <div ref={myRef} /> 形式傳入組件,則不管該節點如何改變,React 都會將 ref 對象的 .current 屬性設置爲相應的 DOM 節點。
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>
    </>
  );
}
  • 能夠用於獲取上一輪渲染的props或者state(prePorpspreState)。
function Counter() {
  const [count, setCount] = useState(0);

  const prevCountRef = useRef();
  useEffect(() => {
    prevCountRef.current = count;
  });
  const prevCount = prevCountRef.current;

  return <h1>Now: {count}, before: {prevCount}</h1>;
}

// 或者使用自定義hook
function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  return <h1>Now: {count}, before: {prevCount}</h1>;
}

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

React 是如何把對 Hook 的調用和組件聯繫起來的?

  • React 保持對當先渲染中的組件的追蹤。多虧了 Hook 規範,咱們得知 Hook 只會在 React 組件中被調用(或自定義 Hook —— 一樣只會在 React 組件中被調用)。
  • 每一個組件內部都有一個「記憶單元格」列表。它們只不過是咱們用來存儲一些數據的 JavaScript 對象。當你用 useState() 調用一個 Hook 的時候,它會讀取當前的單元格(或在首次渲染時將其初始化),而後把指針移動到下一個。這就是多個 useState() 調用會獲得各自獨立的本地 state 的緣由。
  • React依賴Hook調用的順序來確保stateuseState的對應關係。

邏輯共享

按照官方描述,這個是 自定義Hook
  • 自定義 Hook 是一個函數,其名稱必須以「use」 開頭,函數內部能夠調用其餘的 Hook。

    • 不然React沒法判斷某個函數是否包含對其內部 Hook 的調用,React 將沒法自動檢查你的 Hook 是否違反了Hook 的規則。
import React, { useState, useEffect } from 'react';

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

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

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}
//第一處複用
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
//第二處複用
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

注意

  1. 只能在函數最外層調用Hook。不要在循環、條件判斷或者子函數中調用。
  2. 只能在 React 的函數組件中調用 Hook。不要在其餘 JavaScript 函數中調用。(還有一個地方能夠調用 Hook —— 就是自定義的 Hook 中)
  3. useCallbackuseMemo主要用於性能優化,不要過早進行性能優化。不然沒法比較優化結果,極可能某個不注意的角落反而會致使性能下降。
  4. eslint-linter 插件用於匹配以上規則
  5. 與 class 組件中的 setState 方法不一樣,useState 不會自動合併更新對象。你能夠用函數式的 setState 結合展開運算符來達到合併更新對象的效果。

    setState(prevState => {
      // 也可使用 Object.assign
      return {...prevState, ...updatedValues};
    });
相關文章
相關標籤/搜索