react hooks 萬字總結(jié),帶你夯實(shí)基礎(chǔ)
前言
自己在掘金上看了也看了很多關(guān)于hooks的文章未舟,感覺都講得不是很詳細(xì)术瓮。而且也有很多的水文雕擂。最近自己打算重學(xué)react冰更,系統(tǒng)性的再把hooks給學(xué)習(xí)一遍产徊。
Hooks is what?
- react-hooks是react16.8以后冬殃,react新增的鉤子API囚痴,它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性.
- Hook是一些可以讓你在函數(shù)組件里“鉤入” React state 及生命周期等特性的函數(shù)。
why use Hooks审葬?
類組件的缺點(diǎn):(來自官網(wǎng)動機(jī))
在組件之間復(fù)用狀態(tài)邏輯很難
復(fù)雜組件變得難以理解
難以理解的 class
你必須去理解 JavaScript 中 this
的工作方式深滚,這與其他語言存在巨大差異。還不能忘記綁定事件處理器涣觉。沒有穩(wěn)定的語法提案痴荐,代碼非常冗余。
hooks的出現(xiàn)官册,解決了上面的問題生兆。另外,還有一些其他的優(yōu)點(diǎn):
- 增加代碼的可復(fù)用性膝宁,邏輯性鸦难,彌補(bǔ)無狀態(tài)組件沒有生命周期根吁,沒有數(shù)據(jù)管理狀態(tài)state的缺陷
- react-hooks思想更趨近于函數(shù)式編程。用函數(shù)聲明方式代替class聲明方式合蔽,雖說class也是es6構(gòu)造函數(shù)語法糖击敌,但是react-hooks寫起來函數(shù)即是組件,無疑也提高代碼的開發(fā)效率(無需像class聲明組件那樣寫聲明周期拴事,寫生命周期render函數(shù)等)
Hooks沒有破壞性改動
- 完全可選的沃斤。 你無需重寫任何已有代碼就可以在一些組件中嘗試 Hook。但是如果你不想刃宵,你不必現(xiàn)在就去學(xué)習(xí)或使用 Hook衡瓶。
- 100% 向后兼容的。 Hook 不包含任何破壞性改動牲证。
- 現(xiàn)在可用哮针。 Hook 已發(fā)布于 v16.8.0。
使用Hooks的規(guī)則
1. 只在最頂層使用 Hook,不要在循環(huán)从隆,條件或嵌套函數(shù)中調(diào)用 Hook
確背夏欤總是在你的 React 函數(shù)的最頂層調(diào)用他們。遵守這條規(guī)則键闺,你就能確保 Hook 在每一次渲染中都按照同樣的順序被調(diào)用。這讓 React 能夠在多次的 useState
和 useEffect
調(diào)用之間保持 hook 狀態(tài)的正確澈驼。
2. 只在 React 函數(shù)中調(diào)用 Hook
不要在普通的 JavaScript 函數(shù)中調(diào)用 Hook,你可以:
- ? 在 React 的函數(shù)組件中調(diào)用 Hook
- ? 在自定義 Hook 中調(diào)用其他 Hook
至于為什么會有這些規(guī)則辛燥,如果你感興趣,請參考Hook 規(guī)則
useState
const [state, setState] = useState(initialState)
- useState 有一個參數(shù)(initialState 可以是一個函數(shù)缝其,返回一個值挎塌,但一般都不會這么用),該參數(shù)可以為任意數(shù)據(jù)類型内边,一般用作默認(rèn)值.
- useState 返回值為一個數(shù)組榴都,數(shù)組的第一個參數(shù)為我們需要使用的 state,第二個參數(shù)為一個改變state的函數(shù)(功能和this.setState一樣)
來看一個計(jì)時器的案例
import React,{useState} from "react";
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
復(fù)制代碼
-
第一行: 引入 React 中的
useState
Hook漠其。它讓我們在函數(shù)組件中存儲內(nèi)部 state嘴高。 -
第三行: 在
Example
組件內(nèi)部,我們通過調(diào)用useState
Hook 聲明了一個新的 state 變量和屎。它返回一對值給到我們命名的變量上拴驮。我們把變量命名為count
,因?yàn)樗鎯Φ氖屈c(diǎn)擊次數(shù)柴信。我們通過傳0
作為useState
唯一的參數(shù)來將其初始化為0
套啤。第二個返回的值本身就是一個函數(shù)。它讓我們可以更新count
的值随常,所以我們叫它setCount
潜沦。 -
第七行: 當(dāng)用戶點(diǎn)擊按鈕后萄涯,我們傳遞一個新的值給
setCount
。React 會重新渲染Example
組件唆鸡,并把最新的count
傳給它窃判。
使用多個state 變量
// 聲明多個 state 變量
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: '學(xué)習(xí) Hook' }]);
復(fù)制代碼
你不必使用多個 state 變量。State 變量可以很好地存儲對象和數(shù)組喇闸,因此袄琳,你仍然可以將相關(guān)數(shù)據(jù)分為一組。
更新State
import React,{useState} from "react";
function Example() {
const [count, setCount] = useState(0);
const [person, setPerson] = useState({name:'jimmy',age:22});
return (
<div>
<p>name {person.name} </p>
// 如果新的 state 需要通過使用先前的 state 計(jì)算得出燃乍,那么可以將回調(diào)函數(shù)當(dāng)做參數(shù)傳遞給 setState唆樊。
// 該回調(diào)函數(shù)將接收先前的 state,并返回一個更新后的值刻蟹。
<button onClick={() => setCount(count=>count+1)}>Click me</button>
<button onClick={() => setPerson({name:'chimmy'})}>Click me</button>
</div>
);
}
export default Example;
復(fù)制代碼
setPerson更新person時逗旁,不像 class 中的 this.setState
,更新 state 變量總是替換它而不是合并它舆瘪。上例中的person為{name:'chimmy'} 而不是{name:'chimmy',age:22}
useEffect
Effect Hook 可以讓你在函數(shù)組件中執(zhí)行副作用(數(shù)據(jù)獲取片效,設(shè)置訂閱以及手動更改 React 組件中的 DOM 都屬于副作用)操作
useEffect(fn, array)
useEffect在初次完成渲染之后都會執(zhí)行一次, 配合第二個參數(shù)可以模擬類的一些生命周期。
如果你熟悉 React class 的生命周期函數(shù)英古,你可以把 useEffect
Hook 看做 componentDidMount``componentDidUpdate
和 componentWillUnmount
這三個函數(shù)的組合淀衣。
useEffect 實(shí)現(xiàn)componentDidMount
如果第二個參數(shù)為空數(shù)組,useEffect相當(dāng)于類組件里面componentDidMount召调。
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("我只會在組件初次掛載完成后執(zhí)行");
}, []);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
復(fù)制代碼
頁面渲染完成后膨桥,會執(zhí)行一次useEffect。打印“我只會在組件初次掛載完成后執(zhí)行”唠叛,當(dāng)點(diǎn)擊按鈕改變了state只嚣,頁面重新渲染后,useEffect不會執(zhí)行艺沼。
useEffect 實(shí)現(xiàn)componentDidUpdate
如果不傳第二個參數(shù)册舞,useEffect 會在初次渲染和每次更新時,都會執(zhí)行障般。
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("我會在初次組件掛載完成后以及重新渲染時執(zhí)行");
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
復(fù)制代碼
初次渲染時调鲸,會執(zhí)行一次useEffect,打印出“我會在初次組件掛載完成后以及重新渲染時執(zhí)行”剩拢。 當(dāng)點(diǎn)擊按鈕時线得,改變了state,頁面重新渲染徐伐,useEffect都會執(zhí)行贯钩,打印出“我會在初次組件掛載完成后以及重新渲染時執(zhí)行”。
useEffect 實(shí)現(xiàn)componentWillUnmount
effect 返回一個函數(shù),React 將會在執(zhí)行清除操作時調(diào)用它角雷。
useEffect(() => {
console.log("訂閱一些事件");
return () => {
console.log("執(zhí)行清除操作")
}
},[]);
復(fù)制代碼
注意:這里不只是組件銷毀時才會打印“執(zhí)行清除操作”祸穷,每次重新渲染時也都會執(zhí)行。至于原因勺三,我覺得官網(wǎng)解釋的很清楚雷滚,請參考 解釋: 為什么每次更新的時候都要運(yùn)行 Effect
控制useEffect的執(zhí)行
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
const [number, setNumber] = useState(1);
useEffect(() => {
console.log("我只會在cout變化時執(zhí)行");
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click cout</button>
<button onClick={() => setNumber(number + 1)}>Click number</button>
</div>
);
}
export default Example;
復(fù)制代碼
上面的例子,在點(diǎn)擊 click cout按鈕時吗坚,才會打印“我只會在cout變化時執(zhí)行”祈远。 因?yàn)閡seEffect 的第二個參數(shù)的數(shù)組里面的依賴是cout,所以商源,只有cout發(fā)生改變時车份,useEffect 才會執(zhí)行。如果數(shù)組中有多個元素牡彻,即使只有一個元素發(fā)生變化扫沼,React 也會執(zhí)行 effect。
使用多個 Effect 實(shí)現(xiàn)關(guān)注點(diǎn)分離
使用 Hook 其中一個目的就是要解決 class 中生命周期函數(shù)經(jīng)常包含不相關(guān)的邏輯庄吼,但又把相關(guān)邏輯分離到了幾個不同方法中的問題缎除。
import React, { useState, useEffect } from "react";
function Example() {
useEffect(() => {
// 邏輯一
});
useEffect(() => {
// 邏輯二
});
useEffect(() => {
// 邏輯三
});
return (
<div>
useEffect的使用
</div>
);
}
export default Example;
復(fù)制代碼
Hook 允許我們按照代碼的用途分離他們, 而不是像生命周期函數(shù)那樣总寻。React 將按照 effect 聲明的順序依次調(diào)用組件中的每一個 effect器罐。
useEffect中使用異步函數(shù)
useEffect是不能直接用 async await 語法糖的
/* 錯誤用法 ,effect不支持直接 async await*/
useEffect(async ()=>{
/* 請求數(shù)據(jù) */
const res = await getData()
},[])
復(fù)制代碼
useEffect
的回調(diào)參數(shù)返回的是一個清除副作用的 clean-up
函數(shù)废菱。因此無法返回 Promise
技矮,更無法使用 async/await
那我們應(yīng)該如何讓useEffect
支持async/await
呢?
方法一(推薦)
const App = () => {
useEffect(() => {
(async function getDatas() {
await getData();
})();
}, []);
return <div></div>;
};
復(fù)制代碼
方法二
useEffect(() => {
const getDatas = async () => {
const data = await getData();
setData(data);
};
getDatas();
}, []);
復(fù)制代碼
useEffect 做了什么
通過使用這個 Hook殊轴,你可以告訴 React 組件需要在渲染后
執(zhí)行某些操作。React 會保存你傳遞的函數(shù)(我們將它稱之為 “effect”)袒炉,并且在執(zhí)行 DOM 更新之后調(diào)用它旁理。
為什么在組件內(nèi)部調(diào)用 useEffect
?
將 useEffect
放在組件內(nèi)部讓我們可以在 effect 中直接訪問 count
state 變量(或其他 props)我磁。我們不需要特殊的 API 來讀取它 —— 它已經(jīng)保存在函數(shù)作用域中孽文。Hook 使用了 JavaScript 的閉包機(jī)制,而不用在 JavaScript 已經(jīng)提供了解決方案的情況下夺艰,還引入特定的 React API芋哭。
useContext
概念
const value = useContext(MyContext);
接收一個 context 對象(React.createContext
的返回值)并返回該 context 的當(dāng)前值。當(dāng)組件上層最近的 <MyContext.Provider>
更新時郁副,該 Hook 會觸發(fā)重渲染减牺,并使用最新傳遞給 MyContext
provider 的 context value
值。即使祖先使用 React.memo
或 shouldComponentUpdate
,也會在組件本身使用 useContext
時重新渲染拔疚。
別忘記 useContext
的參數(shù)必須是 context 對象本身:
-
正確:
useContext(MyContext)
-
錯誤:
useContext(MyContext.Consumer)
-
錯誤:
useContext(MyContext.Provider)
示例
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
// 創(chuàng)建兩個context
export const UserContext = React.createContext();
export const TokenContext = React.createContext();
ReactDOM.render(
<UserContext.Provider value={{ id: 1, name: "chimmy", age: "20" }}>
<TokenContext.Provider value="我是token">
<App />
</TokenContext.Provider>
</UserContext.Provider>,
document.getElementById("root")
);
復(fù)制代碼
app.js
import React, { useContext } from "react";
import { UserContext, TokenContext } from "./index";
function Example() {
let user = useContext(UserContext);
let token = useContext(TokenContext);
console.log("UserContext", user);
console.log("TokenContext", token);
return (
<div>
name:{user?.name},age:{user?.age}
</div>
);
}
export default Example;
復(fù)制代碼
打印的值如下
提示
如果你在接觸 Hook 前已經(jīng)對 context API 比較熟悉肥隆,那應(yīng)該可以理解,useContext(MyContext)
相當(dāng)于 class 組件中的 static contextType = MyContext
或者 <MyContext.Consumer>
稚失。
useContext(MyContext)
只是讓你能夠讀取 context 的值以及訂閱 context 的變化栋艳。你仍然需要在上層組件樹中使用 <MyContext.Provider>
來為下層組件提供 context。
useReducer
概念
const [state, dispatch] = useReducer(reducer, initialArg, init);
useState
的替代方案句各。它接收一個形如 (state, action) => newState
的 reducer吸占,并返回當(dāng)前的 state 以及與其配套的 dispatch
方法。(如果你熟悉 Redux 的話凿宾,就已經(jīng)知道它如何工作了矾屯。)
在某些場景下,useReducer
會比 useState
更適用菌湃,例如 state 邏輯較復(fù)雜且包含多個子值问拘,或者下一個 state 依賴于之前的 state 等。并且惧所,使用 useReducer
還能給那些會觸發(fā)深更新的組件做性能優(yōu)化骤坐,因?yàn)槟憧梢韵蜃咏M件傳遞 dispatch
而不是回調(diào)函數(shù)
注意點(diǎn)
React 會確保
dispatch
函數(shù)的標(biāo)識是穩(wěn)定的,并且不會在組件重新渲染時改變下愈。這就是為什么可以安全地從useEffect
或useCallback
的依賴列表中省略dispatch
纽绍。
示例
import React, { useReducer } from "react";
export default function Home() {
function reducer(state, action) {
switch (action.type) {
case "increment":
return { ...state, counter: state.counter + 1 };
case "decrement":
return { ...state, counter: state.counter - 1 };
default:
return state;
}
}
const [state, dispatch] = useReducer(reducer, { counter: 0 });
return (
<div>
<h2>Home當(dāng)前計(jì)數(shù): {state.counter}</h2>
<button onClick={(e) => dispatch({ type: "increment" })}>+1</button>
<button onClick={(e) => dispatch({ type: "decrement" })}>-1</button>
</div>
);
}
復(fù)制代碼
useCallback
概念
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
復(fù)制代碼
返回一個 [memoized]回調(diào)函數(shù)。
把內(nèi)聯(lián)回調(diào)函數(shù)及依賴項(xiàng)數(shù)組作為參數(shù)傳入 useCallback
势似,它將返回該回調(diào)函數(shù)的 memoized 版本拌夏,該回調(diào)函數(shù)僅在某個依賴項(xiàng)改變時才會更新。當(dāng)你把回調(diào)函數(shù)傳遞給經(jīng)過優(yōu)化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate
)的子組件時履因,它將非常有用障簿。
示例
import React, { useState } from "react";
// 子組件
function Childs(props) {
console.log("子組件渲染了");
return (
<>
<button onClick={props.onClick}>改標(biāo)題</button>
<h1>{props.name}</h1>
</>
);
}
const Child = React.memo(Childs);
function App() {
const [title, setTitle] = useState("這是一個 title");
const [subtitle, setSubtitle] = useState("我是一個副標(biāo)題");
const callback = () => {
setTitle("標(biāo)題改變了");
};
return (
<div className="App">
<h1>{title}</h1>
<h2>{subtitle}</h2>
<button onClick={() => setSubtitle("副標(biāo)題改變了")}>改副標(biāo)題</button>
<Child onClick={callback} name="桃桃" />
</div>
);
}
復(fù)制代碼
執(zhí)行結(jié)果如下圖
當(dāng)我點(diǎn)擊改副標(biāo)題這個 button 之后,副標(biāo)題會變?yōu)椤父睒?biāo)題改變了」栅迄,并且控制臺會再次打印出子組件渲染了
站故,這就證明了子組件重新渲染了,但是子組件沒有任何變化毅舆,那么這次 Child 組件的重新渲染就是多余的西篓,那么如何避免掉這個多余的渲染呢?
找原因
我們在解決問題的之前憋活,首先要知道這個問題是什么原因?qū)е碌模?/strong>
咱們來分析岂津,一個組件重新重新渲染,一般三種情況:
- 要么是組件自己的狀態(tài)改變
- 要么是父組件重新渲染悦即,導(dǎo)致子組件重新渲染吮成,但是父組件的 props 沒有改變
- 要么是父組件重新渲染橱乱,導(dǎo)致子組件重新渲染,但是父組件傳遞的 props 改變
接下來用排除法查出是什么原因?qū)е碌模?/p>
第一種很明顯就排除了赁豆,當(dāng)點(diǎn)擊改副標(biāo)題 的時候并沒有去改變 Child 組件的狀態(tài)仅醇;
第二種情況,我們這個時候用 React.memo
來解決了這個問題魔种,所以這種情況也排除析二。
那么就是第三種情況了,當(dāng)父組件重新渲染的時候节预,傳遞給子組件的 props 發(fā)生了改變叶摄,再看傳遞給 Child 組件的就兩個屬性,一個是 name
安拟,一個是 onClick
蛤吓,name
是傳遞的常量,不會變糠赦,變的就是 onClick
了会傲,為什么傳遞給 onClick 的 callback 函數(shù)會發(fā)生改變呢?其實(shí)在函數(shù)式組件里每次重新渲染拙泽,函數(shù)組件都會重頭開始重新執(zhí)行淌山,那么這兩次創(chuàng)建的 callback 函數(shù)肯定發(fā)生了改變,所以導(dǎo)致了子組件重新渲染顾瞻。
用useCallback解決問題
const callback = () => {
doSomething(a, b);
}
const memoizedCallback = useCallback(callback, [a, b])
復(fù)制代碼
把函數(shù)以及依賴項(xiàng)作為參數(shù)傳入 useCallback
泼疑,它將返回該回調(diào)函數(shù)的 memoized 版本,這個 memoizedCallback 只有在依賴項(xiàng)有變化的時候才會更新荷荤。
那么只需這樣將傳給Child組件callback函數(shù)的改造一下就OK了
const callback = () => { setTitle("標(biāo)題改變了"); };
// 通過 useCallback 進(jìn)行記憶 callback退渗,并將記憶的 callback 傳遞給 Child
<Child onClick={useCallback(callback, [])} name="桃桃" />
復(fù)制代碼
這樣我們就可以看到只會在首次渲染的時候打印出子組件渲染了,當(dāng)點(diǎn)擊改副標(biāo)題和改標(biāo)題的時候是不會打印子組件渲染了的蕴纳。
useMemo
概念
const cacheSomething = useMemo(create,deps)
-
create
:第一個參數(shù)為一個函數(shù)会油,函數(shù)的返回值作為緩存值。 -
deps
: 第二個參數(shù)為一個數(shù)組古毛,存放當(dāng)前 useMemo 的依賴項(xiàng)钞啸,在函數(shù)組件下一次執(zhí)行的時候,會對比 deps 依賴項(xiàng)里面的狀態(tài)喇潘,是否有改變,如果有改變重新執(zhí)行 create 梭稚,得到新的緩存值颖低。 -
cacheSomething
:返回值,執(zhí)行 create 的返回值弧烤。如果 deps 中有依賴項(xiàng)改變忱屑,返回的重新執(zhí)行 create 產(chǎn)生的值蹬敲,否則取上一次緩存值。
useMemo原理
useMemo 會記錄上一次執(zhí)行 create 的返回值莺戒,并把它綁定在函數(shù)組件對應(yīng)的 fiber 對象上伴嗡,只要組件不銷毀,緩存值就一直存在从铲,但是 deps 中如果有一項(xiàng)改變瘪校,就會重新執(zhí)行 create ,返回值作為新的值記錄到 fiber 對象上名段。
示例
function Child(){
console.log("子組件渲染了")
return <div>Child</div>
}
const Child = memo(Child)
function APP(){
const [count, setCount] = useState(0);
const userInfo = {
age: count,
name: 'jimmy'
}
return <Child userInfo={userInfo}>
}
復(fù)制代碼
當(dāng)函數(shù)組件重新render時阱扬,userInfo每次都將是一個新的對象,無論 count
發(fā)生改變沒伸辟,都會導(dǎo)致 Child組件的重新渲染麻惶。
而下面的則會在 count
改變后才會返回新的對象。
function Child(){
console.log("子組件渲染了")
return <div>Child</div>
}
function APP(){
const [count, setCount] = useState(0);
const userInfo = useMemo(() => {
return {
name: "jimmy",
age: count
};
}, [count]);
return <Child userInfo={userInfo}>
}
復(fù)制代碼
實(shí)際上 useMemo 的作用不止于此信夫,根據(jù)官方文檔內(nèi)介紹:以把一些昂貴的計(jì)算邏輯放到 useMemo 中窃蹋,只有當(dāng)依賴值發(fā)生改變的時候才去更新。
import React, {useState, useMemo} from 'react';
// 計(jì)算和的函數(shù)静稻,開銷較大
function calcNumber(count) {
console.log("calcNumber重新計(jì)算");
let total = 0;
for (let i = 1; i <= count; i++) {
total += i;
}
return total;
}
export default function MemoHookDemo01() {
const [count, setCount] = useState(100000);
const [show, setShow] = useState(true);
const total = useMemo(() => {
return calcNumber(count);
}, [count]);
return (
<div>
<h2>計(jì)算數(shù)字的和: {total}</h2>
<button onClick={e => setCount(count + 1)}>+1</button>
<button onClick={e => setShow(!show)}>show切換</button>
</div>
)
}
復(fù)制代碼
當(dāng)我們?nèi)c(diǎn)擊 show切換按鈕時警没,calcNumber這個計(jì)算和的函數(shù)并不會出現(xiàn)渲染了.只有count 發(fā)生改變時,才會出現(xiàn)計(jì)算.
useCallback 和 useMemo 總結(jié)
簡單理解呢 useCallback 與 useMemo 一個緩存的是函數(shù)姊扔,一個緩存的是函數(shù)的返回的結(jié)果惠奸。useCallback 是來優(yōu)化子組件的,防止子組件的重復(fù)渲染恰梢。useMemo 可以優(yōu)化當(dāng)前組件也可以優(yōu)化子組件佛南,優(yōu)化當(dāng)前組件主要是通過 memoize 來將一些復(fù)雜的計(jì)算邏輯進(jìn)行緩存。當(dāng)然如果只是進(jìn)行一些簡單的計(jì)算也沒必要使用 useMemo嵌言。
我們可以將 useMemo 的返回值定義為返回一個函數(shù)這樣就可以變通的實(shí)現(xiàn)了 useCallback嗅回。useCallback(fn, deps)
相當(dāng)于 useMemo(() => fn, deps)
。
useRef
const refContainer = useRef(initialValue);
useRef
返回一個可變的 ref 對象摧茴,其 .current
屬性被初始化為傳入的參數(shù)(initialValue
)绵载。返回的 ref 對象在組件的整個生命周期內(nèi)保持不變
useRef 獲取dom
useRef,它有一個參數(shù)可以作為緩存數(shù)據(jù)的初始值,返回值可以被dom元素ref標(biāo)記苛白,可以獲取被標(biāo)記的元素節(jié)點(diǎn).
import React, { useRef } from "react";
function Example() {
const divRef = useRef();
function changeDOM() {
// 獲取整個div
console.log("整個div", divRef.current);
// 獲取div的class
console.log("div的class", divRef.current.className);
// 獲取div自定義屬性
console.log("div自定義屬性", divRef.current.getAttribute("data-clj"));
}
return (
<div>
<div className="div-class" data-clj="我是div的自定義屬性" ref={divRef}>
我是div
</div>
<button onClick={(e) => changeDOM()}>獲取DOM</button>
</div>
);
}
export default Example;
復(fù)制代碼
useRef 緩存數(shù)據(jù)
useRef還有一個很重要的作用就是緩存數(shù)據(jù)娃豹,我們知道usestate ,useReducer 是可以保存當(dāng)前的數(shù)據(jù)源的,但是如果它們更新數(shù)據(jù)源的函數(shù)執(zhí)行必定會帶來整個組件從新執(zhí)行到渲染购裙,如果在函數(shù)組件內(nèi)部聲明變量懂版,則下一次更新也會重置,如果我們想要悄悄的保存數(shù)據(jù)躏率,而又不想觸發(fā)函數(shù)的更新躯畴,那么useRef是一個很棒的選擇民鼓。
下面舉一個,每次換成state 上一次值的例子
import React, { useRef, useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
const numRef = useRef(count);
useEffect(() => {
numRef.current = count;
}, [count]);
return (
<div>
<h2>count上一次的值: {numRef.current}</h2>
<h2>count這一次的值: {count}</h2>
<button onClick={(e) => setCount(count + 10)}>+10</button>
</div>
);
}
export default Example;
復(fù)制代碼
當(dāng) ref 對象內(nèi)容發(fā)生變化時蓬抄,useRef
并不會通知你丰嘉。變更 .current
屬性不會引發(fā)組件重新渲染。所以嚷缭,上面的例子中雖然numRef.current的值饮亏,已經(jīng)改變了,但是頁面上還是顯示的上一次的值峭状,重新更新時克滴,才會顯示上一次更新的值。