react
hook這裏主要講 hook 的語法和使用場景css
Hook 是一個特殊的函數,使用了 JavaScript 的閉包機制,可讓你在函數組件裏「鉤入」 React state 及生命週期等特性。Hook 不能在 class 組件中使用。這也就是我開篇說的函數式組件一把索的緣由html
Hook 的調用順序在每次渲染中都是相同的,因此它可以正常工做,只要 Hook 的調用順序在屢次渲染之間保持一致,React 就能正確地將內部 state 和對應的 Hook 進行關聯。但若是咱們將一個 Hook 調用放到一個條件語句中會發生什麼呢?vue
答案:Hook 的調用順序發生了改變出現 bug Hook 規則react
是容許你在 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
好比使用 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 而言沒有影響,此時將他定義爲一個異步變量並不明智。好的方式是將其定義爲一個同步變量。
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>
</>
);
}
複製代碼
The State Reducer Pattern with React Hooks
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 完整指南 -> 這個寫的特別好,特別推薦看學習
簡單說就是把一些須要計算可是不會變得數據存儲在本地,下次用的時候直接拿計算的結果就行了,不須要計算( 若是咱們有 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,它將返回該回調函數的 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 緩存計算數據的值
能夠減小從新 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 發生變化時,它仍會從新渲染.默認狀況下其只會對複雜對象作淺層對比,若是你想要控制對比過程,那麼請將自定義的比較函數經過第二個參數傳入來實現
至關於 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 元素,因此在控制太能夠打印兩條數據信息出來
The State Reducer Pattern with React Hooks
這個有就有點像 vue 裏面的 mixin 了,當咱們在多個組件函數裏面共同使用同一段代碼,而且這段代碼裏面包含了 react 的 hook,咱們想在多個組件函數共享邏輯的時候,咱們能夠把他提取到第三個函數中去,而組件和 Hook 都是函數,因此也一樣適用這種方式。
自定義 Hook 是一個函數,其名稱以 「use」 開頭,函數內部能夠調用其餘的 Hook,在兩個組件中使用相同的 Hook 不會共享 state,是獨立的 state
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 第二個參數一個空數組,每一次請求接口頁面就會從新渲染,第二個參數的空數組引用地址變了,會致使死循環,本身嘗試
//@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"></i> */}
</div>
)
}
複製代碼