在 React Native 中,useRef
是 React 提供的一个非常有用的 Hook,它的作用是在函数组件中保持对某些数据或是组件的持久引用。并且它的改变不会触发组件重新渲染。 在之前的类组件开发模式下,我们通常会用 this
关键字来保存对组件或者数据的引用。而在函数组件中没有 this
,这时,useRef
就应运而生了。
在这篇文章中,我会介绍 useRef
的使用方式、原理、常见场景,希望可以帮助到大家。
首先,我们来看一下 useRef
在代码中是如何使用的。
基本使用
ts
// 声明一个属性
const refContainer = useRef(initialValue);
// 访问属性的值
console.log(refContainer.current);
// 修改属性的值
refContainer.current = 10;
可以看到,useRef
的使用分以下三步:
- 第一步,声明并且赋值一个初始值;
- 第二步,在对应逻辑中修改它的值。需要注意的是:我们需要修改
.current
而不是直接修改声明的变量; - 第三步,通过
refContainer.current
访问数据即可。
它的使用还是比较简单的。下面来一下具体的有哪些常用的使用场景。
useRef 使用场景举例
引用组件实例
在 React Native 的日常中,我们经常需要获取 TextInput
、ScrollView
、FlatList
等组件的实例来调用方法。
tsx
import React, { useRef } from 'react';
import { View, TextInput, Button } from 'react-native';
export default function UseRefExample() {
const inputRef = useRef<TextInput>(null);
const focusInput = () => {
inputRef.current?.focus(); // 调用 TextInput 的 focus 方法
};
return (
<View>
<TextInput ref={inputRef} placeholder="请输入内容" style={{ borderWidth: 1, padding: 8 }} />
<Button title="聚焦输入框" onPress={focusInput} />
</View>
);
}
在这个例子中,我们通过 inputRef.current.focus()
可以让输入框获得焦点,而不需要重新渲染组件。
存储可变值
useRef
也可以用来存储组件生命周期中需要的可变值,例如定时器 ID、计数器等。
tsx
import React, { useRef, useEffect, useState } from 'react';
import { View, Text, Button } from 'react-native';
export default function TimerExample() {
const [count, setCount] = useState(0);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const startTimer = () => {
if (!intervalRef.current) {
intervalRef.current = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
}
};
const stopTimer = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
useEffect(() => {
return () => stopTimer(); // 组件卸载时清理定时器
}, []);
return (
<View>
<Text>计数: {count}</Text>
<Button title="开始" onPress={startTimer} />
<Button title="停止" onPress={stopTimer} />
</View>
);
}
这里的 intervalRef
不会触发组件重新渲染,它只是存储一个定时器 ID,以便后续清理。
与 useEffect 结合使用
当我们需要访问最新的状态或 props,但又不想在依赖数组中引发无限循环时,可以借助 useRef
。
tsx
import React, { useEffect, useRef, useState } from 'react';
import { View, Button, Text } from 'react-native';
export default function LatestValueExample() {
const [count, setCount] = useState(0);
const latestCount = useRef(count);
useEffect(() => {
latestCount.current = count; // 同步最新值
}, [count]);
const showLatestCount = () => {
setTimeout(() => {
console.log('最新计数值:', latestCount.current);
}, 3000);
};
return (
<View>
<Text>计数: {count}</Text>
<Button title="增加" onPress={() => setCount(count + 1)} />
<Button title="3秒后打印最新值" onPress={showLatestCount} />
</View>
);
}
在这个例子中,即使 setTimeout
在 3 秒后才执行,也能拿到最新的 count
值,而不需要在 useEffect
或依赖数组中做复杂处理。
如果 latestCount
没有使用 useRef
,而是普通的变量的话,那么每次组件重新渲染时,它的值会被重置。当你点击「增加」按钮,count 会更新,而 latestCount
并不会持久化。你预期在 3 秒后打印正确的 count 值,但是由于 latestCount 被重置,所以打印的始终是第一次渲染时的 count 值。
控制动画或滑动位置
在 React Native 中,useRef
对配合 Animated.Value
或 ScrollView 的 scrollTo 也非常有用。
tsx
import React, { useRef } from 'react';
import { View, ScrollView, Button, Text, StyleSheet } from 'react-native';
export default function ScrollExample() {
const scrollRef = useRef<ScrollView>(null);
const scrollToBottom = () => {
scrollRef.current?.scrollToEnd({ animated: true });
};
return (
<View style={{ flex: 1 }}>
<ScrollView ref={scrollRef} style={styles.scrollView}>
{Array.from({ length: 50 }).map((_, index) => (
<Text key={index}>第 {index + 1} 行</Text>
))}
</ScrollView>
<Button title="滚动到底部" onPress={scrollToBottom} />
</View>
);
}
const styles = StyleSheet.create({
scrollView: { flex: 1, padding: 10 },
});
这里 scrollRef.current.scrollToEnd()
可以控制 ScrollView 滚动到最后。
总结
useRef
是 React Native 中非常实用的 Hook,既能替代类组件中的 this
,又能在函数组件中保存可变数据,同时保持性能优势。useRef
常用于在函数组件中创建可变引用,并且这种引用的变化不会触发组件的重新渲染。它既可以用来引用组件实例,也能用来存储一些可变的值,例如定时器的 ID 或者最新的状态,非常适合在动画、ScrollView
控制、延迟获取最新状态等场景下使用。我们额外需要注意的是:useRef
并不能用来进行管理 UI 状态,否则可能会导致逻辑混乱。