使用useRef
有段時間了,最近梳理了useRef
的使用細節。html
函數組件每次渲染都會被執行,函數內部的局部變量通常會從新建立,利用useRef
能夠訪問上次渲染的變量,相似類組件的實例變量
效果。react
createRef
不行嗎?createRef
主要解決class
組件訪問DOM元素問題,而且最佳實踐是在組件週期內只建立一次(通常在構造函數裏調用)。若是在函數組件內使用createRef
會形成每次render
都會調用createRef
:git
function WithCreateRef() { const [minus, setMinus] = useState(0); // 每次render都會從新建立`ref` const ref = React.createRef(null); const handleClick = () => { setMinus(minus + 1); }; // 這裏每次都是`null` console.log(`ref.current=${ref.current}`) useEffect(() => { console.log(`denp[minus]>`, ref.current && ref.current.innerText); }, [minus]); return ( <div className="App"> <h1 ref={ref}>Num: {minus}</h1> <button onClick={handleClick}>Add</button> </div> ); }
見文檔github
useRef
返回值都不變;ref.current
發生變化並不會形成re-render
;ref.current
發生變化應該做爲Side Effect
(由於它會影響下次渲染),因此不該該在render
階段更新current
屬性。不能夠
在render
裏更新ref.current
值在Is there something like instance variables提到:npm
Unless you’re doing lazy initialization, avoid setting refs during rendering — this can lead to surprising behavior. Instead, typically you want to modify refs in event handlers and effects.
在render
裏更新refs
致使什麼問題呢?
在異步渲染裏render
階段可能會屢次執行。數組
const RenderCounter = () => { const counter = useRef(0); // counter.current的值可能增長不止一次 counter.current = counter.current + 1; return ( <h1>{`The component has been re-rendered ${counter.current} times`}</h1> ); }
能夠
在render
裏更新ref.current
值一樣也是在Is there something like instance variables提到的:less
Unless you’re doing lazy initialization, avoid setting refs during rendering — this can lead to surprising behavior. Instead, typically you want to modify refs in event handlers and effects.
爲啥lazy initialization
卻能夠在render
裏更新ref.current
值?
這個跟useRef
懶初始化的實現方案有關。異步
const instance = React.useRef(null) if (instance.current == null) { instance.current = { // whatever you need } }
本質上只要保證每次render
不會形成意外效果,均可以在render階段
更新ref.current
。但最好別這樣,容易形成問題,useRef
懶初始化畢竟是個特殊的例外。ide
ref.current
不能夠
做爲其餘hooks(useMemo
, useCallback
, useEffect
)依賴項ref.current
的值發生變動並不會形成re-render
, Reactjs並不會跟蹤ref.current
的變化。函數
function Minus() { const [minus, setMinus] = useState(0); const ref = useRef(null); const handleClick = () => { setMinus(minus + 1); }; console.log(`ref.current=${ref.current && ref.current.innerText}`) // #1 uesEffect useEffect(() => { console.log(`denp[ref.current] >`, ref.current && ref.current.innerText); }, [ref.current]); // #2 uesEffect useEffect(() => { console.log(`denp[minus]>`, ref.current && ref.current.innerText); }, [minus]); return ( <div className="App"> <h1 ref={ref}>Num: {minus}</h1> <button onClick={handleClick}>Add</button> </div> ); }
本例子中當點擊[Add]按鈕兩次後#1 uesEffect
就不會再執行了,如圖:
緣由分析:
依賴項判斷是在render
階段判斷的,發生在在ref.current
更新以前,而useEffect
的effect函數執行在渲染以後。
第一次執行:
首次無腦執行,因此輸出:
ref.current=null denp[ref.current] > Num: 0 denp[minus]> Num: 0
而且此時ref.current
爲null
,因此 #1 uesEffect
至關於useEffect(() => console.log('num 1'), [null])
點擊[Add],第二次執行:
此時ref.current
值爲<h1>Num: 0<h1>
,因此 #1 uesEffect
的依賴項發生變化,最終輸出:
ref.current=Num: 0 denp[ref.current] > Num: 1 denp[minus]> Num: 1
此時 #1 uesEffect
至關於useEffect(() => console.log('num 1'), [<h1>Num: 0<h1>])
點擊[Add],第三次執行:
此時ref.current
值爲<h1>Num: 1<h1>
,因此 #1 uesEffect
的依賴項沒有發生變化,故 #1 uesEffect
的effect函數不會被執行,最終輸出:
ref.current=Num: 1 denp[minus]> Num: 2
若是將ref.current
做爲依賴項,eslint-plugin-react-hooks
也會報警提示的:
React Hook useEffect has an unnecessary dependency: 'ref.current'. Either exclude it or remove the dependency array. Mutable values like 'ref.current' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
ref
做爲其餘hooks(useMemo
, useCallback
, useEffect
)依賴項ref
是不變的,不必做爲其餘hooks依賴。
本質上是記憶hook,但也可做爲data hook,能夠簡單的用useState
模擬useRef
:
const useRef = (initialValue) => { const [ref] = useState({ current: initialValue}); return ref }