react-hooks如何使用?

1. 什麼是react-hooks?

** react-hooks是react16.8之後,react新增的鉤子API,目的是增長代碼的可複用性,邏輯性,彌補無狀態組件沒有生命週期,沒有數據管理狀態state的缺陷。筆者認爲,react-hooks思想和初衷,也是把組件,顆粒化,單元化,造成獨立的渲染環境,減小渲染次數,優化性能。react

useCallback✅es6

useContext✅編程

useEffect✅redux

useLayoutEffect ✅api

useMemo ✅數組

useReducer✅promise

useRef✅瀏覽器

useState✅ 以上就是react-hooks主要的api,接下來我會和你們分享一下這些api的用法,以及使用他們的注意事項。緩存

2.爲何要使用hooks

咱們爲何要使用react-hooks呢,首先和傳統的class聲明的有狀態有這顯著的優勢就是性能優化

1 react-hooks可讓咱們的代碼的邏輯性更強,能夠抽離公共的方法,公共組件。

2 react-hooks思想更趨近於函數式編程。用函數聲明方式代替class聲明方式,雖然說class也是es6構造函數語法糖,可是react-hooks寫起來更有函數即組件,無疑也提升代碼的開發效率(無需像class聲明組件那樣寫聲明週期,寫生命週期render函數等)

3 react-hooks可能把龐大的class組件,化整爲零成不少小組件,useMemo等方法讓組件或者變量制定一個適合本身的獨立的渲染空間,必定程度上能夠提升性能,減小渲染次數。這裏值得一提的是,若是把負責 請求是數據 ➡️ 視圖更新的渲染組件,用react-hooks編寫的話 ,配合immutable等優秀的開源庫,會有更棒的效果(這裏特別注意的是⚠️,若是亂用hooks,不但不會提高性能,反而會影響性能,帶來各類各樣的想不到的問題)。

3.如何使用hooks

接下來和你們探討一下,react-hooks主要api,具體使用

1 useState 數據存儲,派發更新

useState出現,使得react無狀態組件可以像有狀態組件同樣,能夠擁有本身state,useState的參數能夠是一個具體的值,也能夠是一個函數用於判斷複雜的邏輯,函數返回做爲初始值,usestate 返回一個數組,數組第一項用於讀取此時的state值 ,第二項爲派發數據更新,組件渲染的函數,函數的參數便是須要更新的值。useState和useReduce 做爲可以觸發組件從新渲染的hooks,咱們在使用useState的時候要特別注意的是,useState派發更新函數的執行,就會讓整個function組件從頭至尾執行一次,因此須要配合useMemo,usecallback等api配合使用,這就是我說的爲何濫用hooks會帶來負做用的緣由之一了。一下代碼爲usestate基本應用

const DemoState = (props) => {
   /* number爲此時state讀取值 ,setNumber爲派發更新的函數 */
   let [number, setNumber] = useState(0) /* 0爲初始值 */
   return (<div> <span>{ number }</span> <button onClick={ ()=> { setNumber(number+1) console.log(number) /* 這裏的number是不可以即便改變的 */ } } ></button> </div>)
}
複製代碼

上邊簡單的例子說明了useState ,可是當咱們在調用更新函數以後,state的值是不能即時改變的,只有當下一次上下文執行的時候,state值才隨之改變。

const a =1 
const DemoState = (props) => {
   /* useState 第一個參數若是是函數 則處理複雜的邏輯 ,返回值爲初始值 */
   let [number, setNumber] = useState(()=>{
      // number
      return a===1 ? 1 : 2
   }) /* 1爲初始值 */
   return (<div> <span>{ number }</span> <button onClick={ ()=>setNumber(number+1) } ></button> </div>)
}
複製代碼

2 useEffect 組件更新反作用鉤子

若是你想在function組件中,當組件完成掛載,dom渲染完成,作一些操縱dom,請求數據,那麼useEffect是一個不二選擇,若是咱們須要在組件初次渲染的時候請求數據,那麼useEffect能夠充當class組件中的 componentDidMount , 可是特別注意的是,若是不給useEffect執行加入限定條件,函數組件每一次更新都會觸發effect ,那麼也就說明每一次state更新,或是props的更新都會觸發useEffect執行,此時的effect又充當了componentDidUpdate和componentwillreceiveprops,因此說合理的用於useEffect就要給effect加入限定執行的條件,也就是useEffect的第二個參數,這裏說是限定條件,也能夠說是上一次useeffect更新收集的某些記錄數據變化的記憶,在新的一輪更新,useeffect會拿出以前的記憶值和當前值作對比,若是發生了變化就執行新的一輪useEffect的反作用函數,useEffect第二個參數是一個數組,用來收集多個限制條件 。

