You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native横向ScrollView子元素尺寸共享值存储异常问题

Reanimated横向滚动吸附功能尺寸存储异常修复

问题场景

我正在实现一款功能类似Airbnb的横向滚动筛选栏,效果如下:
横向滚动筛选栏效果示意图

需求是存储ScrollView下每个子视图元素的引用,追踪宽度不固定的各子视图宽度、内容偏移位置,实现滚动停止后自动吸附到最近子元素的效果。
当前遇到的问题:onLayout回调触发时,各子视图的尺寸数据无法正确赋值到useSharedValue定义的共享值中,最终sportTileDimensions里仅存储了运动项目列表中的1条结果。

现有问题代码如下:

const DiscoverSportFilter = () => {
  const sports = useSelector(getSports);
  const sportTileDimensions = useSharedValue({});
  const position = useSharedValue(0);
  const animatedRef = useAnimatedRef<Animated.ScrollView>();

  const scrollToNearestItem = (offset: number) => {
    'worklet';
    console.log('sportTileDimensions.current', sportTileDimensions.value);

    // do some scrolling stuff
  };

  const scrollHandler = useAnimatedScrollHandler({
    onScroll: (e, ctx) => {
      position.value = e.contentOffset.x;
    },
    onMomentumEnd: (e, ctx) => {
      scrollToNearestItem(e.contentOffset.x);
    },
  });

  const SportTile = ({ name }) => {
    return (
      <View
        onLayout={({ nativeEvent }) => {
          const { x, width } = nativeEvent.layout;
          sportTileDimensions.value = {
            ...sportTileDimensions.value,
            [name.toLowerCase()]: { x, width },
          };
        }}
        style={{ padding: 10, alignItems: 'center' }}>
        <CustomIcon
          style={{ marginBottom: 5 }}
          name={getSportIcon(name)}
          size={24}
        />
        <Text style={{ fontSize: 12 }}>{name}</Text>
      </View>
    );
  };

  return (
    <Animated.ScrollView
      ref={animatedRef}
      style={{ flexGrow: 0 }}
      horizontal
      showsHorizontalScrollIndicator={false}
      onScroll={scrollHandler}
      scrollEventThrottle={1}>
      {sports.map(({ name }) => (
        <SportTile key={name} name={name} />
      ))}
    </Animated.ScrollView>
  );
};

故障原因

  • 根因是SportTile组件定义在父组件DiscoverSportFilter内部,每次父组件重渲染都会生成全新的SportTile函数,onLayout回调触发时捕获的是初始状态的空共享值对象,每次赋值都是基于空对象新增单条key,之前写入的其他条目全部丢失,最终只会保留最后触发onLayout的那1条数据。
  • 直接展开sportTileDimensions.value做对象合并的写法,在Reanimated的共享值更新逻辑中不是原子操作,多个onLayout几乎同时触发时,会出现读写竞态,进一步加剧数据丢失问题。

修复方案

  • 将SportTile组件移到父组件外部定义,避免每次重渲染生成新组件导致的闭包陈旧问题
  • 共享值更新改用.modify()方法做原子更新,避免多回调同时触发时的竞态问题
  • 尺寸更新逻辑可以直接封装为worklet函数传入子组件,保证更新运行在UI线程,无同步延迟

修复后的核心代码参考:

// 把SportTile移到父组件外部定义
const SportTile = ({ name, onLayoutMeasure }) => {
  return (
    <View
      onLayout={({ nativeEvent }) => {
        const { x, width } = nativeEvent.layout;
        onLayoutMeasure(name.toLowerCase(), x, width);
      }}
      style={{ padding: 10, alignItems: 'center' }}>
      <CustomIcon
        style={{ marginBottom: 5 }}
        name={getSportIcon(name)}
        size={24}
      />
      <Text style={{ fontSize: 12 }}>{name}</Text>
    </View>
  );
};

const DiscoverSportFilter = () => {
  const sports = useSelector(getSports);
  const sportTileDimensions = useSharedValue({});
  const position = useSharedValue(0);
  const animatedRef = useAnimatedRef<Animated.ScrollView>();

  // 定义原子更新的worklet方法
  const updateTileDimension = (key, x, width) => {
    'worklet';
    sportTileDimensions.modify((old) => {
      'worklet';
      old[key] = { x, width };
      return old;
    });
  };

  const scrollToNearestItem = (offset: number) => {
    'worklet';
    console.log('sportTileDimensions.current', sportTileDimensions.value);
    // 滚动吸附逻辑
  };

  const scrollHandler = useAnimatedScrollHandler({
    onScroll: (e) => {
      position.value = e.contentOffset.x;
    },
    onMomentumEnd: (e) => {
      scrollToNearestItem(e.contentOffset.x);
    },
  });

  return (
    <Animated.ScrollView
      ref={animatedRef}
      style={{ flexGrow: 0 }}
      horizontal
      showsHorizontalScrollIndicator={false}
      onScroll={scrollHandler}
      scrollEventThrottle={1}>
      {sports.map(({ name }) => (
        <SportTile 
          key={name} 
          name={name} 
          onLayoutMeasure={updateTileDimension}
        />
      ))}
    </Animated.ScrollView>
  );
};

内容的提问来源于stack exchange,提问作者s2RaVe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 09:21:36