React Hooks系列】之useRef
前言
由于React的函数式组件使用起来方便(对比class组件),我将重点使用函数组件来运行开发。在这系列博客中,我将分享我所学到Hook系列API的知识。 Hooks系列主要分以下内容:
useRef是什么
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
这个ref对象只有一个current属性,你把一个东西保存在内,它的地址一直不会变。
简单示例
import React, { useRef } from "react";
export default function App() {
const r = useRef(0);
console.log(r);
const add = () => {
r.current += 1;
console.log(`r.current:${r.current}`);
};
return (
<div className="App">
<h1>r的current:{r.current}</h1>
<button onClick={add}>点击+1</button>
</div>
);
}
我在上面设置了一个r保存useRef
的结果,并把它log
打出来,我们来看看内部是什么
> {current: 0}
内部是一个current
属性,保存了我传入的0。
useRef变化不会主动使页面渲染
我们点击上方的按钮,让current
+1,可以看到页面没有主动渲染,但是新的current
的值已经变成了1
说明current属性的值发生变化,不会跟useState
或者useReducer
一样触发页面变化。要怎样解决这个问题呢?我们先来个小结再解决
小结
- 我们使用
useCurrent
传入initialValue
,可以得出其变成了一个内含current
属性的对象,current
属性对应的值为initialValue
- 根据react文档,可以知道这个对象的地址从头到位都不会变化
- useRef变化不会主动使页面渲染
应用解决
我们来使用useRef
配合useEffect
模拟一个ComponentDidUpdate
import React, { useEffect, useRef, useState } from "react";
export default function App() {
const r = useRef(0);
const [n, setN] = useState(0);
useEffect(() => {
r.current += 1;
if (r.current > 1) {
console.log("r.current:" + r.current);
console.log("n:" + n);
}
});
return (
<div className="App">
<h1>n:{n}</h1>
<h1>r.current{r.current}</h1>
<button
onClick={() => {
setN(n + 1);
}}
>
{" "}
+1
</button>
</div>
);
}
第一次渲染结果
当点击+1按钮时,
会发现执行了代码,这样就可以实现
ComponentDidUpdate
current发生变化了
上面的代码中,可以看出页面上的current
发生变化了,这是由于我们的state发生改变,所以将新的current也进行了渲染。
你可以理解为react的机制:
这里有个落单的current,我不管。
这个state发生变化了,快更新页面。等等,把这个current也带上吧。
所以我们可以通过设置一个不实用的state来手动设置current
变化主动引发页面更新的情况。
如果你细心一点,就会发现,页面上渲染的值并不是最新的current
的值。原因是Effect
总是在render
后才执行,而这时候current
刚刚变成最新的,对react
来说,它不会单单为了current
再一次执行更新视图。所以页面上依然是旧的current
。这也验证了我们的一开始的结论
useRef变化不会主动使页面渲染
我们一般不会让current显示在视图中,上面的例子只是希望你能注意到current的变化和视图之间的微妙关系
小结
由于current
不会主动让页面渲染,所以我们最好设置一个假的state,来手动设置让current随着这个假的state一起被视图所更新就行。
不得不说forwardRef
ref
在hook组件上,是不能直接使用ref的,官网甚至不推荐在class组件上使用ref。但是我们也许会用到ref,ref主要用于传递给子组件,并拿到对应子组件的dom。这里是官方文档Refs和函数组件
何时使用 Refs
下面是几个适合使用 refs 的情况:
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
解读官方例子
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 才可以引用它
const textInput = useRef(null);//创建一个包含current属性的对象
console.log(textInput);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input type="text" ref={textInput} />//挂到内部dom上
<input type="button" value="Focus the text input" onClick={handleClick} />
</div>
);
}
tips:注意这个例子没有用到
fowardRef
,因为ref没有在子组件上用,而是在函数组件内部用而已
我们可以发现只要把ref
挂到某个react元素上,就可以拿到它的dom。
一共分两个步骤
useRef
创建一个ref
对象ref={xx}
挂到react
元素上
然后就可以使用这个元素了。官方例子是取到这个元素并且通过点击按钮让元素聚焦。我们目前大概理解了ref是怎样用的。
子组件上使用ref
上面的方法不能直接在子组件上使用,也许你会这样写
<Child ref={textInput} />
但是这样还拿不到子组件的DOM,我们需要使用forwardRef
配合使用,上面的例子可以写成这样
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 才可以引用它
const textInput = useRef(null);
console.log(textInput);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<Child ref={textInput} /> //**依然使用ref传递**
<input type="button" value="Focus the text input" onClick={handleClick} />
</div>
);
}
const Child = forwardRef((props, ref) => { //** 看我 **
return <input type="text" ref={ref} />;//** 看我挂到对应的dom上 **
});
上面是通过forwardRef
把Child
函数包起来,然后传入第二个参数ref
最后挂载ref={ref}
这样就可以拿到对应的DOM了,控制台打印一下看看
current: <input type="text"></input>
拿到子组件的DOM元素了。
小结
我们通过上面的例子得出在函数组件内部和子组件上使用ref是不同的
- 如果要在函数内部使用,那就直接创建后挂ref属性给react元素就行了。
- 如果要在子组件上使用,除了上面的步骤,还需要使用
forwardRef
把子组件的函数包起来,然后再传入第二个参数ref
,最后挂载ref
就可以正常取到DOM了。
总结
我们现在已经知道useRef
会生成一个包含current属性的对象,它能够在react渲染全生命周期内保持地址不变,我们可以用它来做一些操作。
例如创建一个全局的始终不变的数据,拿来模拟ComponentDidUpdate
也可以用来挂载到react元素上,拿到这个元素的DOM。(这里区分函数内部组件直接使用ref
和子组件配合forwardRef
)
github
最后推广一下本人长期维护的 github 博客
1.从学习到总结,记录前端重要知识点,涉及 Javascript 深入、HTTP 协议、数据结构和算法、浏览器原理、ES6、前端技巧等内容。
2.在 README 中有目录可对应查找相关知识点。
如果对您有帮助,欢迎star、follow。
转载自:https://juejin.cn/post/6889824743889829902