/* 模擬數據交互 */
function getUserInfo(a){
    return new Promise((resolve)=>{
        setTimeout(()=>{ 
           resolve({
               name:a,
               age:16,
           }) 
        },500)
    })
}

const Demo = ({ a }) => {
    const [ userMessage , setUserMessage ] :any= useState({})
    const div= useRef()
    const [number, setNumber] = useState(0)
    /* 模擬事件監聽處理函數 */
    const handleResize =()=>{}
    /* useEffect使用 ,這裏若是不加限制 ,會是函數重複執行,陷入死循環*/
    useEffect(()=>{
        /* 請求數據 */
       getUserInfo(a).then(res=>{
           setUserMessage(res)
       })
       /* 操做dom */
       console.log(div.current) /* div */
       /* 事件監聽等 */
        window.addEventListener('resize', handleResize)
    /* 只有當props->a和state->number改變的時候 ,useEffect反作用函數從新執行 ,若是此時數組爲空[],證實函數只有在初始化的時候執行一次至關於componentDidMount */
    },[ a ,number ])
    return (<div ref={div} > <span>{ userMessage.name }</span> <span>{ userMessage.age }</span> <div onClick={ ()=> setNumber(1) } >{ number }</div> </div>)
}

複製代碼

若是咱們須要在組件銷燬的階段,作一些取消dom監聽,清除定時器等操做,那麼咱們能夠在useEffect函數第一個參數,結尾返回一個函數,用於清除這些反作用。至關與componentWillUnmount。

const Demo = ({ a }) => {
    /* 模擬事件監聽處理函數 */
    const handleResize =()=>{}
    useEffect(()=>{
       /* 定時器 延時器等 */
       const timer = setInterval(()=>console.log(666),1000)
       /* 事件監聽 */
       window.addEventListener('resize', handleResize)
       /* 此函數用於清除反作用 */
       return function(){
           clearInterval(timer) 
           window.removeEventListener('resize', handleResize)
       }
    },[ a ])
    return (<div > </div>)
}

複製代碼

異步 async effect ?

提醒你們的是 useEffect是不能直接用 async await 語法糖的

/* 錯誤用法 ,effect不支持直接 async await 裝飾的 */
 useEffect(async ()=>{
        /* 請求數據 */
      const res = await getUserInfo(payload)
    },[ a ,number ])

複製代碼

若是咱們想要用 async effect 能夠對effect進行一層包裝

const asyncEffect = (callback, deps)=>{
   useEffect(()=>{
       callback()
   },deps)
}
複製代碼

3useLayoutEffect 渲染更新以前的 useEffect

useEffect 執行順序 組件更新掛載完成 -> 瀏覽器dom 繪製完成 -> 執行useEffect回調 。

useLayoutEffect 執行順序 組件更新掛載完成 -> 執行useLayoutEffect回調-> 瀏覽器dom 繪製完成
因此說useLayoutEffect 代碼可能會阻塞瀏覽器的繪製 若是咱們在useEffect 從新請求數據,渲染視圖過程當中,確定會形成畫面閃動的效果,而若是用useLayoutEffect ,回調函數的代碼就會阻塞瀏覽器繪製,因此可定會引發畫面卡頓等效果,那麼具體要用 useLayoutEffect 仍是 useEffect ,要看實際項目的狀況,大部分的狀況 useEffect 均可以知足的。

const DemoUseLayoutEffect = () => {
    const target = useRef()
    useLayoutEffect(() => {
        /*咱們須要在dom繪製以前,移動dom到制定位置*/
        const { x ,y } = getPositon() /* 獲取要移動的 x,y座標 */
        animate(target.current,{ x,y })
    }, []);
    return (
        <div > <span ref={ target } className="animate"></span> </div>
    )
}
複製代碼

4 useRef 獲取元素 ,緩存數據。

和傳統的class組件ref同樣,react-hooks 也提供獲取元素方法 useRef,它有一個參數能夠做爲緩存數據的初始值,返回值能夠被dom元素ref標記,能夠獲取被標記的元素節點.

