首頁 > 軟體

React-hooks面試考察知識點彙總小結(推薦)

2022-10-06 14:04:17

什麼是hooks?解決了什麼問題?

Hooks 是react16.8新增特性,它可以使用一些state的新特性,簡化邏輯複用,副作用統一資料。

Hooks就是把某個目標結果鉤到某個可能會變化的資料來源或者事件源上,那麼當被鉤到的資料或者事件發生變化時,產生這個目標結果的程式碼會重新執行,產生更新後的結果。

Hook 簡介

Hook出世之前React存在的問題

  • 在元件之間複用狀態邏輯很難

    React 沒有提供將可複用性行為“附加”到元件的途徑(例如,把元件連線到 store)。有一些解決此類問題的方案,比如 render props 和 高階元件。但是這類方案需要重新組織你的元件結構,這可能會很麻煩,使你的程式碼難以理解。

  • 複雜元件變得難以理解

    元件常常在 componentDidMount 和 componentDidUpdate中獲取資料。但是,同一個 componentDidMount 中可能也包含很多其它的邏輯,如設定事件監聽,而之後需在 componentWillUnmount 中清除。相互關聯且需要對照修改的程式碼被進行了拆分,而完全不相關的程式碼卻在同一個方法中組合在一起。如此很容易產生 bug,並且導致邏輯不一致。

  • 難以理解的 class

    class 是學習 React 的一大屏障。你必須去理解 JavaScript 中 this 的工作方式,這與其他語言存在巨大差異。還不能忘記繫結事件處理器。沒有穩定的語法提案,這些程式碼非常冗餘。大家可以很好地理解 props,state 和自頂向下的資料流,但對 class 卻一籌莫展。

Hook帶來的解決方案

  • 你可以使用 Hook 從元件中提取狀態邏輯,使得這些邏輯可以單獨測試並複用。Hook 使你在無需修改元件結構的情況下複用狀態邏輯。
  • Hook 將元件中相互關聯的部分拆分成更小的函數(比如設定訂閱或請求資料),而並非強制按照生命週期劃分。你還可以使用 reducer 來管理元件的內部狀態,使其更加可預測。
  • Hook 使你在非 class 的情況下可以使用更多的 React 特性。 從概念上講,React 元件一直更像是函數。而 Hook 則擁抱了函數,同時也沒有犧牲 React 的精神原則。Hook 提供了問題的解決方案,無需學習複雜的函數式或響應式程式設計技術。

Hook API

useState

useState 是react自帶的一個hook函數,它的作用就是用來宣告狀態變數。useState這個函數接收的引數是我們的狀態初始值(initial state),它返回了一個陣列,這個陣列的第[0]項是當前當前的狀態值,第[1]項是可以改變狀態值的方法函數。

初始化

//返回一個 state,以及更新 state 的函數 setState(接收一個新的 state 值並將元件的一次重新渲染加入佇列)
const [state, setState] = useState(initialState);

函數式更新

//如果新的 state 需要通過使用先前的 state 計算得出,那麼可以將函數傳遞給 setState。該函數將接收先前的 state,並返回一個更新後的值。
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>
    </>
  );
}

惰性初始 state

//如果初始 state 需要通過複雜計算獲得,則可以傳入一個函數,在函數中計算並返回初始的 state,此函數只在初始渲染時被呼叫
const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

跳過 state 更新

呼叫 State Hook 的更新函數並傳入當前的 state 時,React 將跳過子元件的渲染及 effect 的執行。(React 使用 Object.is 比較演演算法 來比較 state。)

useEffect

我們寫的有狀態元件,通常會產生很多的副作用(side effect),比如發起ajax請求獲取資料,新增一些監聽的註冊和取消註冊,手動修改dom等等。我們之前都把這些副作用的函數寫在生命週期函數勾點裡,比如componentDidMountcomponentDidUpdatecomponentWillUnmount。而現在的useEffect就相當與這些宣告周期函數勾點的集合體。它以一抵三。

簡單例子

import { useState, useEffect } from 'react';

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

  // 類似於componentDidMount 和 componentDidUpdate:
  useEffect(() => {
    // 更新檔案的標題
    document.title = `You clicked ${count} times`;
  });

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

清除 effect

通常,元件解除安裝時需要清除 effect 建立的諸如訂閱或計時器 ID 等資源。要實現這一點,useEffect 函數需返回一個清除函數。以下就是一個建立訂閱的例子:

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 清除訂閱
    subscription.unsubscribe();
  };
});

為防止記憶體漏失,清除函數會在元件解除安裝前執行。另外,如果元件多次渲染(通常如此),則在執行下一個 effect 之前,上一個 effect 就已被清除。

effect 的執行時機

componentDidMountcomponentDidUpdate 不同的是,在瀏覽器完成佈局與繪製之後,傳給 useEffect 的函數會延遲呼叫。這使得它適用於許多常見的副作用場景,比如設定訂閱和事件處理等情況,因此不應在函數中執行阻塞瀏覽器更新螢幕的操作。

effect 的條件執行

預設情況下,effect 會在每輪元件渲染完成後執行。這樣的話,一旦 effect 的依賴發生變化,它就會被重新建立。在某些情況下,我們不需要在每次元件更新時都建立新的訂閱,而是僅需要在 source prop 改變時重新建立。要實現這一點,可以給 useEffect 傳遞第二個引數,它是 effect 所依賴的值陣列。

//此時,只有當 props.source 改變後才會重新建立訂閱。(要實現componentDidMount功能只需要設定第二個引數為[]即可)
useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);

useContext

可以深層元件傳值,父元件傳給子孫元件。接收一個 context 物件(React.createContext 的返回值)並返回該 context 的當前值。當前的 context 值由上層元件中距離當前元件最近的 <MyContext.Provider>value prop 決定。

