您好,登錄后才能下訂單哦!
hook如何正確的在react中使用?相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
首先介紹關于hook的含義,以及其所要去面對的一些場景
含義:Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。簡單來說就是可以使用函數組件去使用react中的一些特性
所要解決的問題:
解決組件之間復用狀態邏輯很難得問題,hook能解決的就是在你無需修改之前組件結構的情況下復用狀態邏輯,在不使用hook的情況下,需要使用到一些高級的用法如高級組件、provider、customer等,這種方式對于新手來說不太友好,可能在理解上就比較的困難
對于復雜組件可以去拆分其邏輯,例如在你使用生命周期函數時,不同的生命周期需要在不同的時刻進行,因此在此時對于復雜的組件來說,有的生命周期函數中就存在大量的邏輯,在可讀性上面就大打折扣。當使用hook時,就可以進行組件邏輯的劃分,將相同的邏輯給整合在一起,這樣就大大增加可讀性也在一方面利于維護
不需要對于class組件的理解,當你在最初去學習時,你不得不去理解this這個關鍵字,在當前組件所表示的含義,但是在hook中就不需要。能夠解決你在不使用class組件的情況下去體現react的特性
需要注意的一點就是hook和class組件是不能夠同時使用的,在實際的使用過程中一定要注意,否則就會出現報錯
那么接下來所要介紹的部分就是如何去使用hook
對于使用過class組件的同學,相信對于state肯定有很深的印象,對于一些需要用到的全局變量,在class組件中我們常常采用的方式是this.state = {},但是在hook中我們采用的方式就是使用useState這個hook,然后就可以對這種全局變量進行引用,在引用時只需要用其變量名即可,這里就拿官網的例子來舉例:
import React, { useState } from 'react';
import React, { useState } from 'react'; function Example() { // 聲明一個叫 "count" 的 state 變量 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
在上面的這個例子中,我們設置變量方式采用的就是const [count, setCount] = useState(0)這種方式,其中的0就是給count賦初值為0,如果想要給count賦值為一個空對象,那么只需要const [count, setCount] = useState({}),這樣的方式就行了,那么這樣你在用count時,此時獲取到的值就為一個空對象。
作用:返回一個state,以及更新state的函數
函數式更新:新的state需要通過使用先前的state計算得出,將函數傳遞給setState,該函數將接收先前的state,并返回一個更新后的值
惰性初始state,initialState參數只會在組件的初始渲染中起作用,如果初始化state需要通過一個復雜計算來獲取,則可以傳入一個函數,在函數中計算并返回初始的state,此函數只在初始渲染時被掉用,如下所示:
const [state, setState] = useState(() => { const initialState = someExpensiveComputation(props); return initialState; })
在class組件中我們給放在state中的變量賦值時,通常采用的方式就是this.setState()這種方式,那么在hook中所要采用的就是set+變量名這種方式,如
const [count, setCount] = useState(0)
在這里通過上面我們已經知道的就是count能夠獲取到值,那么其所對應的setCount(值),這種賦值的方式就是給count變量賦值的,然后通過count就能夠獲取到值。
為什么要采用這種方式呢?
原因:是因為react中的單向數據源,這樣的話,能夠保證你的數據源流向會更加的清楚,這也是react所區別于vue中雙向數據源綁定的一點
在hook中,如果我們需要去設置多個類似于上面所說的count,那么就需要多次使用useState這個hook,當然你也可以設置一個變量在hook的最外部,即在hook這個函數組件的外部。需要注意的是別在整個hook這個函數的全局設置,因此在hook的運行機制中,在每次加載時,都會從新去加載里面的變量,因此你是不能夠去獲取到在整個函數內部中使用該變量所改變的值的,能夠獲取到的就只是這個變量的初始值*
對于useEffect hook,其用途類似于class組件中的生命周期函數,用來處理在一些特定時刻需要去做的事情,這種事情常被叫做副作用。在使用useEffect這個hook時,需要注意的一點就是其不能夠被包含在循環,判斷語句中,否則項目會出現報錯,這也是hook的一種設置機制
副作用的劃分:
不需要清除的: 在React更新DOM之后運行一些額外的代碼:如:發送網絡請求,手動變更DOM,記錄日志等
需要清除的:當使用外部數據源時,需要去清除數據,如:定時器,需要我們在結束的時候去清除
渲染時機:在使用useEffect這個hook時,需要注意的就是其渲染的時機,默認情況下會在第一次渲染和每一次更新時去執行。對于如何去控制這個渲染時機,在下面的一個部分會有詳細的介紹
作用:告訴組件在渲染之后執行某些操作
useEffect放在組件內部調用的原因:可以在effect中直接訪問state中的變量
effect返回函數:effect可選的清除機制,每個effect都可以返回一個清除函數
接收內容:一個包含命令式、并且可能有副作用代碼的函數
清除effect:實現方式,effect函數需要返回一個清除函數
effect執行時機:在瀏覽器完成布局和繪制之后,傳給useEffect的函數會延遲調用,因此不應該在函數中執行足賽瀏覽器更新屏幕的操作。
默認條件執行:會在每輪組件渲染完成后執行,因而一旦effect的依賴發生變化,他就會被重新創建。要改變其執行時機,需要給useEffect傳遞第二個參數,只有當第二個參數值發生改變才會重新創建訂閱。如果要使用這個優化的方式,需要確保數組包含了所有外部作用域中會發發生變化,且在effect中使用的變量。如果只想運行一次effect,可以傳遞一個空數組作為第二個參數。
對于useEffect的初步認識只需要了解上面的即可。接下來就來介紹一個官網的實例,來說明useEffect
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // Similar to componentDidMount and componentDidUpdate: useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
在上面的這段代碼中,就使用到了useEffect這個hook,在每次count值改變時,就會在頁面中去打印“You clicked ${count} times”這段文字,當然count肯定對應的就是其所對應的值。
react中有狀態組件中,其生命周期函數的各個階段
在Mounting階段
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
Updating
static getDerivedStateFormProps
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
UnMouting
componentWillUnmount()
這里就介紹了關于useEffect這個hook的使用,有一些生命周期函數就是通過該hook來實現的,這里推薦一篇文章https://blog.logrocket.com/guide-to-react-useeffect-hook/,可以參考下。這里是在參考了一些文章后寫的,具體介紹如下:
constructor: 可以通過useState來初始化state
componentDidMount(),在hook中需要使用下面的這種方式去取代,在useEffect中傳遞第二個參數,該參數為一個空數組,只會去執行一次,如下面所示
useEffect(() => { },[])
componentDidUpdate(),有兩種方式去解決
在每次渲染的時候都去調用hooks,解決的方式如下面所示
useEffect(() => { })
用一個特殊變量的去觸發hook,如下面所示,count指的就是這個特殊的變量,該hook觸發,只會是count的值改變時
useEffect(() => { },[count])
componentWillUnmount(),用hook來代替,需要去return一個callback(回調函數),如下面的形式所示
useEffect(() => { return () => { //執行的為componentWillUnmount } },[])
shouldComponentUpdata(),常使用React.memo來代替,在默認情況下,它將對props對象中的復雜對象進行淺層比較,如果想要去控制比較,可以去提供一個自定義的比較函數作為第二個參數。代替hook的方式如下所示
import React from 'react' function areEqual(prevProps, nextProps) { /* return true if passing nextProps to render would return the same result as passing prevProps to render, otherwise return false */ } const Weather = ({weather}) => { return (<div> <p>{weather.city}</p> <p>{weather.temperature}</p> {console.log('Render')} </div> ) } export default React.memo(Weather, areEqual)
通常在實際的項目開發中少不了使這種自定義的hook,前提是在整個項目中使用了hook的情況下。通常情況下就是去使用useState,useEffect這種系統已經定義好的hook去實現,在調用時你就可以直接調用當你自定義好的hook來實現你所需要的功能。下面就以自定義useReducer這個hook為例
import React, { useEffect } from 'react' function useReducer(reducer, initialState) { const [state, setState] = useState(initialState); function dispatch(action) { const nextState = reducer(state, action); setState(nextState); } return [state, dispatch]; } export default useReducer
在上面的這個實際例子中,我們封裝了一個自定義的useReducerhook,我們可以調用這個hook去完成與reducer一樣的功能了,在調用是就需要我們去傳入兩個參數,一個就是reducer,另外一個就是initialState,然后就能夠取得state,以及dispatch方法。注意這里的返回值使用的是一個數組,這樣的好處就是我們在獲取其返回值時,可以采用數組結構這種方式來獲取。具體關于數組的結構可以去看看es6中的部分,就能夠明白了。那么接下來就是使用這個自定義好的useReducer。使用方式如下
import useReducer form '你封裝useRecuer的組件中' function Todos() { const todosReducer = ( state, dispatch) => { if(dispatch.type == "") { //type值為什么時去執行 const newState == "" //執行一些操作,去更新state return newState //返回新的neState } } const [todos, dispatch] = useReducer(todosReducer, []); function handleAddClick(text) { dispatch({ type: 'add', text }); } return ( <div></div> ) }
這里并沒有把實際的使用情況給寫完,剩余的可以自己去補充,其使用方式就和redux的使用方式相同。這就是整個自定義hook以及去使用的過程,在實際的開發中可以去體驗體驗。
useReducer,能給那些會出發深更新的組件做性能優化,因為可以向子組件去傳遞dispatch而不是回調
useReducer這個hook的封裝,整個封裝的方法如下:
//reducer hook封裝 import { useState } from 'react'; export default useReducer function(reducer, initialState) { const [state, setState] = useState(initialState); function dispatch(action){ const nextState = reducer(state, action); return setState(nextState); } return [state, dispatch] } //實際例子使用 import useReducer from ''; const initialState = {count: 0}; 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(); } } return ( <div> Count: {state.count} <button onClick={() => dispatch({type: 'devrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </div> )
useReducer的惰性初始化,可以選擇惰性地創建初始化state。因此需要設置一個初始化函數作為useReducer的第三個參數傳入,這樣初始化state將設置為init(initialArg),如下所示,就是一個實際的案例在useReducer中去傳遞第三個參數
function init(initialCount) { return {count: initialCount}; } function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; case 'reset': return init(action.payload); default: throw new Error(); } } function Counter({initialCount}) { const [state, dispatch] = useReducer(reducer, initialCount, init); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'reset', payload: initialCount})}> Reset </button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }
注意:如果reducer hook的返回值與當前state相同,react將跳過子組件的渲染及副作用的執行
useCallback
返回值:返回一個memoized回調函數,該回調函數僅在某給依賴項改變時才會更新。
含義:把內聯回調函數及其依賴項數組作為參數傳入useCallback,它將返回該回調函數傳遞給經過優化的并使用引用相等性去避免非必要渲染
useCallBack(fn, deps)相當與useMemo(() => fn,deps)
useMemo
使用方式:const memoziedValue = useMemo(() => computeExpensiveValue(a,b), [a, b])
返回值:返回一個memoized值,把創建函數和依賴項數組作為參數傳入useMemo,僅在某個依賴項改變時才重新計算memoized值。
好處:這種優化有助于避免在每次渲染時都進行高開銷的計算
渲染方式:傳入useMemo的函數會在渲染期間執行,不要在這個函數內部執行與渲染無關的操作,如屬于useEffect中的副作用。如果沒有,那么新的值將會在每次渲染時被重新渲染
注意:依賴項數組不會作為參數傳遞給函數,概述來說,就是每一個出現在函數中的參數也應該出現在依賴項的數組中
useRef
使用方式: const refContainer = useref(initialValue);
返回值:返回一個可ref對象,其.current屬性被初始化為傳入的參數(initialValue)。這返回的的對象將在組件的整個生命周期中持續
含義: useRef就像是一個盒子可以將.current中得可變屬性給保存起來
ref與useRef的區別在于,后者是創建的了一個普通的js對象,useRef和自建一個{current: …。}對象的唯一區別是,useRef會在每次渲染時,返回同一個ref對象
useImperativeHandle
作用:可以在使用ref時自定義暴露給賦組件的實例值,使用的形式如下:
useImperativeHandle(ref, createHandle, [deps])
useLayoutEffect
更新時機:在瀏覽器執行下一次繪制前去執行
與useEffect相同,會在所有的DOM變更之后同步調用effect
useDebugValue
作用:在react devTools中常被用于去當作展示標簽,作為客戶端的鉤子
在hook中,其性能優化的點很多,這個可以在一些https://react.docschina.org/docs/hooks-faq.html#performance-optimizations去學習,下面是我看的一部分。
如何在更新時去跳過effect,可以采用條件式方式,即在useEffect中去傳遞第二個參數
由于某些原因,無法將一個函數移動到effect內部時,可采用下面方式
嘗試將函數移動到當前組件的外部
如果所調用對策方法是一個純計算等,此時可以在effect外面去寫這個函數
如果要增加一個函數去依賴項,那么要明確使用useCallback外部的hook,如下面的例子所示
function ProductPage({ productId }) { // Wrap with useCallback to avoid change on every render const fetchProduct = useCallback(() => { // ... Does something with productId ... }, [productId]); // All useCallback dependencies are specified return <ProductDetails fetchProduct={fetchProduct} />; } function ProductDetails({ fetchProduct }) { useEffect(() => { fetchProduct(); }, [fetchProduct]); // All useEffect dependencies are specified // ... }
實現shouldComponentUpdate的方式
const Button = React.memo((props) => { // your component });
如上面所示,這種實現方式并不是使用了hooks,它相當于純組件,但是僅僅能夠比較的是props。可以去增加第二個參數,采用一種函數的方式去拿到新老的props,如果結果返回true,就跳過更新階段
記住計算結果的方式
使用useMemo這個hook去記住之前的計算結果,從而在多個渲染之中緩存計算
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
上面的代碼會調用computeExpensiveValue(a,b)這個函數,但是它們依賴的a,b沒有改變,那么useMemo在直接去返回上一次結果的值
看完上述內容,你們掌握hook如何正確的在react中使用的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。