回调函数自定义传参_10分钟教你手写8个常用的自定义hooks
Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。本文是一篇以實戰(zhàn)為主的文章,主要講解實際項目中如何使用hooks以及一些最佳實踐。
react hooks核心API使用注意事項
筆者在項目中常用的hooks主要有useState, useEffect,useCallback,useMemo,useRef。
像useReducer, useContext, createContext這些鉤子在H5游戲中也會使用,因為不需要維護錯綜復雜的狀態(tài),所以我們完全可以由上述三個api構建一個自己的小型redux(后面會介紹如何實現(xiàn)小型的redux)來處理全局狀態(tài),但是對于企業(yè)復雜項目來說,我們使用redux及其生態(tài)會更加高效一些。
我們在使用hooks和函數(shù)組件編寫我們的組件時,第一個要考慮的就是渲染性能,我們知道如果在不做任何處理時,我們在函數(shù)組件中使用setState都會導致組件內部重新渲染,一個比較典型的場景:
當我們在容器組件手動更新了任何state時,容器內部的各個子組件都會重新渲染,為了避免這種情況出現(xiàn),我們一般都會使用memo將函數(shù)組件包裹,來達到class組件的pureComponent的效果:import React, { memo, useState, useEffect } from 'react'const A = (props) => { console.log('A1') useEffect(() => { console.log('A2') }) return <div>Adiv>}const B = memo((props) => { console.log('B1') useEffect(() => { console.log('B2') }) return <div>Bdiv>})const Home = (props) => { const [a, setA] = useState(0) useEffect(() => { console.log('start') setA(1) }, []) return <div><A n={a} /><B />div>}當我們將B用memo包裹后,狀態(tài)a的更新將不會導致B組件重新渲染。其實僅僅優(yōu)化這一點還遠遠不夠的,比如說我們子組件用到了容器組件的某個變量或者函數(shù),那么當容器內部的state更新之后,這些變量和函數(shù)都會重新賦值,這樣就會導致即使子組件使用了memo包裹也還是會重新渲染,那么這個時候我們就需要使用useMemo和useCallback了。useMemo可以幫我們將變量緩存起來,useCallback可以緩存回調函數(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和使用內部閉包變量的情景,這個時候我們就可以使用useRef。useRef返回一個可變的 ref 對象,其 .current 屬性被初始化為傳入的參數(shù)(initialValue)。返回的 ref 對象在組件的整個生命周期內保持不變。function AutoFocusIpt() { const inputEl = useRef(null); const useEffect(() => { // `current` 指向已掛載到 DOM 上的文本輸入元素 inputEl.current.focus(); }, []); return ( <> > );}除了以上應用場景外,我們還可以利用它來實現(xiàn)class組件的setState的功能,具體實現(xiàn)后面會有介紹。實現(xiàn)一個小型redux
實現(xiàn)redux我們會利用之前說的useReducer, useContext, createContext這三個api,至于如何實現(xiàn)redux,其實網(wǎng)上也有很多實現(xiàn)方式,這里筆者寫一個demo供大家參考:// actionType.jsconst actionType = { INSREMENT: 'INSREMENT', DECREMENT: 'DECREMENT', RESET: 'RESET'}export default actionType// actions.jsimport 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.jsfunction 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.jsimport 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 ( <Context.Provider value={{state, dispatch}}> { props.children } Context.Provider> )}export { Context, Provider }其實還有更優(yōu)雅的方式實現(xiàn),筆者之前也寫了幾套redux模版,歡迎一起討論哈。接下來我們進入正文,來帶大家實現(xiàn)幾個常用的自定義hooks。實現(xiàn)自定義的useState,支持類似class組件setState方法
熟悉react的朋友都知道,我們使用class組件更新狀態(tài)時,setState會支持兩個參數(shù),一個是更新后的state或者回調式更新的state,另一個參數(shù)是更新后的回調函數(shù),如下面的用法:this.setState({num: 1}, () => { console.log('updated')})但是hooks函數(shù)的useState第二個參數(shù)回調支持類似class組件的setState的第一個參數(shù)的用法,并不支持第二個參數(shù)回調,但是很多業(yè)務場景中我們又希望hooks組件能支持更新后的回調這一方法,那該怎么辦呢?其實問題也很簡單,我們只要對hooks原理和api非常清楚的話,就可以通過自定義hooks來實現(xiàn),這里我們借助上面提到的useRef和useEffect配合useState來實現(xiàn)這一功能。注:react hooks的useState一定要放到函數(shù)組件的最頂層,不能寫在ifelse等條件語句當中,來確保hooks的執(zhí)行順序一致,因為useState底層采用鏈表結構實現(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ù),并且將回調賦值給useRef的current屬性,這樣在更新完成時,我們手動調用current即可實現(xiàn)更新后的回調這一功能,是不是很巧妙呢?實現(xiàn)自定義的useDebounce
節(jié)流函數(shù)和防抖函數(shù)想必大家也不陌生,為了讓我們在開發(fā)中更優(yōu)雅的使用節(jié)流和防抖函數(shù),我們往往需要讓某個state也具有節(jié)流防抖的功能,或者某個函數(shù)的調用,為了避免頻繁調用,我們往往也會采取節(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ù),分別為回調函數(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 <div> <input type="text" onChange={changeIpt} /> { b } { a } div>}以上代碼就實現(xiàn)了state的debounce的功能,具體效果如下圖所示:實現(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功能,我們使用了內部state來處理,通過控制時間間隔來取消節(jié)流效果,當然還有很多其他方法可以實現(xiàn)這個hooks API。具體效果如下:現(xiàn)自定義useTitle
自定義的useTitle hooks其實使用場景也很多,因為我們目前大部分項目都是采用SPA或者混合SPA的方式開發(fā),對于不同的路由我們同樣希望想多頁應用一樣能切換到對應的標題,這樣可以讓用戶更好的知道頁面的主題和內容。這個hooks的實現(xiàn)也很簡單,我們直接上代碼:import { useEffect } from 'react'const useTitle = (title) => { useEffect(() => { document.title = title }, []) return }export default useTitle以上代碼可以看出我們只需要在useEffect中設置document的title屬性就好了,我們不需要return任何值。其實還有更優(yōu)雅和復雜的實現(xiàn)方法,這里就不一一舉例了。具體使用如下:const Home = () => { // ... useTitle('趣談前端') return <div>homediv>}實現(xiàn)自定義的useUpdate
我們都知道如果想讓組件重新渲染,我們不得不更新state,但是有時候業(yè)務需要的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 <div> {Date.now()} <div><button onClick={update}>updatebutton>div> div>}效果如下:實現(xiàn)自定義的useScroll
自定義的useScroll也是高頻出現(xiàn)的問題之一,我們往往會監(jiān)聽一個元素滾動位置的變化來決定展現(xiàn)那些內容,這個應用場景在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 <div> <div ref={scrollRef}> <div className="innerBox">div> div> <div>{ x }, { y }div> div>}通過使用useScroll,鉤子將會幫我們自動監(jiān)聽容器滾動條的變化從而實時獲取滾動的位置。具體效果如下:實現(xiàn)自定義的useMouse和實現(xiàn)自定義的createBreakpoint
自定義的useMouse和createBreakpoint的實現(xiàn)方法和useScroll類似,都是監(jiān)聽窗口或者dom的事件來自動更新我們需要的值,這里我就不一一實現(xiàn)了,如果不懂的可以和我交流。通過這些自定義鉤子能大大提高我們代碼的開發(fā)效率,并將重復代碼進行有效復用,所以大家在工作中可以多嘗試。當我們寫了很多自定鉤子時,一個好的開發(fā)經驗就是統(tǒng)一管理和分發(fā)這些鉤子,筆者建議可以在項目中單獨建一個hooks的目錄專門存放這些可復用的鉤子,方便管理和維護。如下:往期精選:
6大真實場景體驗前端核心技術
Vue3中令人興奮的新功能
從0到1教你基于vue開發(fā)一個組件庫高性能前端架構解決方案
總結
以上是生活随笔為你收集整理的回调函数自定义传参_10分钟教你手写8个常用的自定义hooks的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ai外呼营销系统_为了让居民预约口罩少出
- 下一篇: ncbi查找目的基因序列_教你如何利用N