React Hooks原理與實踐

什麼是React Hooks?

首先須要瞭解react組件的概念

react組件分爲如下幾種:vue

一、功能組件(無狀態組件)react

Functional (Stateless) Component,功能組件也叫無狀態組件,通常只負責渲染。redux

function Welcome(props) {
    return <h1>Hello, {props.name}</h1>; 
}
複製代碼

二、類組件(有狀態組件)
Class (Stateful) Component,類組件也是有狀態組件,也能夠叫容器組件。通常有交互邏輯和業務邏輯。數組

class Welcome extends React.Component {
  state = {
      name: ‘tori’,
  }
componentDidMount() {
      fetch(…);
      …
  }
render() {
  return (
      <> <h1>Hello, {this.state.name}</h1> <button onClick={() => this.setState({name: ‘007’})}>更名</button> </>
    );
}
}
複製代碼

三、渲染組件 Presentational Component,和功能(無狀態)組件相似。瀏覽器

const Hello = (props) => {
  return (
    <div> <h1>Hello! {props.name}</h1> </div>
  )
}
複製代碼

總結

  • 函數組件必定是無狀態組件,展現型組件通常是無狀態組件;
  • 類組件既能夠是有狀態組件,又能夠是無狀態組件;
  • 容器型組件通常是有狀態組件。
  • 劃分的原則歸納爲:分而治之、高內聚、低耦合
  • 經過以上組件之間的組合能實現絕大部分需求。

Hook 出現以前,組件之間複用狀態邏輯很難,解決方案(HOC、Render Props)都須要從新組織組件結構, 且代碼難以理解。在React DevTools 中觀察過 React 應用,你會發現由 providers,consumers,高階組件,render props 等其餘抽象層組成的組件會造成「嵌套地獄」。
組件維護愈來愈複雜,譬如事件監聽邏輯要在不一樣的生命週期中綁定和解綁,複雜的頁面componentDidMount包涵不少邏輯,代碼閱讀性變得不好。
class組件中的this難以理解,且class 不能很好的壓縮,而且會使熱重載出現不穩定的狀況。更多引子介紹參見官方介紹。
因此hook就爲解決這些問題而來:緩存

  • 避免地獄式嵌套,可讀性提升。
  • 函數式組件,比class更容易理解。
  • class組件生命週期太多太複雜,使函數組件存在狀態。
  • 解決HOC和Render Props的缺點。
  • UI 和 邏輯更容易分離。

官方hooks API

1.useState

useState 是 React Hooks 中很基本的一個 API,它的用法主要有這幾種:性能優化

  1. useState 接收一個初始值,返回一個數組,數組裏面分別是當前值和修改這個值的方法(相似 state 和 setState)。
  2. useState 接收一個函數,返回一個數組。
  3. setCount 能夠接收新值,也能夠接收一個返回新值的函數。
1.  const [ count1, setCount1 ] = useState(0);
1.  const [ count2, setCount2 ] = useState(() => 0);
1.  setCount1(1); // 修改 state
複製代碼

class this.setState更新是state是合併, useState中setState是替換。markdown

useState 和 class state 的區別 雖然函數組件也有了 state,可是 function state 和 class state 仍是有一些差別:閉包

  1. function state 的粒度更細,class state 過於無腦。
  2. function state 保存的是快照,class state 保存的是最新值。
  3. 引用類型的狀況下,class state 不須要傳入新的引用,而 function state 必須保證是個新的引用。

關於第2點,舉個例子less

image.png

image.png

在第一個例子中,連續點擊十次,頁面上的數字會從0增加到10。而第二個例子中,連續點擊十次,頁面上的數字只會從0增加到1

class 組件裏面能夠經過 this.state 引用到 count,因此每次 setTimeout 的時候都能經過引用拿到上一次的最新 count,因此點擊多少次最後就加了多少。

在 function component 裏面每次更新都是從新執行當前函數,也就是說 setTimeout 裏面讀取到的 count 是經過閉包獲取的,而這個 count 實際上只是初始值,並非上次執行完成後的最新值,因此最後只加了1次。

2.useRef

要解決上面這個問題,就須要使用useRef,useRef是一個對象,他擁有一個current屬性,而且無論函數組件執行多少次,useRef返回的對象永遠都是原來的那一個

image.png

useRef 有下面這幾個特色:

  1. useRef 是一個只能用於函數組件的方法。
  2. useRef 是除字符串 ref、函數 refcreateRef 以外的第四種獲取 ref 的方法。
  3. useRef 在渲染週期內永遠不會變,所以能夠用來引用某些數據。
  4. 修改 ref.current 不會引起組件從新渲染。

3.useEffect

