使用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 }