React Native中使用useSelector时如何保持ScrollView的滚动位置
问题根因
useSelector本身会订阅指定的Redux状态,只要对应的状态发生变更,就会触发当前组件重新渲染,重渲染会让ScrollView重置到初始的顶部位置,打断首次scrollTo的执行逻辑。- 把
useSelector返回值直接作为useState初始值的写法本身不合理:useState初始值仅在组件首次挂载时生效,后续Redux状态更新时,局部state不会自动同步,但useSelector仍然会触发重渲染,相当于多了一次无意义的重渲染。 - 该写法会导致每次组件渲染时
useSelector都会被执行,哪怕它的返回值已经不再用于更新局部state,额外增加了重渲染的触发概率。
你改成空数组作为初始值后滚动正常,大概率是你同步删除了组件内的useSelector调用,没有了Redux状态订阅带来的额外重渲染,自然不会出现滚动位置被重置的问题。
修复方案
根据你的业务场景可以选择以下任意一种方案:
方案1:拆分ScrollView为独立纯组件
把ScrollView和内部渲染逻辑抽成单独的组件,用
React.memo包裹,仅将渲染必需的参数作为props传入。父组件订阅Redux状态重渲染时,只要传给ScrollView的props没有变化,ScrollView就不会重渲染,滚动位置自然不会被重置。
示例代码:// 抽离的独立ScrollView组件 const ContentScrollView = React.memo(({ data1, data2, initScrollY }) => { const scrollRef = useRef(null); useEffect(() => { scrollRef.current?.scrollTo({ y: initScrollY, animated: false }); }, [initScrollY]); return ( <ScrollView ref={scrollRef}> {/* 内部渲染data1、data2的业务逻辑 */} </ScrollView> ) }) // 父组件 const Page = () => { const reduxData1 = useSelector(state => state.data1); const reduxData2 = useSelector(state => state.data2); const [data1, setData1] = useState(reduxData1); const [data2, setData2] = useState(reduxData2); // 如果需要同步Redux数据到局部state,单独监听变更 useEffect(() => setData1(reduxData1), [reduxData1]); useEffect(() => setData2(reduxData2), [reduxData2]); return <ContentScrollView data1={data1} data2={data2} initScrollY={100} /> }方案2:取消useSelector订阅,仅取Redux初始值
如果你不需要实时监听Redux中
data1、data2的变化,只是需要拿初始值赋值给局部state,可以直接调用Redux的getState()方法取值,不要用useSelector,避免额外的状态订阅带来的重渲染。
示例代码:import store from '你的redux store文件路径'; // 组件内直接取初始值 const [data1, setData1] = useState(store.getState().data1); const [data2, setData2] = useState(store.getState().data2);方案3:缓存滚动位置,重渲染后自动恢复
如果业务上必须让组件跟着Redux状态更新重渲染,可以监听ScrollView的滚动事件实时缓存位置,每次组件重渲染完成后自动滚动到缓存的位置,规避重渲染带来的位置重置问题。
示例代码:const scrollRef = useRef(null); // 用ref缓存滚动位置,重渲染不会重置ref的值 const scrollOffsetRef = useRef(0); const handleScroll = useCallback((e) => { scrollOffsetRef.current = e.nativeEvent.contentOffset.y; }, []); // 每次渲染完成后恢复滚动位置 useEffect(() => { scrollRef.current?.scrollTo({ y: scrollOffsetRef.current, animated: false }); }); return ( <ScrollView ref={scrollRef} onScroll={handleScroll} scrollEventThrottle={16} > {/* 业务渲染逻辑 */} </ScrollView> )
内容的提问来源于stack exchange,提问作者Alisan26
相关产品推荐
相关产品推荐