const DemoUseRef = ()=>{
    const dom= useRef(null)
    const handerSubmit = ()=>{
        /* <div >表單組件</div> dom 節點 */
        console.log(dom.current)
    }
    return <div> {/* ref 標記當前dom節點 */} <div ref={dom} >表單組件</div> <button onClick={()=>handerSubmit()} >提交</button> </div>
}
複製代碼

高階用法 緩存數據

固然useRef還有一個很重要的做用就是緩存數據,咱們知道usestate ,useReducer 是能夠保存當前的數據源的,可是若是它們更新數據源的函數執行一定會帶來整個組件重新執行到渲染,若是在函數組件內部聲明變量,則下一次更新也會重置,若是咱們想要悄悄的保存數據,而又不想觸發函數的更新,那麼useRef是一個很棒的選擇。

** const currenRef = useRef(InitialData)

獲取 currenRef.current 改變 currenRef.current = newValue

useRef能夠第一個參數能夠用來初始化保存數據,這些數據能夠在current屬性上獲取到 ,固然咱們也能夠經過對current賦值新的數據源。

下面咱們經過react-redux源碼來看看useRef的巧妙運用 (react-redux 在react-hooks發佈後,用react-hooks從新了其中的Provide,connectAdvanced)核心模塊,能夠見得 react-hooks在限制數據更新,高階組件上有這必定的優點,其源碼大量運用useMemo來作數據斷定

/* 這裏用到的useRef沒有一個是綁定在dom元素上的,都是作數據緩存用的 */
      /* react-redux 用userRef 來緩存 merge以後的 props */
      const lastChildProps = useRef()
      // lastWrapperProps 用 useRef 來存放組件真正的 props信息
      const lastWrapperProps = useRef(wrapperProps)
      //是否儲存props是否處於正在更新狀態
      const renderIsScheduled = useRef(false)
複製代碼

這是react-redux中用useRef 對數據作的緩存,那麼怎麼作更新的呢 ,咱們接下來看

//獲取包裝的props 
function captureWrapperProps( lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, actualChildProps, childPropsFromStoreUpdate, notifyNestedSubs ) {
   //咱們要捕獲包裝props和子props,以便稍後進行比較
  lastWrapperProps.current = wrapperProps  //子props 
  lastChildProps.current = actualChildProps //通過 merge props 以後造成的 prop
  renderIsScheduled.current = false

}
複製代碼

經過上面咱們能夠看到 ,react-redux 用從新賦值的方法,改變緩存的數據源,避免沒必要要的數據更新, 若是選用useState儲存數據,必然促使組件從新渲染 因此採用了useRef解決了這個問題,至於react-redux源碼怎麼實現的,咱們這裏能夠參考筆者的另一篇文章react-redux源碼解析

5 useContext 自由獲取context

咱們可使用useContext ,來獲取父級組件傳遞過來的context值,這個當前值就是最近的父級組件 Provider 設置的value值,useContext參數通常是由 createContext 方式引入 ,也能夠父級上下文context傳遞 ( 參數爲context )。useContext 能夠代替 context.Consumer 來獲取Provider中保存的value值

/* 用useContext方式 */
const DemoContext = ()=> {
    const value:any = useContext(Context)
    /* my name is alien */
return <div> my name is { value.name }</div>
}

/* 用Context.Consumer 方式 */
const DemoContext1 = ()=>{
    return <Context.Consumer> {/* my name is alien */} { (value)=> <div> my name is { value.name }</div> } </Context.Consumer>
}

export default ()=>{
    return <div> <Context.Provider value={{ name:'alien' , age:18 }} > <DemoContext /> <DemoContext1 /> </Context.Provider> </div>
}
複製代碼

6 useReducer 無狀態組件中的redux

useReducer 是react-hooks提供的可以在無狀態組件中運行的相似redux的功能api,至於它到底能不能代替redux react-redux ,我我的的見解是不能的 ,redux 可以複雜的邏輯中展示優點 ,並且 redux的中間件模式思想也是很是優秀了,咱們能夠經過中間件的方式來加強dispatch redux-thunk redux-sage redux-action redux-promise都是比較不錯的中間件,能夠把同步reducer編程異步的reducer。useReducer 接受的第一個參數是一個函數,咱們能夠認爲它就是一個reducer ,reducer的參數就是常規reducer裏面的state和action,返回改變後的state, useReducer第二個參數爲state的初始值 返回一個數組,數組的第一項就是更新以後state的值 ,第二個參數是派發更新的dispatch函數 。dispatch 的觸發會觸發組件的更新,這裏可以促使組件重新的渲染的一個是useState派發更新函數,另外一個就 useReducer中的dispatch

