接着上篇講 react hook

react hook

這裏主要講 hook 的語法和使用場景css

hook

Hook 是一個特殊的函數,使用了 JavaScript 的閉包機制,可讓你在函數組件裏「鉤入」 React state 及生命週期等特性。Hook 不能在 class 組件中使用。這也就是我開篇說的函數式組件一把索的緣由html

Hook 的調用順序在每次渲染中都是相同的,因此它可以正常工做,只要 Hook 的調用順序在屢次渲染之間保持一致,React 就能正確地將內部 state 和對應的 Hook 進行關聯。但若是咱們將一個 Hook 調用放到一個條件語句中會發生什麼呢?vue

答案:Hook 的調用順序發生了改變出現 bug Hook 規則react

userState

是容許你在 React 函數組件中數據變化能夠異步響應式更新頁面 UI 狀態的 hook。git

userState 函數初始化變量值,返回一個數組,數組第一項是這個初始化的變量,第二項是響應式修改這個變量的方法名。github

import React, { useState } from 'react';

function Example() {
  // 聲明一個叫 「count」 的 state 變量。能夠聲明不少個
  const [count, setCount] = useState<number>(0); // 數組解構,在typescript中使用,咱們能夠用以下的方式聲明狀態的類型
  const [fruit, setFruit] = useState<string>('banana');

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>// 修改 count 的值
        Click me
      </button>
    </div>
  );
}
複製代碼

userState 的返回的第二個參數能夠接受一個函數,若是新的 state 須要經過使用先前的 state 計算得出,那麼能夠將函數傳遞給 setState。該函數將接收先前的 state,並返回一個更新後的值。注意了 useState 不會自動合併更新對象,因此運算符來達到合併更新對象的效果。vuex

function Box() {
  const [state, setState] = useState({ left: 0, top: 0, width: 100, height: 100 });
    useEffect(() => {
    function handleWindowMouseMove(e) {
      // 展開 「...state」 以確保咱們沒有 「丟失」 width 和 height
      setState(state => ({ ...state, left: e.pageX, top: e.pageY }));
    }
  });// 沒有第二個參數,只會渲染一次,永遠不會重複執行
}
複製代碼

通常狀況下,咱們使用 userState hook,給他傳的是一個簡單值,可是若是初始 state 須要經過複雜計算得到,則能夠傳入一個函數,在函數中計算並返回初始的 state,此函數只在初始渲染時被調用typescript

const [state, setState] = useState(() => {
  return  doSomething(props);
});
複製代碼

useState 返回的更新狀態方法是異步的,下一個事件循環週期執行時,狀態纔是最新的值。不要試圖在更改狀態以後立馬獲取狀態。這裏有可能會出現陳舊值引用的問題,這並非 reatc 的 bug,是由於 JavaScript 的正常表現,是由於閉包api

函數式組件與類組件在線區別 demo數組

好比使用 immutable.js 裏面的 set 結構的時候,進行循環刪除裏面的某些項,結果刪除的永遠是數組的最後一項

infos.forEach((el) => {
  if( list.has(el.id)){
    setList(list.delete(item.id))// 這裏是異步,在你循環的時候,頁面尚未重繪,拿不到最後一個值
    }
  })
複製代碼

若是咱們想要實現循環裏面刪除,那麼怎麼作呢?別忘了,useState 是想要咱們直接修改 DOM 的渲染,因此才使用他的。咱們能夠先總體的修改完以後再去影響 DOM 的渲染

infos.forEach((el) => {
    if (list.has(el.id)) {
      list = list.delete(el.id)//這裏是同步刪除
    }
  })
  setList(list)//刪除完了以後,在去修改DOM的結構
複製代碼

React 這樣設計的目的是爲了性能考慮,爭取把全部狀態改變後只重繪一次就能解決更新問題,而不是改一次重繪一次,也是很容易理解的.內部是經過 merge 操做將新狀態和老狀態合併後,從新返回一個新的狀態對象,組件中出現  setTimeout  等閉包時,儘可能在閉包內部引用 ref 而不是 state,不然容易出現讀取到舊值的狀況.閉包引用的是原來的舊值,一旦通過 setUsetate,引用的就是一個新的對象,和原來的對象引用的地址不同了。

