React Hooks

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

問題

react 現(xiàn)在面臨的問題
  • 在組件之間復(fù)用狀態(tài)邏輯很難
  • 復(fù)雜組件變得難以理解
  • 難以理解的 class

漸進(jìn)策略

沒有計(jì)劃從 React 中移除 class, 最重要的是,Hook 和現(xiàn)有代碼可以同時(shí)工作,你可以漸進(jìn)式地使用他們。

Hook 全家福

Basic Hooks

  • useState
  • useEffect
  • useContext

自定義 Hook

Additional Hooks

  • useReducer
  • useCallback
  • useMemo
  • useRef
  • useImperativeHandle
  • useLayoutEffect
  • useDebugValue

Hook 使用規(guī)則

Hook 就是 JavaScript 函數(shù),但是使用它們會(huì)有兩個(gè)額外的規(guī)則:

  • 只能在函數(shù)最外層調(diào)用 Hook。不要在循環(huán)、條件判斷或者子函數(shù)中調(diào)用。
  • 只能在 React 的函數(shù)組件中調(diào)用Hook。不要在其他 JavaScript 函數(shù)中調(diào)用(自定義 Hook 除外)。
    ESLint 插件 可以用來強(qiáng)制執(zhí)行這兩條規(guī)則
npm install eslint-plugin-react-hooks
// 你的 ESLint 配置
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // 檢查 Hook 的規(guī)則
    "react-hooks/exhaustive-deps": "warn" // 檢查 effect 的依賴
  }
}

useState

通過在函數(shù)組件里調(diào)用它來給組件添加一些內(nèi)部 state。React 會(huì)在重復(fù)渲染時(shí)保留這個(gè) state。

import React, { useState } from 'react';

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

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

useState 會(huì)返回一對(duì)值: 當(dāng)前的狀態(tài)和更新這個(gè)狀態(tài)的函數(shù)。
更新狀態(tài)的函數(shù)就類似 class 組件setState方法, 但是更新新狀態(tài)的函數(shù)不會(huì)將新的 state舊的 state 合并, 而是直接替換舊的 state
入?yún)?/em> : 是作為 state 的初始值, 只在第一次渲染的時(shí)候用到??梢允?number, boolean, string, object 的值, 如果初始值需要而外的計(jì)算也可以是一個(gè) function。
出參 : [state, setState] state => 當(dāng)前的狀態(tài); setState => 更改這個(gè)狀態(tài)的函數(shù)。


useEffect

useEffect 就是一個(gè) Effect Hook,給函數(shù)組件增加了操作副作用的能力。它跟 class 組件中的 componentDidMount、componentDidUpdatecomponentWillUnmount 具有相同的用途,只不過被合并成了一個(gè) API。

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

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

  // 相當(dāng)于 componentDidMount 和 componentDidUpdate:
  useEffect(() => {
    // 使用瀏覽器的 API 更新頁面標(biāo)題
    document.title = `You clicked ${count} times`;
  });

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

當(dāng)你調(diào)用 useEffect 時(shí),就是在告訴 React 在完成對(duì) DOM 的更改后運(yùn)行你的“副作用”函數(shù)。由于副作用函數(shù)是在組件內(nèi)聲明的,所以它們可以訪問到組件的 propsstate。

useEffect 會(huì)在每次渲染后都執(zhí)行嗎?

是的, 默認(rèn)情況下,React 會(huì)在每次渲染后調(diào)用副作用函數(shù) —— 包括第一次渲染的時(shí)候。 React 保證了每次運(yùn)行 effect 的時(shí),DOM 都已經(jīng)更新完畢。
傳遞給 useEffect 的函數(shù)在每次渲染中都會(huì)有所不同,這是刻意為之的。每次我們重新渲染,都會(huì)生成新的 effect,替換掉之前的。某種意義上講,effect 更像是渲染結(jié)果的一部分 —— 每個(gè) effect “屬于”一次特定的渲染。

useEffect(()=>{
  ... // 要做的事
  return () => {} // 清除操作
}, [依賴] )

為什么要在 effect 中返回一個(gè)函數(shù)?

這是 effect 可選的清除機(jī)制。每個(gè) effect 都可以返回一個(gè)清除函數(shù)。如此可以將添加和移除訂閱的邏輯放在一起。它們都屬于 effect 的一部分。

React 何時(shí)清除 effect?

React 會(huì)在組件卸載的時(shí)候執(zhí)行清除操作。正如之前學(xué)到的,effect 在每次渲染的時(shí)候都會(huì)執(zhí)行。這就是為什么 React 會(huì)在執(zhí)行當(dāng)前 effect 之前對(duì)上一個(gè) effect 進(jìn)行清除。

通過跳過 Effect 進(jìn)行性能優(yōu)化

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

