useSharedValue 在功能组件被重新渲染时没有重新初始化

问题描述 投票:0回答:0

场景: 我有一个 TextInput,您可以在其中输入一些名称。每个名称都存储在一个数组中,我想在 ScrollView 中呈现它们。用户应该能够通过拖放来重新排列 ScrollView 中的名称。
我的代码基于这个很棒的 Expo Snack,它展示了带有拖放功能的 ScrollView。小吃是这个YouTube教程的更新(和工作)版本。

它的工作方式是有一个 Reanimated Shared Value,它使用一个对象将每个数组项的键映射到它的当前位置。这个对象的类型是

{ [id: string]: number }
。对于我的名字数组(请假设每个名字都是唯一的)这可能是这样的:

{
    "Thomas": 2,
    "Stefan": 0,
    "Alex": 1,
}

共享值

positions
SortableList.tsx
中用这样的对象初始化并传递给每个列表项。当前移动的列表项更新共享值。
我的列表和项目组件代码与 Snack 几乎相同,所以我将我的代码片段放在下面稍微短一些。
与 Snack 相比,唯一的主要区别是,如果一个新名称被添加到数组中并且想将新名称附加到列表的末尾,我将尝试重新呈现 SortableList。

如果有人可以告诉我如何在不破坏 ScrollView 的情况下在 Snack 中动态添加新歌曲,那将是完全足够的:-)

问题: 当我重新渲染 SortableList 并像这样将一个新对象传递给 useSharedValue 时

const positions = useSharedValue({"Thomas": 2,"Stefan": 0,"Alex": 1,"NewName": 4});
hook stills 返回旧值。我确实设置了断点并检查了所有内容:重新呈现 SortableList,重新呈现每个子项,新名称已成功添加到数组并呈现为列表的子项。只有 useSharedValue 仍然返回陈旧的 postitions 对象。

预期行为: 当我的组件重新渲染并且我将一个新对象传递给 useSharedValue 时,它应该返回一个基于新对象的新共享值。

代码:

export default function TextInputAndList() {
  const [text, setText] = useState("");
  const textInputRef = useRef<TextInput>(null);
// dummyData for initially rendering a scrollable list. Would be empty once everything works.
  const [names, setNames] = useState<string[]>(dummyData);

  function addName() {
    if (textInputRef.current) {
      textInputRef.current.clear();
      setNames(prev => [...prev, text]);
    }
  }

  return (
    <View>
        <TextInput
          ref={textInputRef}
          placeholder="Enter name"
          onChangeText={newText => setText(newText)}
          onSubmitEditing={addName}
          defaultValue={text}
        />
      <SortableList listItems={names} />
    </View>
  );
}
function SortableList({ listItems }: { listItems: string[] }) {

// I'm aware that this reverts my rearrangment. Please see my comment below.
  const positions = useSharedValue(listToObject(listItems)); // The troublemaker

  const scrollY = useSharedValue(0);
  const autoScroll = useSharedValue(ScrollDirection.None);
  const scrollViewRef = useAnimatedRef<Animated.ScrollView>();
  const dimensions = useWindowDimensions();
  const insets = useSafeAreaInsets();
  const [contentHeight, setContentHeight] = useState(listItems.length * HEIGHT);

  useAnimatedReaction(
    () => scrollY.value,
    scrolling => {
      scrollTo(scrollViewRef, 0, scrolling, false);
    }
  );

  const handleScroll = useAnimatedScrollHandler(event => {
    scrollY.value = event.contentOffset.y;
  });

  const containerHeight = dimensions.height - insets.top - insets.bottom;

  return (
    <>
      <View style={{ flex: 1 }}>
        <Animated.ScrollView
          ref={scrollViewRef}
          onScroll={handleScroll}
          scrollEventThrottle={16}
          style={{
            flex: 1,
            position: "relative",
          }}
          contentContainerStyle={{
            height: contentHeight,
          }}
        >
          {listItems.map(listItem => (
            <MovableObject
              key={listItem}
              id={listItem}
              positions={positions}
              lowerBound={scrollY}
              autoScrollDirection={autoScroll}
              itemsCount={listItems.length}
              containerHeight={containerHeight}
            />
          ))}
        </Animated.ScrollView>
      </View>
    </>
  );
}
// memo() to avoid rerendering whenever the TextInput changes
export default React.memo(SortableList);

MovableObject请参考截图中的MovableSong

到目前为止我尝试了什么: 我知道如果 useSharedValue 实际上会重新初始化,我发布的代码会恢复所有拖放重新排列。
但是,我已经尝试将

listToObject(listItems)
存储在一个状态中并将其作为 props 传递,并将其存储在 redux 状态中。我仍然遇到同样的问题,所以这就是为什么我要尽可能简单地在这里编写代码。
我还尝试在 TextInputAndList 组件中初始化共享值,但这也没有改变任何事情。 我还尝试了一些非常厚颜无耻的 useEffect hacks,如果播放器数组发生变化,我会手动设置 positions.value。这确实导致值更新,但之后我无法触发 ScrollView 的重新呈现。使用 RefreshControl 之类的东西也不会导致我的新名字出现。

TLDR: 我想要一个可滚动的列表,我可以在其中通过拖放重新排列项目并动态添加新项目。一个现有的例子是 Spotify 的播放列表编辑器。但不是删除列表项的“减号”按钮我想要(一个 TextInput)添加新的。

react-native drag-and-drop scrollview rendering react-native-reanimated
© www.soinside.com 2019 - 2024. All rights reserved.