前言
最近由于公司的專案開發,就學習了在react關于hook的使用,對其有個基本的認識以及如何在專案中去應用hook,在這篇博客中主要從以下的幾個點進行介紹:
- hook簡介
- hook中常用api的使用
- hook在使用程序中需要去注意的地方
- hook中怎樣去實作class組件中的宣告周期函式
hook
首先介紹關于hook的含義,以及其所要去面對的一些場景
- 含義:Hook 是 React 16.8 的新增特性,它可以讓你在不撰寫 class 的情況下使用 state 以及其他的 React 特性,簡單來說就是可以使用函陣列件去使用react中的一些特性
- 所要解決的問題:
- 解決組件之間復用狀態邏輯很難得問題,hook能解決的就是在你無需修改之前組件結構的情況下復用狀態邏輯,在不使用hook的情況下,需要使用到一些高級的用法如高級組件、provider、customer等,這種方式對于新手來說不太友好,可能在理解上就比較的困難
- 對于復雜組件可以去拆分其邏輯,例如在你使用生命周期函式時,不同的生命周期需要在不同的時刻進行,因此在此時對于復雜的組件來說,有的生命周期函式中就存在大量的邏輯,在可讀性上面就大打折扣,當使用hook時,就可以進行組件邏輯的劃分,將相同的邏輯給整合在一起,這樣就大大增加可讀性也在一方面利于維護
- 不需要對于class組件的理解,當你在最初去學習時,你不得不去理解this這個關鍵字,在當前組件所表示的含義,但是在hook中就不需要,能夠解決你在不使用class組件的情況下去體現react的特性
- 需要注意的一點就是hook和class組件是不能夠同時使用的,在實際的使用程序中一定要注意,否則就會出現報錯
那么接下來所要介紹的部分就是如何去使用hook
state hook
- 對于使用過class組件的同學,相信對于state肯定有很深的印象,對于一些需要用到的全域變數,在class組件中我們常常采用的方式是this.state = {},但是在hook中我們采用的方式就是使用useState這個hook,然后就可以對這種全域變數進行參考,在參考時只需要用其變數名即可,這里就拿官網的例子來舉例:
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;
})
在hook中如何給全域變數設定值
在class組件中我們給放在state中的變數賦值時,通常采用的方式就是this.setState()這種方式,那么在hook中所要采用的就是set+變數名這種方式,如
const [count, setCount] = useState(0)
在這里通過上面我們已經知道的就是count能夠獲取到值,那么其所對應的setCount(值),這種賦值的方式就是給count變數賦值的,然后通過count就能夠獲取到值,
- 為什么要采用這種方式呢?
- 原因:是因為react中的單向資料源,這樣的話,能夠保證你的資料源流向會更加的清楚,這也是react所區別于vue中雙向資料源系結的一點
hook中設定多個全域變數的方式
在hook中,如果我們需要去設定多個類似于上面所說的count,那么就需要多次使用useState這個hook,當然你也可以設定一個變數在hook的最外部,即在hook這個函陣列件的外部,需要注意的是別在整個hook這個函式的全域設定,因此在hook的運行機制中,在每次加載時,都會從新去加載里面的變數,因此你是不能夠去獲取到在整個函式內部中使用該變數所改變的值的,能夠獲取到的就只是這個變數的初始值*
useEffect 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肯定對應的就是其所對應的值,
useEffect去取代calss中的生命周期函式的方式
react中有狀態組件中,其生命周期函式的各個階段
- 在Mounting階段
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
- Updating
- static getDerivedStateFormProps
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
- UnMouting
- componentWillUnmount()
使用hook去代替生命周期函式的方式
這里就介紹了關于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,前提是在整個專案中使用了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以及去使用的程序,在實際的開發中可以去體驗體驗,
額外的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中常被用于去當作展示標簽,作為客戶端的鉤子
hooks中的性能優化
在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的學習大概就如上面所說,對于hook其中的內容還很多所以對于hook的學習最好是去官網看看,鏈接如下https://react.docschina.org/docs/hooks-intro.html在官網中介紹的更加詳細,這里的中文檔案和英文檔案內容都一樣,不過對于英文好的同學建議看看英文版本,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/229218.html
標籤:其他
上一篇:單點登陸的三種實作方式
下一篇:CSS3+JS完美實作放大鏡模式