可以直接影響 DOM 的變量,這樣咱們纔會將其稱之爲狀態。當某一個變量對於 DOM 而言沒有影響,此時將他定義爲一個異步變量並不明智。好的方式是將其定義爲一個同步變量。

React Hooks 異步操做踩坑記

useReducer

useState 的替代方案,升級版,但咱們遇到多個 useState 之間互相影響,須要或者說只是某一個參數不同,其餘的大體差很少的時候,咱們就可使用 useReducer 替換,這個有點像 vue 裏面的 vuex 的感受,也有點 Redux 的感受,可是隻是有一點點,幾個仍是徹底不同的概念

const initialState = {count: 0};

// 多個 useState 結合成一個了
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>
    </>
  );
}

複製代碼

useReducer

The State Reducer Pattern with React Hooks

React Hooks 的體系設計之一 - 分層

Umi Hooks - 助力擁抱 React Hooks

Effect Hook

React 會等待瀏覽器完成畫面渲染以後纔會延遲調用 useEffect,他至關於 react class 的三個生命週期函數 componentDidMount(組件掛載完成),componentDidUpdate(組件更新) 和 componentWillUnmount(組件將要銷燬) 三個生命週期函數的組合,能夠實現減小重複代碼的編寫

componentDidMount: 組件掛載完成的時候,須要執行一堆東西

componentDidUpdate:組件更新鉤子函數,就理解成 vue 裏面的 watch 吧,當你監聽的某一個數據發生變化的時候,就會執行這一個 Effect Hook 鉤子函數裏面的東西。

