1、什么是Hooks
在不編寫 class 的情況下使用 state 以及其他的 React 特性(生命周期)
規(guī)則
- 只在最頂層使用Hook
不要在循環(huán)、條件或是嵌套函數(shù)中調(diào)用Hook;因為React在為組件每次渲染時,保存和讀取數(shù)據(jù)是依賴Hook的執(zhí)行按順序來進行的。Hooks會記錄下調(diào)用的次序以及入?yún)⒑统鰠ⅲ坏╉樞虺鲥e,那么在再次渲染時對應(yīng)的返回值就會出現(xiàn)問題。 - 只在React函數(shù)中調(diào)用Hook
不要在普通的JS函數(shù)中調(diào)用Hook。Hook只能在React的函數(shù)組件中調(diào)用,或是在自定義Hook中調(diào)用其他的Hook。 - 自定義Hooks使用 use 為開頭進行聲明
State Hooks
函數(shù)組件中,使用class組件的state類似功能。進行函數(shù)式組件內(nèi)部狀態(tài)管理,以及組件更新。
是一個異步更新
import React, { useState } from "react";
export default function UseState() {
const [count, setCount] = useState(0);
return (
<div>
<p> useState</p>
<h2>點擊次數(shù):{count}</h2>
<button
onClick={() => {
setCount(count + 1);
}}
>
點擊
</button>
</div>
);
}
useState 做了什么
通過useState進行注冊的“state”以及變更方法,會被React保留,不會隨著函數(shù)組件退出而被銷毀。
useState 入?yún)?/h3>
入?yún)ⅲ骸皊tate”初始化時需要的值|或是一個純函數(shù)返回初始化的值
useState 出參
出參:是一個數(shù)組。
- 第一個值:是“state”初始化的賦值結(jié)果(類似于 this.state.xxx),用于后續(xù)組件或函數(shù)中的計算以及展示。
- 第二個值:是修改這個"state"的方法(類似于 this.setstate)。
修改state的setState方法有兩種使用方式
- 方法一:利用原有的state進行更新
setState(state+1) - 方法二:利用函數(shù)式更新,這樣可以避免在某些無法直接獲取state準確值時進行使用
例如在useEffect中,不想指定state作為依賴,又需要修改state時,這時候因為state被閉包,永遠拿不到準確值
setState(s=>s+1)
// 連續(xù)多次調(diào)用方法一,只會執(zhí)行一次。但是方法二是每次都生效。
const [count,setCount] = useState(0);
if (way === "newState") {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// 執(zhí)行結(jié)束后 count = count + 1
} else if (way === "func") {
setCount((s) => s + 1);
setCount((s) => s + 1);
setCount((s) => s + 1);
setCount((s) => s + 1);
setCount((s) => s + 1);
// 執(zhí)行結(jié)束后 count = count + 5
}
如何使用多個state變量
方法一:
多個state,多個聲明。因為變量與函數(shù)是成對出現(xiàn)的,可以方便區(qū)分。
const [state1,setState1] = useState(0);
const [state2,setState2] = useState(2);
const [state3,setState3] = useState(3);-
方法二:
將相互關(guān)連的數(shù)據(jù),可以放在一個對象中進行管理。但是不建議將所有數(shù)據(jù)都放在一個對象中。因為useState不會幫你合并,只會直接替換。
const initState = {
state1:'haha',
state2:'hehe'
}
const [mixState,setMinState] = useState(initState);
setMixState({state1:'heihei'}); // 這樣就沒有state2了
setMixState({...mixState,state1:'heihei'});所以盡量將相關(guān)聯(lián)的數(shù)據(jù)放到一個state,但是相關(guān)性不大的數(shù)據(jù),放在一起,尤其是在復(fù)雜數(shù)據(jù)結(jié)構(gòu)進行數(shù)據(jù)更新時會顯得很麻煩。
Effect Hooks
函數(shù)組件中,使用class組件中生命周期(componentDidmount/componentUpdate)等生命周期,方便在函數(shù)組件進行渲染的時候
可以在渲染過程中進行 訂閱/取消訂閱 消息 等操作。
UseEffect是不會阻塞瀏覽器更新屏幕,是異步的。如果需要測量屏幕之類的同步Effect,可以使用useLauoutEffect。
當出現(xiàn)多個useEffect時,會根據(jù)聲明順序,依次調(diào)用多個useEffect。
import React, { useState, useEffect } from "react";
export default function UseEffect() {
const [count, setCount] = useState(0);
// 相當于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用瀏覽器的 API 更新頁面標題
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>點擊次數(shù):{count}</p>
<button onClick={() => setCount(count + 1)}>點擊</button>
</div>
);
}
需要清除的Effect
如同ComponentWillUnmount一樣,綁定的監(jiān)聽事件或是定時器,需要在組件卸載時進行取消,防止內(nèi)存泄漏。那么useEffect也
允許通過在傳入的函數(shù)中,返回一個可執(zhí)行的函數(shù),作為清除Effect時被調(diào)用。
useEffect(()=>{
console.log('每次渲染都執(zhí)行');
return ()=>{
console.log('卸載時執(zhí)行');
}
});
注意:
effect在每次渲染時候都會執(zhí)行。但在執(zhí)行當前effect時,會主動對上一個effect進行清除,主動執(zhí)行函數(shù)中返回的可執(zhí)行函數(shù)。
因為每一次的渲染執(zhí)行effect時可能都會進行一個綁定,那么如果沒有取消上一個監(jiān)聽,會導(dǎo)致監(jiān)聽錯亂,甚至重復(fù)監(jiān)聽。
例如:模擬一個函數(shù)組件渲染更新過程
//Mount FuncComponent
執(zhí)行effect,添加一個監(jiān)聽
// update FuncComponent
執(zhí)行清除Effect函數(shù),移除上一個添加的監(jiān)聽
執(zhí)行effect,添加一個監(jiān)聽
如果沒有清除這一步,那么頁面上同樣一個組件在每一次更新都會重新注冊,卻不刪除原有監(jiān)聽。這樣會出現(xiàn)bug!
同樣的如果我們的useEffect執(zhí)行了并不需要重復(fù)調(diào)用的操作,那么我們可以通過第二個參數(shù)進行跳過。
類似于 componentDidUpdate一樣,可以通過檢查prevState,prevProps來確定是否執(zhí)行更新。
useEffect,也可一將需要比較的參數(shù)組成數(shù)組,作為第二個可選參數(shù),通知React,是否需要在渲染中再次執(zhí)行。
// 父組件將更新次數(shù)傳遞給下層組件,下層組件每5次進行一個執(zhí)行一次useEffect
useEffect(()=>{
console.log('counter變?yōu)榱耍?,props.updateTimes)
},[parseInt(props.updateTimes/5)]);
如果想執(zhí)行只運行一次的 effect(僅在組件掛載和卸載時執(zhí)行),可以傳遞一個空數(shù)組([])作為第二個參數(shù)。
這就告訴 React 你的 effect 不依賴于 props 或 state 中的任何值,所以它永遠都不需要重復(fù)執(zhí)行。
這并不屬于特殊情況 —— 它依然遵循依賴數(shù)組的工作方式。
自定義Hooks
如果函數(shù)的名字以 “use” 開頭并調(diào)用其他 Hook,我們就說這是一個自定義 Hook。
每一個函數(shù)組件中調(diào)用同一個自定義Hooks,但每個函數(shù)組件中自定義Hooks返回的值是獨立的,不會共享。
相當于將一組Hooks封裝在了一起,然后直接加以調(diào)用。這樣與直接在函數(shù)組件中調(diào)用沒有區(qū)別。
// 自定義一個模擬useReducer的自定義組件
function useCustomReducer(reducer, initState) {
const [state, setState] = useState(initState);
function dispatch(action, payload) {
const newState = reducer(action, state, payload);
setState(newState);
}
return [state, dispatch];
}
其他Hooks
useContext
const value = useContext(MyContext);
接收一個 context 對象(React.createContext 的返回值)并返回該 context 的當前值。
當前的 context 值由上層組件中距離當前組件最近的 <MyContext.Provider> 的 value prop 決定。
當組件上層最近的 <MyContext.Provider> 更新時,該 Hook 會觸發(fā)重渲染,
并使用最新傳遞給 MyContext provider 的 context value 值。
即使祖先使用 React.memo 或 shouldComponentUpdate,也會在組件本身使用 useContext 時重新渲染。
useReducer
const [state,dispatch] = useReducer(reducer,initialArg,initState)
參數(shù):
- reducer 純函數(shù),接收一個action(字符串,用于處理究竟執(zhí)行哪個更新操作),一個變更值,返回一個新的state
- initialArg 純函數(shù) 接收 initstate,并返回一個操作后的state,最終作為初始化state
- initState 默認state,若沒有第二個參數(shù),只傳initstate,則直接作為初始化state
useMemo
const memoizedValue = useMemo(() =>
computeExpensiveValue(a, b), [a, b]);
返回一個 通過第一個函數(shù)計算得出的 memoized 值,就是要求第一個參數(shù)是一個純函數(shù),且必須有返回值。
把“創(chuàng)建”函數(shù)和依賴項數(shù)組作為參數(shù)傳入 useMemo,它僅會在某個依賴項改變時才重新計算 memoized 值。
這種優(yōu)化有助于避免在每次渲染時都進行高開銷的計算。
記住,傳入 useMemo 的函數(shù)會在渲染期間執(zhí)行。
請不要在這個函數(shù)內(nèi)部執(zhí)行與渲染無關(guān)的操作,諸如副作用這類的操作屬于 useEffect 的適用范疇,而不是 useMemo。
如果沒有提供依賴項數(shù)組,useMemo 在每次渲染時都會計算新的值。
useCallBack
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
返回一個 memoized 回調(diào)函數(shù),使用還是同樣的使用,只是對這個函數(shù),以及對應(yīng)的依賴項進行了一個緩存。
只要函數(shù)依賴項不發(fā)生改變,那么傳遞給子組件時,不會因為父組件的更新,子組件接收這個函數(shù)是一個新對象,而導(dǎo)致子組件的不必要更新。
把內(nèi)聯(lián)回調(diào)函數(shù)及依賴項數(shù)組作為參數(shù)傳入 useCallback,它將返回該回調(diào)函數(shù)的 memoized 版本,該回調(diào)函數(shù)僅在某個依賴項改變時才會更新。
當你把回調(diào)函數(shù)傳遞給經(jīng)過優(yōu)化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時,它將非常有用。
useCallback(fn, deps) 相當于 useMemo(() => fn, deps)。
對比useMemo,useMemo緩存的是一個值,useCallback緩存的是一個函數(shù),是對一個單獨的props值進行緩存
memo緩存的是組件本身,是站在全局的角度進行優(yōu)化
useRef
useRef可以接受一個默認值,并返回一個含有current屬性的可變對象;
使用場景:
1、獲取子組件的實例(子組件需為react類繼承組件);
function parent(){
const childRef = useRef(null);
return (<>
<Child ref={childRef} />
<button onClick={()=>{console.log(childRef.current)}} >
獲取Child組件
</button>
</>)
}
class child extends Components{
return <>haha</>
}
2、獲取組件中某個DOM元素;
const inputRef = useRef(null);
<input ref={inputRef} type="text" />
3、用做組件的全局變量。
useRef返回對象中含有一個current屬性,該屬性可以在整個組件色生命周期內(nèi)不變,不會因為重復(fù)render而重復(fù)申明,
類似于react類繼承組件的屬性this.xxx一樣。
// 存儲普通變量
const isClick = useRef(false);
console.log(isClick.current); // 輸出 false
// 存儲創(chuàng)建開銷較高的對象
const complexObjectRef = useRef(null);
function getComplexOebjct{
if(!complexObjectRef.current){
complexObjectRef.current = new ComplexObject();
}
return complexObjectRef.current;
}
const complexObject = getComplexOebjct();
useImperativeHandle
useImperativeHandle(ref, createHandle, [deps]);
入?yún)?/strong>
ref:定義 current 對象的 ref
createHandle:一個函數(shù),返回值是一個對象,即這個 ref 的 current
-
[deps]:即依賴列表,當監(jiān)聽的依賴發(fā)生變化,useImperativeHandle 才會重新將子組件的實例屬性輸出到父組件
與React.forwadRef搭配,返回一個由子組件修改后的Ref,傳遞回父組件方便父組件查詢部分值的變化。
import React, { useRef, useState, useImperativeHandle } from "react";
const Child = React.forwardRef((props, ref) => {
const inputRef = useRef(null);
const globalAttr = useRef(0);
const [clickTime, setClickTime] = useState(0);
useImperativeHandle(ref, () => {
return {
globalAttr,
addClickTime: () => {
setClickTime(clickTime + 1);
},
focus: () => {
inputRef.current.focus();
},
};
});
return (
<div>
<p>父組件點擊次數(shù)值:{clickTime}</p>
<p>全局變量值:{globalAttr.current}</p>
<input ref={inputRef} type="text" />
<button
onClick={() => {
globalAttr.current += 1;
}}
>
更新子組件全局變量
</button>
</div>
);
});
export default function UseImperativeHandle() {
const childRef = useRef(null);
return (
<>
<Child ref={childRef} />
<button
onClick={() => {
childRef.current.focus();
}}
>
獲取子節(jié)點輸入框焦點
</button>
<button
onClick={() => {
childRef.current.addClickTime();
}}
>
增加子組件添加值
</button>
<button
onClick={() => {
console.log(childRef.current.globalAttr);
}}
>
輸出子組件全局變量
</button>
</>
);
}
useLayoutEffect
使用方法與useEffect 是一致的。
useLayoutEffect里面的callback函數(shù)會在DOM更新完成后立即執(zhí)行,但是會在瀏覽器進行任何繪制之前運行完成,阻塞了瀏覽器的繪制。
但是它是阻塞瀏覽器渲染的更新,在繪制之前會執(zhí)行的副作用,一般用于有需要變更或是獲取DOM的操作。
useDebugValue
開發(fā)過程中使用,正式環(huán)境沒啥用
代碼地址