React Hooks 入門

目錄

  • 什么是 React Hooks?
  • 為什么要?jiǎng)?chuàng)造 Hooks?
  • Hooks API 一覽
  • Hooks 使用規(guī)則

什么是 React Hooks?

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.

  • Hooks 是在 React 16.8 版本的新特性,允許你以函數(shù)組件的形式去寫每個(gè)組件
  • 每一個(gè) Hook 是由 React 提供的函數(shù),它可以讓你在函數(shù)組件中“鉤”連到一些 React 特性。

Class?生命周期函數(shù)?this?
—— 可以通通say goodbye


Hooks 會(huì)使 React 變得臃腫嗎?

  • hook 不包含任何破壞性的更改,它提供了使用我們已知的 React 特性的能力,如 state 、context 和生命周期。
  • 減少編寫 React 應(yīng)用時(shí)需要考慮的概念數(shù)量。Hooks 可以使得你始終使用函數(shù),而不必在函數(shù)、類、高階組件和 reader 屬性之間不斷切換。
  • 隨之而來,代碼量也會(huì)減少。??

為什么要?jiǎng)?chuàng)造 Hooks?

React 的使命:讓開發(fā)者更容易地構(gòu)建好的 UI。??

為了這個(gè)目標(biāo),React 團(tuán)隊(duì)做了哪些努力呢???

  1. 嘗試簡化復(fù)雜的東西(Suspense)
  2. 提升性能,嘗試讓 React 本身運(yùn)行的更快(Time Slicing)
  3. 使用開發(fā)者工具幫助開發(fā)者 debug(Profile)

React 還存在什么糟糕的地方?

  • ?? 在組件間復(fù)用狀態(tài)邏輯很難——嵌套地獄
    • mixin
    • props
    • 高階組件
  • ?? 龐大的組件,邏輯分散雜亂無章,變得難以理解
  • ?? 令人困惑的 Classes

So,有沒有什么方法來解決這三個(gè)問題呢?

Of course!React Hooks!??


內(nèi)置的 Hook


Hook API 介紹

請(qǐng)參照 demo 示例 進(jìn)行學(xué)習(xí)。

useState

傳入值作為state的默認(rèn)值,返回一個(gè)數(shù)組,數(shù)組的第一項(xiàng)是對(duì)應(yīng)的狀態(tài)(默認(rèn)值會(huì)賦予狀態(tài)),數(shù)組的第二項(xiàng)是更新狀態(tài)的函數(shù)。

const [state, setState] = useState(initialState);
setState(newState);

不同于 setState,useState 不會(huì)自動(dòng)合并更新的對(duì)象,需要我們自己用對(duì)象擴(kuò)展的形式合并,如果需要管理包含多個(gè)子值的狀態(tài)對(duì)象,則用 useReducer 更為適合。

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

Lazy initial state:如果初始狀態(tài)是一個(gè)昂貴的計(jì)算的結(jié)果,你可以提供一個(gè)函數(shù)來進(jìn)行計(jì)算,它只會(huì)在初始渲染時(shí)執(zhí)行。

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

useEffect

可以在函數(shù)組件中執(zhí)行副作用操作,并且是在函數(shù)渲染DOM完成后執(zhí)行副作用操作,異步執(zhí)行。

可以認(rèn)為 useEffect 就是組合了 componentDidMount,componentDidUpdate,以及 componentWillUnmount(在 useEffect 的回調(diào)中),但是又有區(qū)別,useEffect 不會(huì)阻止瀏覽器更新屏幕。

useEffect(() => {
  const subscription = props.source.subscribe();
  // 清理步驟,本質(zhì)上就是消除副作用
  return () => {
    subscription.unsubscribe();
  };
}, [props.source]);

【注意】:永遠(yuǎn)要對(duì) useEffect 的依賴誠實(shí),被依賴的參數(shù)一定要填上去,否則會(huì)產(chǎn)生非常難以察覺與修復(fù)的 BUG。 (利用 eslint-plugin-react-hooks 自動(dòng)訂正你的代碼中的依賴)

*useLayoutEffect

useEffect 相同,都是用來執(zhí)行副作用,主要用來讀取DOM布局并觸發(fā)同步渲染,在瀏覽器執(zhí)行繪制之前,useLayoutEffect 內(nèi)部的更新計(jì)劃將被同步刷新,同步調(diào)用 effect。

官網(wǎng)建議還是盡可能的是使用標(biāo)準(zhǔn)的 useEffect 以避免阻塞視覺更新。


useCallback

可以認(rèn)為是對(duì)依賴項(xiàng)的監(jiān)聽,接受一個(gè)回調(diào)函數(shù)和依賴項(xiàng)數(shù)組,返回一個(gè)該回調(diào)函數(shù)的 memoized(記憶)版本,該回調(diào)函數(shù)僅在某個(gè)依賴項(xiàng)改變時(shí)才會(huì)更新。

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

【用途】:解決將函數(shù)抽到 useEffect 外部的問題。

useMemo

主要用于渲染過程優(yōu)化,兩個(gè)參數(shù)依次是計(jì)算函數(shù)(通常是組件函數(shù))和依賴狀態(tài)列表,當(dāng)依賴的狀態(tài)發(fā)生改變時(shí),才會(huì)觸發(fā)計(jì)算函數(shù)的執(zhí)行。

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

