新聞中心
前言
Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。本文是一篇以實戰(zhàn)為主的文章,主要講解實際項目中如何使用hooks以及一些最佳實踐,不會一步步再介紹一遍react hooks的由來和基本使用,因為寫hooks的文章很多,而且官網(wǎng)對于react hooks的介紹也很詳細,所以大家不熟悉的可以看一遍官網(wǎng)。

創(chuàng)新互聯(lián)公司于2013年成立,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項目做網(wǎng)站、成都網(wǎng)站建設(shè)網(wǎng)站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元巴彥淖爾做網(wǎng)站,已為上家服務(wù),為巴彥淖爾各地企業(yè)和個人服務(wù),聯(lián)系電話:028-86922220
正文
1、 react hooks核心API使用注意事項
筆者在項目中常用的hooks主要有useState, useEffect,useCallback,useMemo,useRef。當然像useReducer, useContext, createContext這些鉤子在H5游戲中也會使用,因為不需要維護錯綜復雜的狀態(tài),所以我們完全可以由上述三個api構(gòu)建一個自己的小型redux(后面會介紹如何實現(xiàn)小型的redux)來處理全局狀態(tài),但是對于企業(yè)復雜項目來說,我們使用redux及其生態(tài)會更加高效一些。
我們在使用hooks和函數(shù)組件編寫我們的組件時,第一個要考慮的就是渲染性能,我們知道如果在不做任何處理時,我們在函數(shù)組件中使用setState都會導致組件內(nèi)部重新渲染,一個比較典型的場景:
當我們在容器組件手動更新了任何state時,容器內(nèi)部的各個子組件都會重新渲染,為了避免這種情況出現(xiàn),我們一般都會使用memo將函數(shù)組件包裹,來達到class組件的pureComponent的效果:
import React, { memo, useState, useEffect } from 'react'
const A = (props) => {
console.log('A1')
useEffect(() => {
console.log('A2')
})
return A
}
const B = memo((props) => {
console.log('B1')
useEffect(() => {
console.log('B2')
})
return B
})
const Home = (props) => {
const [a, setA] = useState(0)
useEffect(() => {
console.log('start')
setA(1)
}, [])
return
}當我們將B用memo包裹后,狀態(tài)a的更新將不會導致B組件重新渲染。其實僅僅優(yōu)化這一點還遠遠不夠的,比如說我們子組件用到了容器組件的某個變量或者函數(shù),那么當容器內(nèi)部的state更新之后,這些變量和函數(shù)都會重新賦值,這樣就會導致即使子組件使用了memo包裹也還是會重新渲染,那么這個時候我們就需要使用useMemo和useCallback了。
useMemo可以幫我們將變量緩存起來,useCallback可以緩存回調(diào)函數(shù),它們的第二個參數(shù)和useEffect一樣,是一個依賴項數(shù)組,通過配置依賴項數(shù)組來決定是否更新。
import React, { memo, useState, useEffect, useMemo } from 'react'
const Home = (props) => {
const [a, setA] = useState(0)
const [b, setB] = useState(0)
useEffect(() => {
setA(1)
}, [])
const add = useCallback(() => {
console.log('b', b)
}, [b])
const name = useMemo(() => {
return b + 'xuxi'
}, [b])
return
}此時a更新后B組件不會再重新渲染。以上幾個優(yōu)化步驟主要是用來優(yōu)化組件的渲染性能,我們平時還會涉及到獲取組件dom和使用內(nèi)部閉包變量的情景,這個時候我們就可以使用useRef。
useRef返回一個可變的 ref 對象,其 .current 屬性被初始化為傳入的參數(shù)(initialValue)。返回的 ref 對象在組件的整個生命周期內(nèi)保持不變。
function AutoFocusIpt() {
const inputEl = useRef(null);
const useEffect(() => {
// `current` 指向已掛載到 DOM 上的文本輸入元素
inputEl.current.focus();
}, []);
return (
<>
>
);
}除了以上應用場景外,我們還可以利用它來實現(xiàn)class組件的setState的功能,具體實現(xiàn)后面會有介紹。
2、實現(xiàn)一個小型redux
實現(xiàn)redux我們會利用之前說的useReducer, useContext, createContext這三個api,至于如何實現(xiàn)redux,其實網(wǎng)上也有很多實現(xiàn)方式,這里筆者寫一個demo供大家參考:
// actionType.js
const actionType = {
INSREMENT: 'INSREMENT',
DECREMENT: 'DECREMENT',
RESET: 'RESET'
}
export default actionType
// actions.js
import actionType from './actionType'
const add = (num) => ({
type: actionType.INSREMENT,
payload: num
})
const dec = (num) => ({
type: actionType.DECREMENT,
payload: num
})
const getList = (data) => ({
type: actionType.GETLIST,
payload: data
})
export {
add,
dec,
getList
}
// reducer.js
function init(initialCount) {
return {
count: initialCount,
total: 10,
user: {},
article: []
}
}
function reducer(state, action) {
switch (action.type) {
case actionType.INSREMENT:
return {count: state.count + action.payload};
case actionType.DECREMENT:
return {count: state.count - action.payload};
case actionType.RESET:
return init(action.payload);
default:
throw new Error();
}
}
export { init, reducer }
// redux.js
import React, { useReducer, useContext, createContext } from 'react'
import { init, reducer } from './reducer'
const Context = createContext()
const Provider = (props) => {
const [state, dispatch] = useReducer(reducer, props.initialState || 0, init);
return (
{ props.children }
)
}
export { Context, Provider }
其實還有更優(yōu)雅的方式實現(xiàn),筆者之前也寫了幾套redux模版,歡迎一起討論哈。接下來我們進入正文,來帶大家實現(xiàn)幾個常用的自定義hooks。
3、實現(xiàn)自定義的useState,支持類似class組件setState方法
熟悉react的朋友都知道,我們使用class組件更新狀態(tài)時,setState會支持兩個參數(shù),一個是更新后的state或者回調(diào)式更新的state,另一個參數(shù)是更新后的回調(diào)函數(shù),如下面的用法:
this.setState({num: 1}, () => {
console.log('updated')
})但是hooks函數(shù)的useState第二個參數(shù)回調(diào)支持類似class組件的setState的第一個參數(shù)的用法,并不支持第二個參數(shù)回調(diào),但是很多業(yè)務(wù)場景中我們又希望hooks組件能支持更新后的回調(diào)這一方法,那該怎么辦呢?其實問題也很簡單,我們只要對hooks原理和api非常清楚的話,就可以通過自定義hooks來實現(xiàn),這里我們借助上面提到的useRef和useEffect配合useState來實現(xiàn)這一功能。
注:react hooks的useState一定要放到函數(shù)組件的最頂層,不能寫在ifelse等條件語句當中,來確保hooks的執(zhí)行順序一致,因為useState底層采用鏈表結(jié)構(gòu)實現(xiàn),有嚴格的順序之分。
我們先來看看實現(xiàn)的代碼:
import { useEffect, useRef, useState } from 'react'
const useXState = (initState) => {
const [state, setState] = useState(initState)
let isUpdate = useRef()
const setXState = (state, cb) => {
setState(prev => {
isUpdate.current = cb
return typeof state === 'function' ? state(prev) : state
})
}
useEffect(() => {
if(isUpdate.current) {
isUpdate.current()
}
})
return [state, setXState]
}
export default useXState筆者利用useRef的特性來作為標識區(qū)分是掛載還是更新,當執(zhí)行setXstate時,會傳入和setState一模一樣的參數(shù),并且將回調(diào)賦值給useRef的current屬性,這樣在更新完成時,我們手動調(diào)用current即可實現(xiàn)更新后的回調(diào)這一功能,是不是很巧妙呢?
4、實現(xiàn)自定義的useDebounce
節(jié)流函數(shù)和防抖函數(shù)想必大家也不陌生,為了讓我們在開發(fā)中更優(yōu)雅的使用節(jié)流和防抖函數(shù),我們往往需要讓某個state也具有節(jié)流防抖的功能,或者某個函數(shù)的調(diào)用,為了避免頻繁調(diào)用,我們往往也會采取節(jié)截流防抖這一思想,原生的節(jié)流防抖函數(shù)可能如一下代碼所示:
// 節(jié)流
function throttle(func, ms) {
let previous = 0;
return function() {
let now = Date.now();
let context = this;
let args = arguments;
if (now - previous > ms) {
func.apply(context, args);
previous = now;
}
}
}
// 防抖
function debounce(func, ms) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args)
}, ms);
}
}
那么我們首先來實現(xiàn)一下防抖的hooks,代碼如下:
import { useEffect, useRef } from 'react'
const useDebounce = (fn, ms = 30, deps = []) => {
let timeout = useRef()
useEffect(() => {
if (timeout.current) clearTimeout(timeout.current)
timeout.current = setTimeout(() => {
fn()
}, ms)
}, deps)
const cancel = () => {
clearTimeout(timeout.current)
timeout = null
}
return [cancel]
}
export default useDebounce由代碼可以知道,useDebounce接受三個參數(shù),分別為回調(diào)函數(shù),時間間隔以及依賴項數(shù)組,它暴露了cancel API,主要是用來控制何時停止防抖函數(shù)用的。具體使用如下:
// ...
import { useDebounce } from 'hooks'
const Home = (props) => {
const [a, setA] = useState(0)
const [b, setB] = useState(0)
const [cancel] = useDebounce(() => {
setB(a)
}, 2000, [a])
const changeIpt = (e) => {
setA(e.target.value)
}
return
{ b } { a }
}
以上代碼就實現(xiàn)了state的debounce的功能,具體效果如下圖所示:
5、 實現(xiàn)自定義的useThrottle
同理,我們繼續(xù)來實現(xiàn)節(jié)流的hooks函數(shù)。直接上代碼:
import { useEffect, useRef, useState } from 'react'
const useThrottle = (fn, ms = 30, deps = []) => {
let previous = useRef(0)
let [time, setTime] = useState(ms)
useEffect(() => {
let now = Date.now();
if (now - previous.current > time) {
fn();
previous.current = now;
}
}, deps)
const cancel = () => {
setTime(0)
}
return [cancel]
}
export default useThrottle代碼和自定義useDebounce類似,但需要注意一點就是為了實現(xiàn)cancel功能,我們使用了內(nèi)部state來處理,通過控制時間間隔來取消節(jié)流效果,當然還有很多其他方法可以實現(xiàn)這個hooks API。具體效果如下:
6、實現(xiàn)自定義useTitle
自定義的useTitle hooks其實使用場景也很多,因為我們目前大部分項目都是采用SPA或者混合SPA的方式開發(fā),對于不同的路由我們同樣希望想多頁應用一樣能切換到對應的標題,這樣可以讓用戶更好的知道頁面的主題和內(nèi)容。這個hooks的實現(xiàn)也很簡單,我們直接上代碼:
import { useEffect } from 'react'
const useTitle = (title) => {
useEffect(() => {
document.title = title
}, [])
return
}
export default useTitle以上代碼可以看出我們只需要在useEffect中設(shè)置document的title屬性就好了,我們不需要return任何值。其實還有更優(yōu)雅和復雜的實現(xiàn)方法,這里就不一一舉例了。具體使用如下:
const Home = () => {
// ...
useTitle('趣談前端')
return home
}7、實現(xiàn)自定義的useUpdate
我們都知道如果想讓組件重新渲染,我們不得不更新state,但是有時候業(yè)務(wù)需要的state是沒必要更新的,我們不能僅僅為了讓組件會重新渲染而強制讓一個state做無意義的更新,所以這個時候我們就可以自定義一個更新的hooks來優(yōu)雅的實現(xiàn)組件的強制更新,實現(xiàn)代碼如下:
import { useState } from 'react'
const useUpdate = () => {
const [, setFlag] = useState()
const update = () => {
setFlag(Date.now())
}
return update
}
export default useUpdate以上代碼可以發(fā)現(xiàn),我們useUpdate鉤子返回了一個函數(shù),該函數(shù)就是用來強制更新用的。使用方法如下:
const Home = (props) => {
// ...
const update = useUpdate()
return
{Date.now()}
}效果如下:
8、實現(xiàn)自定義的useScroll
自定義的useScroll也是高頻出現(xiàn)的問題之一,我們往往會監(jiān)聽一個元素滾動位置的變化來決定展現(xiàn)那些內(nèi)容,這個應用場景在H5游戲開發(fā)中應用十分廣泛,接下來我們來看看實現(xiàn)代碼:
import { useState, useEffect } from 'react'
const useScroll = (scrollRef) => {
const [pos, setPos] = useState([0,0])
useEffect(() => {
function handleScroll(e){
setPos([scrollRef.current.scrollLeft, scrollRef.current.scrollTop])
}
scrollRef.current.addEventListener('scroll', handleScroll, false)
return () => {
scrollRef.current.removeEventListener('scroll', handleScroll, false)
}
}, [])
return pos
}
export default useScroll由以上代碼可知,我們在鉤子函數(shù)里需要傳入一個元素的引用,這個我們可以在函數(shù)組件中采用ref和useRef來獲取到,鉤子返回了滾動的x,y值,即滾動的左位移和頂部位移,具體使用如下:
import React, { useRef } from 'react'
import { useScroll } from 'hooks'
const Home = (props) => {
const scrollRef = useRef(null)
const [x, y] = useScroll(scrollRef)
return
{ x }, { y }
}通過使用useScroll,鉤子將會幫我們自動監(jiān)聽容器滾動條的變化從而實時獲取滾動的位置。具體效果如下:
9、 實現(xiàn)自定義的useMouse和實現(xiàn)自定義的createBreakpoint
自定義的useMouse和createBreakpoint的實現(xiàn)方法和useScroll類似,都是監(jiān)聽窗口或者dom的事件來自動更新我們需要的值,這里我就不一一實現(xiàn)了,如果不懂的可以和我交流。通過這些自定義鉤子能大大提高我們代碼的開發(fā)效率,并將重復代碼進行有效復用,所以大家在工作中可以多嘗試。
當我們寫了很多自定鉤子時,一個好的開發(fā)經(jīng)驗就是統(tǒng)一管理和分發(fā)這些鉤子,筆者建議可以在項目中單獨建一個hooks的目錄專門存放這些可復用的鉤子,方便管理和維護。如下:
當前名稱:十分鐘教你手寫九個常用的自定義Hooks
網(wǎng)站URL:http://m.fisionsoft.com.cn/article/cdpidhj.html


咨詢
建站咨詢
