React Hooks 知識點總結

Hook 簡介

Hook出世以前React存在的問題

  1. 在組件之間複用狀態邏輯很難html

    React 沒有提供將可複用性行爲「附加」到組件的途徑(例如,把組件鏈接到 store)。有一些解決此類問題的方案,好比 render props高階組件。可是這類方案須要從新組織你的組件結構,這可能會很麻煩,使你的代碼難以理解。node

  2. 複雜組件變得難以理解react

    組件經常在 componentDidMountcomponentDidUpdate中獲取數據。可是,同一個 componentDidMount 中可能也包含不少其它的邏輯,如設置事件監聽,而以後需在 componentWillUnmount 中清除。相互關聯且須要對照修改的代碼被進行了拆分,而徹底不相關的代碼卻在同一個方法中組合在一塊兒。如此很容易產生 bug,而且致使邏輯不一致。ajax

  3. 難以理解的 class算法

    class 是學習 React 的一大屏障。你必須去理解 JavaScript 中 this 的工做方式,這與其餘語言存在巨大差別。還不能忘記綁定事件處理器。沒有穩定的語法提案,這些代碼很是冗餘。你們能夠很好地理解 props,state 和自頂向下的數據流,但對 class 卻束手無策。編程

Hook帶來的解決方案

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

Hook API

useState

useState 是react自帶的一個hook函數,它的做用就是用來聲明狀態變量。useState這個函數接收的參數是咱們的狀態初始值(initial state),它返回了一個數組,這個數組的第[0]項是當前當前的狀態值,第[1]項是能夠改變狀態值的方法函數。segmentfault

初始化

//返回一個 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。)api

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 的話,就已經知道它如何工做了。)

指定初始 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>
    )
}
複製代碼

自定義 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);
複製代碼

參考文章

相關文章
相關標籤/搜索