【好處】:相比于 React.memo,可以做到更細(xì)粒度的優(yōu)化渲染。如函數(shù) Child 整體可能用到了 A、B 兩個(gè) props,而渲染僅用到了 B,使用 React.memo() 方案時(shí),A 的變化會(huì)導(dǎo)致重渲染,而使用 useMemo 的方案則不會(huì)。

useCallback(fn, deps) 相當(dāng)于 useMemo(() => fn, deps)


useContext

useContext 接受一個(gè) context 對(duì)象(由 createContext 創(chuàng)建)作為參數(shù),并返回 Context.Consumer

  1. 創(chuàng)建一個(gè) Context
const stateContext = createContext('default');
  1. 在根節(jié)點(diǎn)使用 Store.Provider 注入
function Parent() {
  const [count, setCount] = useState(0);
  return (
    <Store.Provider value={{ count }}>
      <Child />
    </Store.Provider>
  );
}
  1. 在子節(jié)點(diǎn)使用 useContext 拿到注入的數(shù)據(jù)
const Child = memo((props) => {
  const { count } = useContext(Store)
  // ...
});

【問題】:當(dāng)函數(shù)多了,Provider 的 value 會(huì)變得很臃腫。
【解決】:使用 useReducer 解決這個(gè)問題。


useReducer

它和 Redux 的工作方式是一樣的。useReducer 的出現(xiàn)是 useState 的替代方案,能夠讓我們更好的管理狀態(tài)。

const [state, dispatch] = useReducer(reducer, initialArg, init);

【arg1】:reducer。
【arg2】:指定狀態(tài)的默認(rèn)值。
【arg3】:接受一個(gè)函數(shù)作為參數(shù),并把第二個(gè)參數(shù)當(dāng)作函數(shù)的參數(shù)執(zhí)行。主要作用是初始值的惰性求值,把一些對(duì)狀態(tài)的邏輯抽離出來,有利于重置state。

// 定義一個(gè)init函數(shù)
function init(initialCount) {
    return [
        ...initialCount,
    ];
}

// useReducer使用
useReducer(reducer,[{id: Date.now(), value: "Hello react"}], init);

useRef

返回一個(gè)可變的 ref 對(duì)象,其 .current 屬性初始化為傳遞的參數(shù)(initialValue)。返回的對(duì)象將持續(xù)整個(gè)組件的生命周期。事實(shí)上 useRef 是一個(gè)非常有用的 API,許多情況下,我們需要保存一些改變的東西,它會(huì)派上大用場(chǎng)的。

const refContainer = useRef(initialValue);

*useImperativeHandle

可以讓你在使用 ref 時(shí)自定義暴露給父組件的實(shí)例值。
當(dāng)我們使用父組件把 ref 傳遞給子組件的時(shí)候,這個(gè)Hook 允許在子組件中把自定義實(shí)例附加到父組件傳過來的 ref 上,有利于父組件控制子組件。

useImperativeHandle(ref, createHandle, [deps])

*useDebugValue

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

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

  // Show a label in DevTools next to this Hook
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

?? Hooks 的使用規(guī)則

  • 只能在函數(shù)最外層調(diào)用 Hook,不要在循環(huán)、條件判斷或者子函數(shù)中調(diào)用。(??? Why?
  • 只在React函數(shù)組件中調(diào)用 Hooks(除非是自定義Hooks)。

Hooks 內(nèi)部是如何工作的:

// 偽代碼
let hooks, i;
function useState() {
  i++;
  if (hooks[i]) {
    // 再次渲染時(shí)
    return hooks[i];
  }
  // 第一次渲染
  hooks.push(...);
}

// 準(zhǔn)備渲染
i = -1;
hooks = fiber.hooks || [];
// 調(diào)用組件
YourComponent();
// 緩存 Hooks 的狀態(tài)
fiber.hooks = hooks;

第一條規(guī)則可以確保每次組件呈現(xiàn)時(shí)調(diào)用鉤子的順序是相同的。
【注意】自定義 Hooks 從技術(shù)上講并不是 React 的特性。編寫自定義 Hooks 的可行性源自于 Hooks 的設(shè)計(jì)方式。


封裝 http hook

import { useState, useEffect } from 'react';

export const useHttp = (url, dependencies) => {
  const [isLoading, setIsLoading] = useState(false);
  const [fetchedData, setFetchedData] = useState(null);

  useEffect(() => {
    setIsLoading(true);
    console.log('Sending Http request to URL: ' + url);
    fetch(url)
      .then(response => {
        if (!response.ok) {
          throw new Error('Failed to fetch.');
        }
        return response.json();
      })
      .then(data => {
        setIsLoading(false);
        setFetchedData(data);
      })
      .catch(err => {
        console.log(err);
        setIsLoading(false);
      });
  }, dependencies);

  return [isLoading, fetchedData];
};

能在 useEffect 中使用 async/await 嗎?

【結(jié)論】:不能在 useEffect 中返回一個(gè) promise。JavaScript 異步函數(shù)總是返回一個(gè)promise,而 useEffect 應(yīng)該只返回另一個(gè)函數(shù),該函數(shù)用于清除效果。也就是說,如果你在useEffect 中啟動(dòng) setInterval,你將返回一個(gè)函數(shù)(這里有一個(gè)閉包)來清除 interval。

? 參考



最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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