componentWillUnmount:清除 effect ,在某種狀況下,你須要清理一些數據爲了不內存泄露的時候就能夠用它。 返回一個函數,就表示你要作的清空操做了。不返回一個函數就表示不須要作清空操做。(組件卸載,

const [debounceVal, setDebounceVal] = useState(value)
  useEffect(() => {
    const handle = setTimeout(() => {
      setDebounceVal(value)
    }, delay)
    return () => {
      clearTimeout(handle) // 組件銷燬的時候清空定時器
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [value])
  return debounceVal
複製代碼

默認狀況下,它在第一次渲染以後和每次更新以後都會執行,並且 effect 的清除階段在每次從新渲染時都會執行,這個能就會致使性能問題 ,因此他又稱是反作用。他能夠接受第二個參數,他會對比更新先後的兩個數據,若是沒有變化的話,就不執行 hook 裏面的東西。僅僅只有在第二次參數發生變化的時候纔會執行。這樣就避免沒有必要的重複渲染和清除操做

能夠傳遞一個空數組([])做爲第二個參數。這就告訴 React 你的 effect 不依賴於 props 或 state 中的任何值,因此它永遠都不須要重複執行。意味着該 hook 只在組件掛載時運行一次,並不是從新渲染時,(須要注意的是[]是一個引用類型的值,在某些狀況下自定義 hooks,他做爲第二個參數也會致使頁面從新渲染,由於引用地址變了,因此在自定義 hooks 的時候須要注意,在自定義 hook 詳細說

useEffect 完整指南 -> 這個寫的特別好,特別推薦看學習

超性感的 React Hooks(四):useEffect

useMemo

簡單說就是把一些須要計算可是不會變得數據存儲在本地,下次用的時候直接拿計算的結果就行了,不須要計算( 若是咱們有 CPU 密集型操做,咱們能夠經過將初始操做的結果存儲在緩存中來優化使用。若是操做必然會再次執行,咱們將再也不麻煩再次使用咱們的 CPU,由於相同結果的結果存儲在某個地方,咱們只是簡單地返回結果他經過內存來提高速度,React.useMemo 是新出來的 hooks api,而且這個 api 是做用於 function 組件,此方法僅做爲性能優化的方式而存在。但請不要依賴它來「阻止」渲染,由於這會產生 bug。

把「建立」函數和依賴項數組做爲參數傳入 useMemo,它僅會在某個依賴項改變時才從新計算 memoized 值。這種優化有助於避免在每次渲染時都進行高開銷的計算。

function App() {
  const [num, setNum] = useState(0);

  // 一個很是耗時的一個計算函數
  // result 最後返回的值是 49995000
  function expensiveFn() {
    let result = 0;

    for (let i = 0; i < 10000; i++) {
      result += i;
    }

    console.log(result) // 49995000
    return result;
  }

  const base = expensiveFn();
  //  const base = useMemo(expensiveFn, []); 只有在第一次點擊的時候纔會執行,後來都不執行了,他的第二個參數和useEffect同樣的意思

  return (
    <div className="App">
      <h1>count:{num}</h1>
      <button onClick={() => setNum(num + base)}>+1</button>
    </div>
  );
}
複製代碼

記住,傳入 useMemo 的函數會在渲染期間執行。請不要在這個函數內部執行與渲染無關的操做,諸如反作用這類的操做屬於 useEffect 的適用範疇,而不是 useMemo

useCallback

父組件給子組件傳遞函數的時候,父組件每一次的修改都會從新渲染,都會致使它們在每次渲染上都有不一樣的引用,最後的結果是,每一次父組件的修改都直接致使了子組件沒有必要的渲染。(引用類型

這個時候咱們吧把函數以及依賴項做爲參數傳入 useCallback,它將返回該回調函數的 memoized 版本,這個 memoizedCallback 只有在依賴項有變化的時候纔會更新。

給定相同 props 的狀況下渲染相同的結果,而且經過記憶組件渲染結果的方式來提升組件的性能表現,第二個參數表明的意義和上面的同樣

// 避免引用類型的重複渲染

const handleIndicator = useCallback((indicator: EvaluateIndicator) => {
    console.log('傳給字組件')
  }, [])
複製代碼

// 函數防抖

import React, { useState, useCallback } from 'react'
import { debounce } from '../../utils/tool'

import './index.scss'
interface searchlParams {
  handleSearch: (val: string) => void
}
const Search: React.FC<searchlParams> = ({ handleSearch }) => {
  const [value, setValue] = useState<string>('')
  /* 防抖的另一種寫法 */
  const debounceSearch = useCallback(
    debounce((val) => handleSearch(val), 2000),
    [],
  )
  const changhandleSearch = (e: any) => {
    setValue(e.target.value)
    debounceSearch(e.target.value)
  }
  return (
    <div className="search-wrapper">
      <input className="input-control" value={value} onChange={changhandleSearch} placeholder="搜索" />
    </div>
  )
}
複製代碼

子組件須要配合 React.memo 的使用,React.memo 和 useCallback 都是爲了減小從新 render 的次數

useCallback 和 useMemo 均可緩存函數的引用或值,可是從更細的使用角度來講 useCallback 緩存函數的引用,useMemo 緩存計算數據的值

如何對 React 函數式組件進行優化

淺談 React 性能優化的方向

useCallback、useMemo 分析 & 差異

React.memo

能夠減小從新 render 的次數的。

//子組件

function Child(props) {
  console.log(props.name)
  return <h1>{props.name}</h1>
}

export default React.memo(Child)

// 父組件
function App() {
  const [count, setCount] = useState<number>(1)

  return (
    <div className="App">
      <h1>{ count }</h1>
      <button onClick={() => setCount(count+1)}>改變數字</button>
      <Child name="sunseekers"></Child>
    </div>
  );
}
複製代碼

若是你的函數組件在給定相同 props 的狀況下渲染相同的結果,那麼你能夠經過將其包裝在 React.memo 中調用,以此經過記憶組件渲染結果的方式來提升組件的性能表現。這意味着在這種狀況下,React 將跳過渲染組件的操做並直接複用最近一次渲染的結果。(若是沒有用 React.memo 包裹,每一次 count 變化,子組件都會從新渲染)

僅檢查 props 變動。若是函數組件被 React.memo 包裹,且其實現中擁有 useState 或 useContext 的 Hook,當 context 發生變化時,它仍會從新渲染.默認狀況下其只會對複雜對象作淺層對比,若是你想要控制對比過程,那麼請將自定義的比較函數經過第二個參數傳入來實現

如何對 React 函數式組件進行優化

useRef

至關於 vue 裏面的 refs ,只是在這邊的用法不同而已。useRef 返回一個可變的 ref 對象,其 current 屬性被初始化爲傳入的參數(initialValue)。返回的 ref 對象在組件的整個生命週期內保持不變,當咱們遇到了由於閉包問題致使的陳舊值引用的問題,咱們就能夠用它來解決問題

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已掛載到 DOM 上的文本輸入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}
複製代碼

在更新過程當中它會被執行兩次,第一次傳入參數 null,而後第二次會傳入參數 DOM 元素,因此在控制太能夠打印兩條數據信息出來

Refs and the DOM

refs 經過函數引用 demo

The State Reducer Pattern with React Hooks

自定義 Hook

這個有就有點像 vue 裏面的 mixin 了,當咱們在多個組件函數裏面共同使用同一段代碼,而且這段代碼裏面包含了 react 的 hook,咱們想在多個組件函數共享邏輯的時候,咱們能夠把他提取到第三個函數中去,而組件和 Hook 都是函數,因此也一樣適用這種方式。

自定義 Hook 是一個函數,其名稱以 「use」 開頭,函數內部能夠調用其餘的 Hook,在兩個組件中使用相同的 Hook 不會共享 state,是獨立的 state

  1. 接口請求,在每個接口前面都加一個 loading
import { useState, useCallback, useEffect } from 'react'

export function useFriendStatus(fn, dependencies) {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)

  // 請求的方法 這個方法會自動管理loading
  const request = useCallback(() => {
    setLoading(true)
    setData(fn)
    setLoading(false)
  })
  // 根據傳入的依賴項來執行請求
  useEffect(() => {
    request()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [dependencies])

  return {
    // 請求獲取的數據
    data,
    // loading狀態
    loading,
    // 請求的方法封裝
    request,
  }
}

// 組件中使用
const { data, loading } = useFriendStatus(fetchTodos({ tab: 'activeTab' }), 'activeTab')

複製代碼

若是 dependencies 是引用類型的要注意了,會致使每一次加載頁面引用的地址都不同,直接致使頁面死循環,因此處理的時候, 要特別當心和注意了。好比說,若是咱們給 useFriendStatus 第二個參數一個空數組,每一次請求接口頁面就會從新渲染,第二個參數的空數組引用地址變了,會致使死循環,本身嘗試

  1. 函數防抖
//@ts-ignore
import React, { useState, useEffect } from 'react'
export default function useDebounce(value, delay) {
  const [debounceVal, setDebounceVal] = useState(value)
  useEffect(() => {
    const handle = setTimeout(() => {
      setDebounceVal(value)
    }, delay)
    return () => {
      clearTimeout(handle)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [value])
  return debounceVal
}
// 組件中使用
interface searchlParams {
  handleSearch: (val: string) => void
}
const Search = ({ handleSearch:searchlParams }) => {
  const [value, setValue] = useState<string>('')
  // 函數防抖,每一次內部變量變化都會註冊和執行setTimeout,函數從新渲染以後
  const debounceSearch = useDebounce(value, 2000)
  useEffect(() => {
    handleSearch(value)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [debounceSearch])

  return (
    <div className="search-wrapper">
      <input className="input-control" value={value} onChange={(e) => setValue(e.target.value)} placeholder="搜索" />
      {/* <i className="iconfont ico search-ico">&#xe6aa;</i> */}
    </div>
  )
}
複製代碼

在 react 函數式組件中使用防抖與節流函數

自定義 Hook

使用 React Hooks + 自定義 Hook 封裝一步一步打造一個完善的小型應用

原文連接,會保持一直更新,建議關注原文,最新更新

相關文章
相關標籤/搜索