我們出入的第二個(gè)參數(shù)[count], 如果 count 的值是5, 下一次的值還是5, react會(huì)對(duì)這兩次的值進(jìn)行比較, 如果發(fā)現(xiàn)是相等的, 就會(huì)跳過這個(gè) effect, 否則就會(huì)執(zhí)行。
知識(shí)點(diǎn): 如果你傳入了一個(gè)空數(shù)組([]),effect 內(nèi)部的 propsstate 就會(huì)一直擁有其初始值。盡管傳入 [] 作為第二個(gè)參數(shù)更接近大家更熟悉的 componentDidMountcomponentWillUnmount 思維模式,但我們有更好的來避免過于頻繁的重復(fù)調(diào)用 effect。


自定義 Hook

通過自定義 Hook,可以將組件邏輯提取到可重用的函數(shù)中。

// 提前公用邏輯到自定義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;
}
// 使用自定義 Hook
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

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

知識(shí)點(diǎn): 自定義 Hook 是一個(gè)函數(shù),其名稱以 “use” 開頭,函數(shù)內(nèi)部可以調(diào)用其他的 Hook。

在兩個(gè)組件中使用相同的 Hook 會(huì)共享 state 嗎?

不會(huì)。自定義 Hook 是一種重用狀態(tài)邏輯的機(jī)制(例如設(shè)置為訂閱并存儲(chǔ)當(dāng)前值),所以每次使用自定義 Hook 時(shí),其中的所有 state 和副作用都是完全隔離的。


useContext

useContext(MyContext)等同于類中的靜態(tài)contextType = MyContext,或者等同于<MyContext.Consumer>
useContext(MyContext)只允許您讀取上下文并訂閱其更改。您仍然需要樹中的<MyContext.Provider>來提供此上下文的值


useReducer

useState的替代方案。接受類型為(state,action)=> newStatereducer,并返回與dispatch方法配對(duì)的當(dāng)前狀態(tài)。(參考 redux)

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({initialState}) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

初始值也可以延遲初始化, useReducer(reducer, initialCount, init), init 是一個(gè)函數(shù), 初始值將設(shè)置為init(initialArg)
如果從Reducer Hook返回與當(dāng)前狀態(tài)相同的值,則React將退出而不渲染子項(xiàng)或觸發(fā)效果。 (React使用Object.is比較算法。)


useCallback

useCallback將返回一個(gè)回調(diào)的memoized(一種優(yōu)化手段,遇到計(jì)算開銷很大的函數(shù)時(shí),會(huì)緩存其計(jì)算結(jié)果,下次同樣的輸入就可以直接返回緩存的結(jié)果)版本,該版本僅在其中一個(gè)依賴項(xiàng)發(fā)生更改時(shí)才會(huì)更改。

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

返回的就是一個(gè)memoized版本的 callback


useMemo

useMemo只會(huì)在其中一個(gè)依賴項(xiàng)發(fā)生更改時(shí)重新計(jì)算memoized值。此優(yōu)化有助于避免在每個(gè)渲染上進(jìn)行昂貴的計(jì)算

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

返回的就是一個(gè)memoized版本的值
useCallback(fn, deps) 相當(dāng)于a useMemo(() => fn, deps).
請(qǐng)記住,傳遞給useMemo的函數(shù)在渲染期間運(yùn)行。不要做那些在渲染時(shí)通常不會(huì)做的事情。例如,副作用應(yīng)該用useEffect,而不是useMemo


useRef

useRef返回一個(gè)可變的ref對(duì)象,其.current屬性被初始化為傳遞的參數(shù)(initialValue)。返回的對(duì)象將持續(xù)整個(gè)組件的生命周期

const refContainer = useRef(initialValue);

useRef()ref屬性更有用。保持任何可變值的方法類似于在類中使用實(shí)例字段的方法。
useRef()創(chuàng)建了一個(gè)普通的JavaScript對(duì)象。 useRef()與自己創(chuàng)建{current:...}對(duì)象之間的唯一區(qū)別是useRef會(huì)在每個(gè)渲染上為您提供相同的ref對(duì)象。


useImperativeHandle

useImperativeHandle用于自定義暴露給父組件的ref屬性。需要配合forwardRef一起使用

// 子組件
import React, { forwardRef, useImperativeHandle, useRef } from "react";

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} className="ipt" />;
}
export default FancyInput = forwardRef(FancyInput);
//父組件
import React, { Component, createRef } from "react";
import FancyInput from "./input";

export default class ImperativeApp extends Component {
  constructor(props) {
    super(props);
    this.inputRef = createRef();
  }

  render() {
    return (
      <>
        <FancyInput ref={this.inputRef} />
        <button
          className="btn"
          onClick={() => {
            this.inputRef.current.focus();
          }}
        >
          Click
        </button>
      </>
    );
  }
}


useLayoutEffect

簽名和 useEffect 相同,但所有的 DOM突變后同步觸發(fā)。使用它從 DOM 讀取布局并同步重新渲染。在瀏覽器有機(jī)會(huì)繪制之前,將在 useLayoutEffect 內(nèi)部計(jì)劃的更新將同步刷新

在可能的情況下首選標(biāo)準(zhǔn)useEffect以避免阻止視覺更新
注意useLayoutEffect在與componentDidMountcomponentDidUpdate相同的階段觸發(fā)


useDebugValue

useDebugValue可用于在React DevTools中顯示自定義掛鉤的標(biāo)簽

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容