您好,登錄后才能下訂單哦!
這篇文章主要介紹了react中一共有多少個(gè)hooks,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
react共有9個(gè)hooks:useState、useEffect、useContext、useReducer、useCallback、useMemo、useRef、useImperativeHandle、useLayoutEffect。
本教程操作環(huán)境:Windows7系統(tǒng)、react17.0.1版、Dell G3電腦。
在 React 的世界中,有容器組件和 UI 組件之分,在 React Hooks 出現(xiàn)之前,UI 組件我們可以使用函數(shù),無(wú)狀態(tài)組件來(lái)展示 UI,而對(duì)于容器組件,函數(shù)組件就顯得無(wú)能為力,我們依賴于類(lèi)組件來(lái)獲取數(shù)據(jù),處理數(shù)據(jù),并向下傳遞參數(shù)給 UI 組件進(jìn)行渲染。在我看來(lái),使用 React Hooks 相比于從前的類(lèi)組件有以下幾點(diǎn)好處:
代碼可讀性更強(qiáng),原本同一塊功能的代碼邏輯被拆分在了不同的生命周期函數(shù)中,容易使開(kāi)發(fā)者不利于維護(hù)和迭代,通過(guò) React Hooks 可以將功能代碼聚合,方便閱讀維護(hù)
組件樹(shù)層級(jí)變淺,在原本的代碼中,我們經(jīng)常使用 HOC/render props 等方式來(lái)復(fù)用組件的狀態(tài),增強(qiáng)功能等,無(wú)疑增加了組件樹(shù)層數(shù)及渲染,而在 React Hooks 中,這些功能都可以通過(guò)強(qiáng)大的自定義的 Hooks 來(lái)實(shí)現(xiàn)
React 在 v16.8 的版本中推出了 React Hooks 新特性,雖然社區(qū)還沒(méi)有最佳實(shí)踐如何基于 React Hooks 來(lái)打造復(fù)雜應(yīng)用(至少我還沒(méi)有),憑借著閱讀社區(qū)中大量的關(guān)于這方面的文章,下面我將通過(guò)十個(gè)案例來(lái)幫助你認(rèn)識(shí)理解并可以熟練運(yùn)用 React Hooks 大部分特性。
在類(lèi)組件中,我們使用 this.state
來(lái)保存組件狀態(tài),并對(duì)其修改觸發(fā)組件重新渲染。比如下面這個(gè)簡(jiǎn)單的計(jì)數(shù)器組件,很好詮釋了類(lèi)組件如何運(yùn)行:
import React from "react"; class App extends React.Component { constructor(props) { super(props); this.state = { count: 0, name: "alife" }; } render() { const { count } = this.state; return ( <p> Count: {count} <button onClick={() => this.setState({ count: count + 1 })}>+</button> <button onClick={() => this.setState({ count: count - 1 })}>-</button> </p> ); } }
一個(gè)簡(jiǎn)單的計(jì)數(shù)器組件就完成了,而在函數(shù)組件中,由于沒(méi)有 this 這個(gè)黑魔法,React 通過(guò) useState 來(lái)幫我們保存組件的狀態(tài)。
import React, { useState } from "react"; function App() { const [obj, setObject] = useState({ count: 0, name: "alife" }); return ( <p className="App"> Count: {obj.count} <button onClick={() => setObject({ ...obj, count: obj.count + 1 })}>+</button> <button onClick={() => setObject({ ...obj, count: obj.count - 1 })}>-</button> </p> ); }
通過(guò)傳入 useState 參數(shù)后返回一個(gè)帶有默認(rèn)狀態(tài)和改變狀態(tài)函數(shù)的數(shù)組。通過(guò)傳入新?tīng)顟B(tài)給函數(shù)來(lái)改變?cè)镜臓顟B(tài)值。值得注意的是 useState 不幫助你處理狀態(tài),相較于 setState 非覆蓋式更新?tīng)顟B(tài),useState 覆蓋式更新?tīng)顟B(tài),需要開(kāi)發(fā)者自己處理邏輯。(代碼如上)
似乎有個(gè) useState 后,函數(shù)組件也可以擁有自己的狀態(tài)了,但僅僅是這樣完全不夠。
函數(shù)組件能保存狀態(tài),但是對(duì)于異步請(qǐng)求,副作用的操作還是無(wú)能為力,所以 React 提供了 useEffect 來(lái)幫助開(kāi)發(fā)者處理函數(shù)組件的副作用,在介紹新 API 之前,我們先來(lái)看看類(lèi)組件是怎么做的:
import React, { Component } from "react"; class App extends Component { state = { count: 1 }; componentDidMount() { const { count } = this.state; document.title = "componentDidMount" + count; this.timer = setInterval(() => { this.setState(({ count }) => ({ count: count + 1 })); }, 1000); } componentDidUpdate() { const { count } = this.state; document.title = "componentDidMount" + count; } componentWillUnmount() { document.title = "componentWillUnmount"; clearInterval(this.timer); } render() { const { count } = this.state; return ( <p> Count:{count} <button onClick={() => clearInterval(this.timer)}>clear</button> </p> ); } }
在例子中,組件每隔一秒更新組件狀態(tài),并且每次觸發(fā)更新都會(huì)觸發(fā) document.title 的更新(副作用),而在組件卸載時(shí)修改 document.title(類(lèi)似于清除)
從例子中可以看到,一些重復(fù)的功能開(kāi)發(fā)者需要在 componentDidMount 和 componentDidUpdate 重復(fù)編寫(xiě),而如果使用 useEffect 則完全不一樣。
import React, { useState, useEffect } from "react"; let timer = null; function App() { const [count, setCount] = useState(0); useEffect(() => { document.title = "componentDidMount" + count; },[count]); useEffect(() => { timer = setInterval(() => { setCount(prevCount => prevCount + 1); }, 1000); // 一定注意下這個(gè)順序: // 告訴react在下次重新渲染組件之后,同時(shí)是下次執(zhí)行上面setInterval之前調(diào)用 return () => { document.title = "componentWillUnmount"; clearInterval(timer); }; }, []); return ( <p> Count: {count} <button onClick={() => clearInterval(timer)}>clear</button> </p> ); }
我們使用 useEffect 重寫(xiě)了上面的例子,useEffect 第一個(gè)參數(shù)接收一個(gè)函數(shù),可以用來(lái)做一些副作用比如異步請(qǐng)求,修改外部參數(shù)等行為,而第二個(gè)參數(shù)稱之為dependencies,是一個(gè)數(shù)組,如果數(shù)組中的值變化才會(huì)觸發(fā) 執(zhí)行useEffect 第一個(gè)參數(shù)中的函數(shù)。返回值(如果有)則在組件銷(xiāo)毀或者調(diào)用函數(shù)前調(diào)用。
1.比如第一個(gè) useEffect 中,理解起來(lái)就是一旦 count 值發(fā)生改變,則修改 documen.title 值;
2.而第二個(gè) useEffect 中傳遞了一個(gè)空數(shù)組[],這種情況下只有在組件初始化或銷(xiāo)毀的時(shí)候才會(huì)觸發(fā),用來(lái)代替 componentDidMount 和 componentWillUnmount,慎用;
還有另外一個(gè)情況,就是不傳遞第二個(gè)參數(shù),也就是useEffect只接收了第一個(gè)函數(shù)參數(shù),代表不監(jiān)聽(tīng)任何參數(shù)變化。每次渲染DOM之后,都會(huì)執(zhí)行useEffect中的函數(shù)。
基于這個(gè)強(qiáng)大 Hooks,我們可以模擬封裝出其他生命周期函數(shù),比如 componentDidUpdate 代碼十分簡(jiǎn)單
function useUpdate(fn) { // useRef 創(chuàng)建一個(gè)引用 const mounting = useRef(true); useEffect(() => { if (mounting.current) { mounting.current = false; } else { fn(); } }); }
現(xiàn)在我們有了 useState 管理狀態(tài),useEffect 處理副作用,異步邏輯,學(xué)會(huì)這兩招足以應(yīng)對(duì)大部分類(lèi)組件的使用場(chǎng)景。
上面介紹了 useState、useEffect 這兩個(gè)最基本的 API,接下來(lái)介紹的 useContext 是 React 幫你封裝好的,用來(lái)處理多層級(jí)傳遞數(shù)據(jù)的方式,在以前組件樹(shù)種,跨層級(jí)祖先組件想要給孫子組件傳遞數(shù)據(jù)的時(shí)候,除了一層層 props 往下透?jìng)髦猓覀冞€可以使用 React Context API 來(lái)幫我們做這件事,舉個(gè)簡(jiǎn)單的例子:
const { Provider, Consumer } = React.createContext(null); function Bar() { return <Consumer>{color => <p>{color}</p>}</Consumer>; } function Foo() { return <Bar />; } function App() { return ( <Provider value={"grey"}> <Foo /> </Provider> ); }
通過(guò) React createContext 的語(yǔ)法,在 APP 組件中可以跨過(guò) Foo 組件給 Bar 傳遞數(shù)據(jù)。而在 React Hooks 中,我們可以使用 useContext 進(jìn)行改造。
const colorContext = React.createContext("gray"); function Bar() { const color = useContext(colorContext); return <p>{color}</p>; } function Foo() { return <Bar />; } function App() { return ( <colorContext.Provider value={"red"}> <Foo /> </colorContext.Provider> ); }
傳遞給 useContext 的是 context 而不是 consumer,返回值即是想要透?jìng)鞯臄?shù)據(jù)了。用法很簡(jiǎn)單,使用 useContext 可以解決 Consumer 多狀態(tài)嵌套的問(wèn)題。
function HeaderBar() { return ( <CurrentUser.Consumer> {user => <Notifications.Consumer> {notifications => <header> Welcome back, {user.name}! You have {notifications.length} notifications. </header> } } </CurrentUser.Consumer> ); }
而使用 useContext 則變得十分簡(jiǎn)潔,可讀性更強(qiáng)且不會(huì)增加組件樹(shù)深度。
function HeaderBar() { const user = useContext(CurrentUser); const notifications = useContext(Notifications); return ( <header> Welcome back, {user.name}! You have {notifications.length} notifications. </header> ); }
useReducer 這個(gè) Hooks 在使用上幾乎跟 Redux/React-Redux 一模一樣,唯一缺少的就是無(wú)法使用 redux 提供的中間件。我們將上述的計(jì)時(shí)器組件改寫(xiě)為 useReducer,
import React, { useReducer } from "react"; const initialState = { count: 0 }; function reducer(state, action) { switch (action.type) { case "increment": return { count: state.count + action.payload }; case "decrement": return { count: state.count - action.payload }; default: throw new Error(); } } function App() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({ type: "increment", payload: 5 })}> + </button> <button onClick={() => dispatch({ type: "decrement", payload: 5 })}> - </button> </> ); }
用法跟 Redux 基本上是一致的,用法也很簡(jiǎn)單,算是提供一個(gè) mini 的 Redux 版本。
在類(lèi)組件中,我們經(jīng)常犯下面這樣的錯(cuò)誤:
class App { render() { return <p> <SomeComponent style={{ fontSize: 14 }} doSomething={ () => { console.log('do something'); }} /> </p>; } }
這樣寫(xiě)有什么壞處呢?一旦 App 組件的 props 或者狀態(tài)改變了就會(huì)觸發(fā)重渲染,即使跟 SomeComponent 組件不相關(guān),由于每次 render 都會(huì)產(chǎn)生新的 style 和 doSomething(因?yàn)橹匦聄ender前后, style 和 doSomething分別指向了不同的引用),所以會(huì)導(dǎo)致 SomeComponent 重新渲染,倘若 SomeComponent 是一個(gè)大型的組件樹(shù),這樣的 Virtual Dom 的比較顯然是很浪費(fèi)的,解決的辦法也很簡(jiǎn)單,將參數(shù)抽離成變量。
const fontSizeStyle = { fontSize: 14 }; class App { doSomething = () => { console.log('do something'); } render() { return <p> <SomeComponent style={fontSizeStyle} doSomething={ this.doSomething } /> </p>; } }
在類(lèi)組件中,我們還可以通過(guò) this 這個(gè)對(duì)象來(lái)存儲(chǔ)函數(shù),而在函數(shù)組件中沒(méi)辦法進(jìn)行掛載了。所以函數(shù)組件在每次渲染的時(shí)候如果有傳遞函數(shù)的話都會(huì)重渲染子組件。
function App() { const handleClick = () => { console.log('Click happened'); } return <SomeComponent onClick={handleClick}>Click Me</SomeComponent>; }
這里多說(shuō)一句,一版把函數(shù)式組件理解為class組件render函數(shù)的語(yǔ)法糖,所以每次重新渲染的時(shí)候,函數(shù)式組件內(nèi)部所有的代碼都會(huì)重新執(zhí)行一遍。所以上述代碼中每次render,handleClick都會(huì)是一個(gè)新的引用,所以也就是說(shuō)傳遞給SomeComponent組件的props.onClick一直在變(因?yàn)槊看味际且粋€(gè)新的引用),所以才會(huì)說(shuō)這種情況下,函數(shù)組件在每次渲染的時(shí)候如果有傳遞函數(shù)的話都會(huì)重渲染子組件。
而有了 useCallback 就不一樣了,你可以通過(guò) useCallback 獲得一個(gè)記憶后的函數(shù)。
function App() { const memoizedHandleClick = useCallback(() => { console.log('Click happened') }, []); // 空數(shù)組代表無(wú)論什么情況下該函數(shù)都不會(huì)發(fā)生改變 return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>; }
老規(guī)矩,第二個(gè)參數(shù)傳入一個(gè)數(shù)組,數(shù)組中的每一項(xiàng)一旦值或者引用發(fā)生改變,useCallback 就會(huì)重新返回一個(gè)新的記憶函數(shù)提供給后面進(jìn)行渲染。
這樣只要子組件繼承了 PureComponent 或者使用 React.memo 就可以有效避免不必要的 VDOM 渲染。
useCallback 的功能完全可以由 useMemo 所取代,如果你想通過(guò)使用 useMemo 返回一個(gè)記憶函數(shù)也是完全可以的。
useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs).
所以前面使用 useCallback 的例子可以使用 useMemo 進(jìn)行改寫(xiě):
function App() { const memoizedHandleClick = useMemo(() => () => { console.log('Click happened') }, []); // 空數(shù)組代表無(wú)論什么情況下該函數(shù)都不會(huì)發(fā)生改變 return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>; }
唯一的區(qū)別是:**useCallback 不會(huì)執(zhí)行第一個(gè)參數(shù)函數(shù),而是將它返回給你,而 useMemo 會(huì)執(zhí)行第一個(gè)函數(shù)并且將函數(shù)執(zhí)行結(jié)果返回給你。**所以在前面的例子中,可以返回 handleClick 來(lái)達(dá)到存儲(chǔ)函數(shù)的目的。
所以 useCallback 常用記憶事件函數(shù),生成記憶后的事件函數(shù)并傳遞給子組件使用。而 useMemo 更適合經(jīng)過(guò)函數(shù)計(jì)算得到一個(gè)確定的值,比如記憶組件。
function Parent({ a, b }) { // Only re-rendered if `a` changes: const child1 = useMemo(() => <Child1 a={a} />, [a]); // Only re-rendered if `b` changes: const child2 = useMemo(() => <Child2 b= />, [b]); return ( <> {child1} {child2} </> ) }
當(dāng) a/b 改變時(shí),child1/child2 才會(huì)重新渲染。從例子可以看出來(lái),只有在第二個(gè)參數(shù)數(shù)組的值發(fā)生變化時(shí),才會(huì)觸發(fā)子組件的更新。
useRef 跟 createRef 類(lèi)似,都可以用來(lái)生成對(duì) DOM 對(duì)象的引用,看個(gè)簡(jiǎn)單的例子:
import React, { useState, useRef } from "react"; function App() { let [name, setName] = useState("Nate"); let nameRef = useRef(); const submitButton = () => { setName(nameRef.current.value); }; return ( <p className="App"> <p>{name}</p> <p> <input ref={nameRef} type="text" /> <button type="button" onClick={submitButton}> Submit </button> </p> </p> ); }
useRef 返回的值傳遞給組件或者 DOM 的 ref 屬性,就可以通過(guò) ref.current 值訪問(wèn)組件或真實(shí)的 DOM 節(jié)點(diǎn),重點(diǎn)是組件也是可以訪問(wèn)到的,從而可以對(duì) DOM 進(jìn)行一些操作,比如監(jiān)聽(tīng)事件等等。
當(dāng)然 useRef 遠(yuǎn)比你想象中的功能更加強(qiáng)大,useRef 的功能有點(diǎn)像類(lèi)屬性,或者說(shuō)您想要在組件中記錄一些值,并且這些值在稍后可以更改。
利用 useRef 就可以繞過(guò) Capture Value 的特性。可以認(rèn)為 ref 在所有 Render 過(guò)程中保持著唯一引用,因此所有對(duì) ref 的賦值或取值,拿到的都只有一個(gè)最終狀態(tài),而不會(huì)在每個(gè) Render 間存在隔離。
React Hooks 中存在 Capture Value 的特性:
function App() { const [count, setCount] = useState(0); useEffect(() => { setTimeout(() => { alert("count: " + count); }, 3000); }, [count]); return ( <p> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>增加 count</button> <button onClick={() => setCount(count - 1)}>減少 count</button> </p> ); }
先點(diǎn)擊增加button,后點(diǎn)擊減少button,3秒后先alert 1,后alert 0,而不是alert兩次0。這就是所謂的 capture value 的特性。而在類(lèi)組件中 3 秒后輸出的就是修改后的值,因?yàn)檫@時(shí)候** message 是掛載在 this 變量上,它保留的是一個(gè)引用值**,對(duì) this 屬性的訪問(wèn)都會(huì)獲取到最新的值。講到這里你應(yīng)該就明白了,useRef 創(chuàng)建一個(gè)引用,就可以有效規(guī)避 React Hooks 中 Capture Value 特性。
function App() { const count = useRef(0); const showCount = () => { alert("count: " + count.current); }; const handleClick = number => { count.current = count.current + number; setTimeout(showCount, 3000); }; return ( <p> <p>You clicked {count.current} times</p> <button onClick={() => handleClick(1)}>增加 count</button> <button onClick={() => handleClick(-1)}>減少 count</button> </p> ); }
只要將賦值與取值的對(duì)象變成 useRef,而不是 useState,就可以躲過(guò) capture value 特性,在 3 秒后得到最新的值。
通過(guò) useImperativeHandle 用于讓父組件獲取子組件內(nèi)的索引
import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react"; function ChildInputComponent(props, ref) { const inputRef = useRef(null); useImperativeHandle(ref, () => inputRef.current); return <input type="text" name="child input" ref={inputRef} />; } const ChildInput = forwardRef(ChildInputComponent); function App() { const inputRef = useRef(null); useEffect(() => { inputRef.current.focus(); }, []); return ( <p> <ChildInput ref={inputRef} /> </p> ); }
通過(guò)這種方式,App 組件可以獲得子組件的 input 的 DOM 節(jié)點(diǎn)。
大部分情況下,使用 useEffect 就可以幫我們處理組件的副作用,但是如果想要同步調(diào)用一些副作用,比如對(duì) DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用會(huì)在 DOM 更新之后同步執(zhí)行。
function App() { const [width, setWidth] = useState(0); useLayoutEffect(() => { const title = document.querySelector("#title"); const titleWidth = title.getBoundingClientRect().width; console.log("useLayoutEffect"); if (width !== titleWidth) { setWidth(titleWidth); } }); useEffect(() => { console.log("useEffect"); }); return ( <p> <h2 id="title">hello</h2> <h3>{width}</h3> </p> ); }
在上面的例子中,useLayoutEffect 會(huì)在 render,DOM 更新之后同步觸發(fā)函數(shù),會(huì)優(yōu)于 useEffect 異步觸發(fā)函數(shù)。
簡(jiǎn)單來(lái)說(shuō)就是調(diào)用時(shí)機(jī)不同,useLayoutEffect
和原來(lái)componentDidMount
&componentDidUpdate
一致,在react完成DOM更新后馬上同步調(diào)用的代碼,會(huì)阻塞頁(yè)面渲染。而useEffect
是會(huì)在整個(gè)頁(yè)面渲染完才會(huì)調(diào)用的代碼。
官方建議優(yōu)先使用useEffect
However, we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.
在實(shí)際使用時(shí)如果想避免頁(yè)面抖動(dòng)(在useEffect
里修改DOM很有可能出現(xiàn))的話,可以把需要操作DOM的代碼放在useLayoutEffect
里。關(guān)于使用useEffect
導(dǎo)致頁(yè)面抖動(dòng)。
不過(guò)useLayoutEffect
在服務(wù)端渲染時(shí)會(huì)出現(xiàn)一個(gè)warning,要消除的話得用useEffect
代替或者推遲渲染時(shí)機(jī)。
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“react中一共有多少個(gè)hooks”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。