(一)useEffect
useEffect – React 中文文檔
useEffect hook用于模擬以前的class組件的生命周期,但比原本的生命周期有著更強大的功能
1.類組件的生命周期
在類組件編程時,網絡請求,訂閱等操作都是在生命周期中完成
import React, { Component } from 'react'export default class App extends Component {// 組件掛載后執行componentDidMount(){// 發送請求// 事件總線綁定// 創建定時器等}// 組件更新后執行componentDidUpdate(){}// 組件銷毀前執行componentWillUnmount(){// 事件總線解綁// 清除定時器}render() {return (<div>App</div>
2.函數式組件的生命周期
函數式組件沒有明確的生命周期,使用useEffect來模擬生命周期
useEffect(setup, dependencies?)
在useRffect的第一個參數傳入回調函數,執行請求、掛載等操作
useEffect會在組件每次掛載、更新后調用回調?
import { useState, useEffect } from 'react'
function App() {const [count, setCount] = useState(0)useEffect(()=>{// 發送請求// store倉庫訂閱subscribe// 事件總線綁定 eventbus.on// 操作外部domdocument.title = count})return (<><div>{count}</div><button onClick={()=>{setCount(count+1)}}>加1</button></>)
}
如何在組件銷毀前取消訂閱或者移除綁定?
只需要在第一個回調里返回一個回調函數即可,useEffect會在組件銷毀前/組件更新前調用
useEffect(()=>{// 發送請求// store倉庫訂閱subscribe// 事件總線綁定 eventbus.on// 操作外部domdocument.title = count// 計數器const time = setInterval(()=>{console.log(1);},1000)return ()=>{// store倉庫取消訂閱 unsubscribe// 清除事件總線// 清除計數器等操作clearInterval(time)}})
回調函數內的代碼太長了
拆分useEffect,每個功能都可以單獨寫一個useEffect,react會自動處理
useEffect(()=>{// 發送請求})useEffect(()=>{// store倉庫訂閱subscribereturn ()=>{// store倉庫取消訂閱 unsubscribe}})useEffect(()=>{// 計數器const time = setInterval(()=>{console.log(count);},1000)return ()=>{// 清除計數器等操作clearInterval(time)}})
執行次數會不會太多了?
向上面那樣書寫的話,每次update都會執行回調,更新一次dom就請求一次、綁定一次事件這樣子也太蠢了,因此useEffect可以傳入第二個參數,用來控制依據什么來決定是否執行,和之前useCallback、useMemo一樣,都會傳入dependencies這個參數
// 只執行一次useEffect(()=>{// 發送請求},[])// 只執行一次useEffect(()=>{// store倉庫訂閱subscribereturn ()=>{// store倉庫取消訂閱 unsubscribe}},[])// count改變才執行useEffect(()=>{document.title = count},[count])
useEffect先簡單寫到這里,useEffect雖然是模擬生命周期,但它能做的事比生命周期更多,能夠根據傳入的數組參數判斷是否執行?
(二)useRef
useRef – React 中文文檔
useRef
是一個 React Hook,它能幫助引用一個不需要渲染的值
useRef(initialValue)
initialValue
:ref 對象的?current
?屬性的初始值。可以是任意類型的值。這個參數在首次渲染后被忽略?
useRef hook主要有兩個功能:
- 綁定dom元素
- 保存一個數據,在整個生命周期中可以保存不變?
1.綁定dom元素
初始化const xxx = useRef();通過ref={xxx}來綁定ref
import { useState, useRef } from 'react'
function App() {const [count, setCount] = useState(0)const nameRef = useRef()console.log(nameRef.current);return (<><div ref={nameRef}>csq</div><div>{count}</div><button onClick={()=>{setCount(count+1)}}>加1</button></>)
}
通過xxx.current獲取該dom元素
2.綁定一個值(解決閉包陷阱)
先說說閉包陷阱:
閉包陷阱是指使用react hooks的時候,由于閉包特性,在某些函數內獲取useState或者props的值時獲取到的是舊的值,而實際值已經改變
使用 ref 可以確保:
- 可以在重新渲染之間?存儲信息(普通對象存儲的值每次渲染都會重置)。
- 改變它?不會觸發重新渲染(狀態變量會觸發重新渲染)。
- 對于組件的每個副本而言,這些信息都是本地的(外部變量則是共享的)。
改變 ref 不會觸發重新渲染,所以 ref 不適合用于存儲期望顯示在屏幕上的信息。如有需要,使用 state 代替。
將新增count的操作放到useCallback回調里,會導致讀取到的count始終為0
const [count, setCount] = useState(0)const increment = useCallback(()=>{setCount(count+1) // set(0+1)console.log(count); // 0},[])return (<><div>{count}</div><button onClick={()=>increment()}>加1</button></>)
因為useCallback傳入的依賴為空,意味著increment函數只生成一次,只能讀取到生成時count的狀態,即0(我感覺我也是蒙的)
這樣就形成了閉包陷阱!
解決辦法:
(1)添加useCallback的依賴即可
const increment = useCallback(()=>{setCount(count+1) console.log(count)},[count])
(2)使用useRef
const [count, setCount] = useState(0)const countRef = useRef()// count改變會引起重新渲染,這樣countRef的值每次都和count相等countRef.current = countconst increment = useCallback(()=>{setCount(countRef.current+1) },[])return (<><div>{count}</div><button onClick={()=>increment()}>加1</button></>)
這里肯定不是應用useRef的最好場景,畢竟加個依賴項就解決了
但使用useRef的話,increment函數就不會重新加載了!
(三)useImperativeHandle
useImperativeHandle – React 中文文檔
useImperativeHandle
是 React 中的一個 Hook,它能讓你自定義由 ref 暴露出來的句柄。
useImperativeHandle(ref, createHandle, dependencies?)
1.在父組件使用子組件的ref?
子組件獲取父組件ref的方法:forwardRef()
forwardRef – React 中文文檔
import { useRef, memo, forwardRef, useImperativeHandle } from 'react'
function App() {const childrenRef = useRef()const getDom = ()=>{console.log(childrenRef.current);}return (<><button onClick={getDom}>獲取dom元素</button><Children ref={childrenRef}></Children></>)
}const Children = memo(forwardRef(function(props,ref){return (<><input type="text" ref={ref} /></>)
}))
2.通過useImperativeHandle hook控制子組件ref能暴露的部分
import { useRef, memo, forwardRef, useImperativeHandle } from 'react'
function App() {const childrenRef = useRef()const getDom = ()=>{console.log(childrenRef.current);childrenRef.current.set(100)childrenRef.current.focus()}return (<><button onClick={getDom}>獲取dom元素</button><Children ref={childrenRef}></Children></>)
}const Children = memo(forwardRef(function(props,ref){const inputRef = useRef()useImperativeHandle(ref,()=>{// 返回對象 該對象就是父組件能操作的chilrenRefreturn {set(value){inputRef.current.value = value},focus(){inputRef.current.focus()},}})return (<><input type="text" ref={inputRef} /></>)
}))
?這個hook的使用不是很常見,只要了解就ok
(四)useLayoutEffect
useLayoutEffect – React 中文文檔
useLayoutEffect
是 useEffect 的一個版本,在瀏覽器重新繪制屏幕之前觸發。
useLayoutEffect(setup, dependencies?)
useLayoutEffect和useEffect在各個方面都是相同的,只是執行的時期不同,useLayout會阻塞dom的更新。如果需要在dom更新前進行操作,使用useLayoutEffect
1.使用useEffect
function App() {const [count,setCount] = useState(0)// 可見count在點擊重置之后會先閃回0再變為隨機數useEffect(()=>{console.log('useEffect');if(count == 0){setCount(Math.random()+100)}})return (<><h1>count:{count}</h1><button onClick={()=>setCount(0)}>重置為0</button></>)
}
2.使用useLayoutEffect
如果需要在dom渲染之前改變的需求就使用useLayoutEffect?
function App() {const [count,setCount] = useState(0)// 在dom重新渲染前就捕獲count進行更新 不會出現閃動情況useLayoutEffect(()=>{console.log('useLayoutEffect');if(count == 0){setCount(Math.random()+100)}})return (<><h1>count:{count}</h1><button onClick={()=>setCount(0)}>重置為0</button></>)
}