當元件上層最近的 <MyContext.Provider> 更新時,該 Hook 會觸發重渲染,並使用最新傳遞給 MyContext provider 的 context value 值。即使祖先使用 React.memoshouldComponentUpdate,也會在元件本身使用 useContext 時重新渲染

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!    </button>
  );
}

useReducer

useState 的替代方案,可以用於複雜狀態處理。它接收一個形如 (state, action) => newState 的 reducer,並返回當前的 state 以及與其配套的 dispatch 方法。(如果你熟悉 Redux 的話,就已經知道它如何工作了。)參考 前端react面試題詳細解答

指定初始 state

有兩種不同初始化 useReducer state 的方式,你可以根據使用場景選擇其中的一種。將初始 state 作為第二個引數傳入 useReducer 是最簡單的方法:

//nst [state, dispatch] = useReducer(reducer, initialArg, init);
 const [state, dispatch] = useReducer(
    reducer,
    {count: initialCount}
 );

某些場景下,useReducer 會比 useState 更適用,例如 state 邏輯較複雜且包含多個子值,或者下一個 state 依賴於之前的 state 等。並且,使用 useReducer 還能給那些會觸發深更新的元件做效能優化,因為你可以向子元件傳遞 dispatch 而不是回撥函數 。

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

跳過 dispatch

如果 Reducer Hook 的返回值與當前 state 相同,React 將跳過子元件的渲染及副作用的執行。(React 使用 Object.is 比較演演算法 來比較 state。)

useMemo

把“建立”函數和依賴項陣列作為引數傳入 useMemo,它僅會在某個依賴項改變時才重新計算 memoized 值。這種優化有助於避免在每次渲染時都進行高開銷的計算。如果沒有提供依賴項陣列,useMemo 在每次渲染時都會計算新的值。memo是淺比較,意思是,物件只比較記憶體地址,只要你記憶體地址沒變,管你物件裡面的值千變萬化都不會觸發render。

你可以把 useMemo 作為效能優化的手段,但不要把它當成語意上的保證。將來,React 可能會選擇“遺忘”以前的一些 memoized 值,並在下次渲染時重新計算它們,比如為離屏元件釋放記憶體。先編寫在沒有 useMemo 的情況下也可以執行的程式碼 —— 之後再在你的程式碼中新增 useMemo,以達到優化效能的目的。

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

useCallback

把內聯回撥函數及依賴項陣列作為引數傳入 useCallback,它將返回該回撥函數的 memoized 版本,該回撥函數僅在某個依賴項改變時才會更新。當你把回撥函數傳遞給經過優化的並使用參照相等性去避免非必要渲染(例如 shouldComponentUpdate)的子元件時,它將非常有用。

useMemouseCallback 類似,都是有著快取的作用,useMemo 是快取值的,useCallback 是快取函數的。

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

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

useRef

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

useEffect裡面的state的值,是固定的,這個是有辦法解決的,就是用useRef,可以理解成useRef的一個作用:就是相當於全域性作用域,一處被修改,其他地方全更新。

本質上,useRef 就像是可以在其 .current 屬性中儲存一個可變值的“盒子”。你應該熟悉 ref 這一種存取 DOM 的主要方式。如果你將 ref 物件以 <div ref={myRef} /> 形式傳入元件,則無論該節點如何改變,React 都會將 ref 物件的 .current 屬性設定為相應的 DOM 節點。然而,useRef()ref 屬性更有用。它可以很方便地儲存任何可變值,其類似於在 class 中使用範例欄位的方式。

請記住,當 ref 物件內容發生變化時,useRef 並不會通知你。變更 .current 屬性不會引發元件重新渲染。如果想要在 React 繫結或解綁 DOM 節點的 ref 時執行某些程式碼,則需要使用回撥 ref 來實現。

const Hook =()=>{
    const [count, setCount] = useState(0)
    const btnRef = useRef(null)

    useEffect(() => {
        console.log('use effect...')
        const onClick = ()=>{
            setCount(count+1)
        }
        btnRef.current.addEventListener('click',onClick, false)
        return ()=> btnRef.current.removeEventListener('click',onClick, false)
    },[count])

    return(
        <div>
            <div>
                {count}            </div>
            <button ref={btnRef}>click me </button>
        </div>
    )
}

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle 可以讓你在使用 ref 時自定義暴露給父元件的範例值。在大多數情況下,應當避免使用 ref 這樣的命令式程式碼。useImperativeHandle 應當與 forwardRef 一起使用:

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

在本例中,渲染 <FancyInput ref={inputRef} /> 的父元件可以呼叫 inputRef.current.focus()

自定義 Hook

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

例如,下面的 useFriendStatus 是我們第一個自定義的 Hook:

import { 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;
}

自定義一個當resize 的時候 監聽window的width和height的hook

import {useEffect, useState} from "react";

export const useWindowSize = () => {
    const [width, setWidth] = useState()
    const [height, setHeight] = useState()

    useEffect(() => {
        const {clientWidth, clientHeight} = document.documentElement
        setWidth(clientWidth)
        setHeight(clientHeight)
    }, [])

    useEffect(() => {
        const handleWindowSize = () =>{
            const {clientWidth, clientHeight} = document.documentElement
            setWidth(clientWidth)
            setHeight(clientHeight)
        };

        window.addEventListener('resize', handleWindowSize, false)

        return () => {
            window.removeEventListener('resize',handleWindowSize, false)
        }
    })

    return [width, height]
}

使用:

const [width, height] = useWindowSize()
const isOnline = useFriendStatus(id);

 

到此這篇關於React-hooks面試考察知識點彙總的文章就介紹到這了,更多相關React-hooks面試內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com