useEffect 是一個 Effect Hook,經常使用於一些反作用的操做,在必定程度上能夠充當 componentDidMountcomponentDidUpdatecomponentWillUnmount 這三個生命週期。useEffect 是很是重要的一個方法,能夠說是 React Hooks 的靈魂,它用法主要有這麼幾種:

  1. useEffect 接收兩個參數,分別是要執行的回調函數、依賴數組。
  2. 若是依賴數組爲空數組,那麼回調函數會在第一次渲染結束後(componentDidMount)執行,返回的函數會在組件卸載時(componentWillUnmount)執行。
  3. 若是不傳依賴數組,那麼回調函數會在每一次渲染結束後(componentDidMount 和 componentDidUpdate)執行。
  4. 若是依賴數組不爲空數組,那麼回調函數會在依賴值每次更新渲染結束後(componentDidUpdate)執行,這個依賴值通常是 state 或者 props。

image.png

useEffect 比較重要,它主要有這幾個做用:

  1. 代替部分生命週期,如 componentDidMount、componentDidUpdate、componentWillUnmount。
  2. 更加 reactive,相似 mobx 的 reaction 和 vue 的 watch。
  3. 從命令式變成聲明式,不須要再關注應該在哪一步作某些操做,只須要關注依賴數據。
  4. 經過 useEffect 和 useState 能夠編寫一系列自定義的 Hook。
useEffect vs useLayoutEffect

useLayoutEffect 也是一個 Hook 方法,從名字上看和 useEffect 差很少,他倆用法也比較像。在90%的場景下咱們都會用 useEffect,然而在某些場景下卻不得不用 useLayoutEffect。useEffect 和 useLayoutEffect 的區別是:

  1. useEffect 不會阻塞瀏覽器渲染,而 useLayoutEffect 會。
  2. useEffect 會在瀏覽器渲染結束後執行,useLayoutEffect 則是在 DOM 更新完成後,瀏覽器繪製以前執行。

例如咱們使用useEffect方法來更新Demo的位置,那麼在頁面渲染時,咱們就會看到Demo原來的位置,而後這個時候useEffect方法纔會執行,更新Demo的位置,咱們就會在頁面上看到Demo位置的變化,可是有的時候咱們不想讓用戶看到這個變化的過程,會比較醜,好比變動一個元素的位置,就會變成閃現過去,這個時候就須要使用useLayoutEffect

4.useContext

跨組件共享數據的鉤子函數

const value = useContext(MyContext); 
// MyContext 爲 context 對象(React.createContext 的返回值) 
// useContext 返回MyContext的返回值。 
// 當前的 context 值由上層組件中距離當前組件最近的<MyContext.Provider> 的 value prop 決定。
複製代碼

useContext 的組件總會在 context 值變化時從新渲染, 因此<MyContext.Provider>包裹的越多,層級越深,性能會形成影響。 <MyContext.Provider>的value 發生變化時候,包裹的組件不管是否訂閱content value,全部組件都會從新渲染。

5.useReducer

const [state, dispatch] = useReducer(reducer, initialState);
複製代碼

reducer就是一個只能經過action將state從一個過程轉換成另外一個過程的純函數;

useReducer就是一種經過(state,action) => newState的過程,和redux工做方式同樣。 數據流: dispatch(action) => reducer更新state => 返回更新後的state

官方推薦如下場景須要useReducer更佳:

  • state 邏輯較複雜且包含多個子值, 能夠集中處理。
  • 下一個 state 依賴於以前的 state。
  • 想更穩定的構建自動化測試用例。
  • 想深層級修改子組件的一些狀態,使用 useReducer 還能給那些會觸發深更新的組件作性能優化,由於你能夠向子組件傳遞 dispatch 而不是回調函數 。

使用reducer有助於將讀取與寫入分開。

6.useMemo

useMemo 的用法相似 useEffect,經常用於緩存一些複雜計算的結果。useMemo 接收一個函數和依賴數組,當數組中依賴項變化的時候,這個函數就會執行,返回新的值。

const sum = useMemo(() => {
    // 一系列計算
}, [count])
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);; 返回一個 memoized 值,和useCallback同樣,當依賴項發生變化,纔會從新計算 memoized 的值。useMemo和useCallback不一樣之處是:它容許你將memoized應用於任何值類型(不只僅是函數)。
複製代碼

image.png DatePicker 組件每次打開或者切換月份的時候,都須要大量的計算來算出當前須要展現哪些日期。而後再將計算後的結果渲染到單元格里面,這裏可使用 useMemo 來緩存,只有當傳入的日期變化時纔去計算。

  • useMemo會在render前執行
  • 若是沒有提供依賴項數組,useMemo 在每次渲染時都會計算新的值。
  • useMemo用於返回memoize,防止每次render時大計算量帶來的開銷。
  • 使用useMemo優化需謹慎,由於優化自己也帶來了計算,大多數時候,你不須要考慮去優化沒必要要的從新渲染

7.useCallback

和 useMemo 相似,只不過 useCallback 是用來緩存函數。

const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
//返回一個 memoized 回調函數。
複製代碼

總結: useCallback將返回一個記憶的回調版本,僅在其中一個依賴項已更改時才更改。當將回調傳遞給依賴於引用相等性的優化子組件以防止沒必要要的渲染時,此方法頗有用。使用回調函數做爲參數傳遞,每次render函數都會變化,也會致使子組件rerender, useCallback能夠優化rerender。

相關文章
相關標籤/搜索