const DemoUseReducer = ()=>{
    /* number爲更新後的state值, dispatchNumbner 爲當前的派發函數 */
   const [ number , dispatchNumbner ] = useReducer((state,action)=>{
       const { payload , name  } = action
       /* return的值爲新的state */
       switch(name){
           case 'add':
               return state + 1
           case 'sub':
               return state - 1 
           case 'reset':
             return payload       
       }
       return state
   },0)
   return <div> 當前值:{ number } { /* 派發更新 */ } <button onClick={()=>dispatchNumbner({ name:'add' })} >增長</button> <button onClick={()=>dispatchNumbner({ name:'sub' })} >減小</button> <button onClick={()=>dispatchNumbner({ name:'reset' ,payload:666 })} >賦值</button> { /* 把dispatch 和 state 傳遞給子組件 */ } <MyChildren dispatch={ dispatchNumbner } State={{ number }} /> </div>
}
複製代碼

固然實際業務邏輯可能更復雜的,須要咱們在reducer裏面作更復雜的邏輯操做。

7 useMemo 小而香性能優化

useMemo我認爲是React設計最爲精妙的hooks之一,優勢就是能造成獨立的渲染空間,可以使組件,變量按照約定好規則更新。渲染條件依賴於第二個參數deps。 咱們知道無狀態組件的更新是從頭至尾的更新,若是你想要重新渲染一部分視圖,而不是整個組件,那麼用useMemo是最佳方案,避免了不須要的更新,和沒必要要的上下文的執行,在介紹useMemo以前,咱們先來講一說, memo, 咱們知道class聲明的組件能夠用componentShouldUpdate來限制更新次數,那麼memo就是無狀態組件的ShouldUpdate , 而咱們今天要講的useMemo就是更爲細小的ShouldUpdate單元,

先來看看memo ,memo的做用結合了pureComponent純組件和 componentShouldUpdate功能,會對傳進來的props進行一次對比,而後根據第二個函數返回值來進一步判斷哪些props須要更新。

/* memo包裹的組件,就給該組件加了限制更新的條件,是否更新取決於memo第二個參數返回的boolean值, */
const DemoMemo = connect(state =>
    ({ goodList: state.goodList })
)(memo(({ goodList, dispatch, }) => {
    useEffect(() => {
        dispatch({
            name: 'goodList',
        })
    }, [])
    return <Select placeholder={'請選擇'} style={{ width: 200, marginRight: 10 }} onChange={(value) => setSeivceId(value)} > { goodList.map((item, index) => <Option key={index + 'asd' + item.itemId} value={item.itemId} > {item.itemName} </Option>) } </Select>
    /* 判斷以前的goodList 和新的goodList 是否相等,若是相等, 則不更新此組件 這樣就能夠制定屬於本身的渲染約定 ,讓組件只有知足預約的下才從新渲染 */
}, (pre, next) => is(pre.goodList, next.goodList)))
複製代碼

useMemo的應用理念和memo差很少,都是斷定是否知足當前的限定條件來決定是否執行useMemo的callback函數,而useMemo的第二個參數是一個deps數組,數組裏的參數變化決定了useMemo是否更新回調函數,useMemo返回值就是通過斷定更新的結果。它能夠應用在元素上,應用在組件上,也能夠應用在上下文當中。若是又一個循環的list元素,那麼useMemo會是一個不二選擇,接下來咱們一塊兒探尋一下useMemo的優勢

/* 用 useMemo包裹的list能夠限定當且僅當list改變的時候才更新此list,這樣就能夠避免selectList從新循環 */
 {useMemo(() => (
      <div>{ selectList.map((i, v) => ( <span className={style.listSpan} key={v} > {i.patentName} </span> ))} </div>
), [selectList])}
複製代碼

1 useMemo能夠減小沒必要要的循環,減小沒必要要的渲染

useMemo(() => (
    <Modal width={'70%'} visible={listshow} footer={[ <Button key="back" >取消</Button>, <Button key="submit" type="primary" > 肯定 </Button> ]} > { /* 減小了PatentTable組件的渲染 */ } <PatentTable getList={getList} selectList={selectList} cacheSelectList={cacheSelectList} setCacheSelectList={setCacheSelectList} /> </Modal>
 ), [listshow, cacheSelectList])
複製代碼

2 useMemo能夠減小子組件的渲染次數

