useRef 是什么
useRef(initialValue) 返回一个 在组件整个生命周期内保持不变的对象:{ current: initialValue }。
更新 ref.current 不会触发重新渲染,这点和 useState 不同。
你代码里的两种用法
1. useRef(new Animated.Value(0)).current(page/index.js 第 92 行)
const lineAnimateRef = useRef(new Animated.Value(0)).current
const onPress = (tab, index) => {
componentState.setTopTabberIndex(index)
}
useEffect(() => {
Animated.spring(lineAnimateRef, {
toValue: topTabberIndex * UIConfig.rpxToPx(100 + 96),
bounciness: 0,
useNativeDriver: true
}).start()
这里在 第一次渲染 时执行 useRef(new Animated.Value(0)),得到 { current: AnimatedValue },再取 .current,所以变量 lineAnimateRef 实际指向的是 Animated.Value 实例本身,不是「整颗 ref 对象」。
目的:保证 Animated.Value 只创建一次(不会在每次渲染时 new),同时变量名可以直接当动画值传给 Animated.spring。
若写成 const r = useRef(new Animated.Value(0)),就要用 r.current 传给 Animated.spring,语义等价。
2. useRef(null) + 传给 ref(Home/index.js 第 54–64 行)
const ScrollViewWithTopTabber = observer(({ navigation }) => {
const componentState = useComponentState()
const scrollRef = useRef(null)
useEffect(() => {
scrollRef.current.scrollTo({ x: componentState.topTabberIndex * UIConfig.width, animated: true })
}, [componentState.topTabberIndex])
return (
<ScrollView
ref={scrollRef}
这里 useRef(null) 保留整颗 ref:scrollRef.current 先是 null,挂载后由 React 填成 ScrollView 实例,用于在 useEffect 里 命令式调用 scrollTo。
常见场景归纳
| 场景 | 典型形式 | 说明 |
|---|---|---|
| 引用 DOM / 原生组件实例 |
useRef(null) + <ScrollView ref={scrollRef} />
|
调 focus、scrollTo、measure 等命令式 API |
| 保存可变值且不重渲染 |
const count = useRef(0) 或 count.current++
|
存定时器 id、上一次 props、渲染次数等 |
| 保存只创建一次的对象 |
useRef(expensive()).current 或 useRef(new Animated.Value(0)).current
|
避免每次 render 重新创建;动画值、第三方实例常用 |
| 子组件暴露方法(可选) |
useImperativeHandle + forwardRef
|
父组件通过 ref 调子组件方法 |
| 与类组件对比 | 函数组件里用 useRef 替代实例字段 |
如 this.xxx
|
示例代码
① 组件实例 ref(与你 Home 一致)
const scrollRef = useRef(null);
return <ScrollView ref={scrollRef} />;
// scrollRef.current?.scrollTo(...)
② 不触发渲染的可变数据
const prevId = useRef();
useEffect(() => {
prevId.current = id;
}, [id]);
③ 只初始化一次的值(与你 Animated.Value 一致)
// 方式 A:常用写法,变量直接是 Animated.Value
const anim = useRef(new Animated.Value(0)).current;
// 方式 B:保留 ref 对象
const animRef = useRef(new Animated.Value(0));
Animated.spring(animRef.current, { ... });
④ 存定时器 / 订阅,卸载时清理
const timerRef = useRef(null);
useEffect(() => {
timerRef.current = setInterval(...);
return () => clearInterval(timerRef.current);
}, []);
小结:第一种用 useRef 锁住只创建一次的 Animated.Value(取 .current 只是为了少写一层 xxx.current);第二种用 useRef(null) 接住组件实例,做 命令式滚动。两者都是 useRef「跨渲染保持同一引用、改 current 不触发渲染」的典型用法。