const DemoUseMemo=()=>{
  /* 用useMemo 包裹以後的log函數能夠避免了每次組件更新再從新聲明 ,能夠限制上下文的執行 */
    const newLog = useMemo(()=>{
        const log =()=>{
            console.log(6666)
        }
        return log
    },[])
    return <div onClick={()=>newLog()} ></div>
}
複製代碼

3 useMemo讓函數在某個依賴項改變的時候才運行,這能夠避免不少沒必要要的開銷(這裏要注意⚠️⚠️⚠️的是若是被useMemo包裹起來的上下文,造成一個獨立的閉包,會緩存以前的state值,若是沒有加相關的更新條件,是獲取不到更新以後的state的值的,以下邊👇⬇️)

const DemoUseMemo=()=>{
    const [ number ,setNumber ] = useState(0)
    const newLog = useMemo(()=>{
        const log =()=>{
            /* 點擊span以後 打印出來的number 不是實時更新的number值 */
            console.log(number)
        }
        return log
      /* [] 沒有 number */  
    },[])
    return <div> <div onClick={()=>newLog()} >打印</div> <span onClick={ ()=> setNumber( number + 1 ) } >增長</span> </div>
}

複製代碼

useMemo很不錯,react-redux 用react-hooks重寫後運用了大量的useMemo情景,我爲你們分析兩處

useMemo 同過 store didStoreComeFromProps contextValue 屬性制定是否須要重置更新訂閱者subscription ,這裏我就不爲你們講解react-redux了,有興趣的同窗能夠看看react-redux源碼,看看是怎麼用useMemo的

const [subscription, notifyNestedSubs] = useMemo(() => {
  if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY

  const subscription = new Subscription(
    store,
    didStoreComeFromProps ? null : contextValue.subscription // old 
  )
  
  const notifyNestedSubs = subscription.notifyNestedSubs.bind(
    subscription
  )

  return [subscription, notifyNestedSubs]
}, [store, didStoreComeFromProps, contextValue])
複製代碼

react-redux經過 判斷 redux store的改變來獲取與之對應的state

const previousState = useMemo(() => store.getState(), [store])
複製代碼

講到這裏,若是咱們應用useMemo根據依賴項合理的顆粒化咱們的組件,能起到很棒的優化組件的做用。

8 useCallback useMemo版本的回調函數

useMemo和useCallback接收的參數都是同樣,都是在其依賴項發生變化後才執行,都是返回緩存的值,區別在於useMemo返回的是函數運行的結果,useCallback返回的是函數,這個回調函數是通過處理後的也就是說父組件傳遞一個函數給子組件的時候,因爲是無狀態組件每一次都會從新生成新的props函數,這樣就使得每一次傳遞給子組件的函數都發生了變化,這時候就會觸發子組件的更新,這些更新是沒有必要的,此時咱們就能夠經過usecallback來處理此函數,而後做爲props傳遞給子組件

/* 用react.memo */
const DemoChildren = React.memo((props)=>{
   /* 只有初始化的時候打印了 子組件更新 */
    console.log('子組件更新')
   useEffect(()=>{
       props.getInfo('子組件')
   },[])
   return <div>子組件</div>
})

const DemoUseCallback=({ id })=>{
    const [number, setNumber] = useState(1)
    /* 此時usecallback的第一參數 (sonName)=>{ console.log(sonName) } 通過處理賦值給 getInfo */
    const getInfo  = useCallback((sonName)=>{
          console.log(sonName)
    },[id])
    return <div> {/* 點擊按鈕觸發父組件更新 ,可是子組件沒有更新 */} <button onClick={ ()=>setNumber(number+1) } >增長</button> <DemoChildren getInfo={getInfo} /> </div>
}

複製代碼

這裏應該提醒的是,useCallback ,必須配合 react.memo pureComponent ,不然不但不會提高性能,還有可能下降性能

4總結

react-hooks的誕生,也不是說它可以徹底代替class聲明的組件,對於業務比較複雜的組件,class組件仍是首選,只不過咱們能夠把class組件內部拆解成funciton組件,根據業務需求,哪些負責邏輯交互,哪些須要動態渲染,而後配合usememo等api,讓性能提高起來。react-hooks使用也有一些限制條件,好比說不能放在流程控制語句中,執行上下文也有必定的要求。整體來講,react-hooks仍是很不錯的,值得你們去學習和探索。

微信掃碼關注公衆號,按期分享技術文章

在這裏插入圖片描述

相關文章
相關標籤/搜索