当使用React Native Reanimated 的共享值时,更新不相关的状态会导致意外行为

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

经过整整 6 个小时的磨练,我终于得到了我的逻辑挑战问题的最小可重现示例:

import "expo-dev-client";
import React, { useState } from "react";
import { Button, View } from "react-native";
import Animated, { useSharedValue } from "react-native-reanimated";

export default function App() {
  const [a, setA] = useState(false);
  const toggleScreen = () => setA((p) => !p);
  if (a) return <Content toggleScreen={toggleScreen} />;
  else
    return (
      <View style={{ flex: 1, justifyContent: "center" }}>
        <Button title="next" onPress={toggleScreen} />
      </View>
    );
}

function Content({ toggleScreen }: { toggleScreen: () => void }) {
  const [a, setA] = useState(0);
  const sharedValue = useSharedValue(0);

  return (
    <View style={{ flexGrow: 1, paddingTop: 50 }}>
      <View style={{ flexDirection: "row" }}>
        <Button title="back" onPress={toggleScreen} />
        <Button
          title={`update state ${a}`}
          onPress={() => setA((p) => p + 1)}
        />
      </View>
      <View style={{ flexGrow: 1, justifyContent: "center" }}>
        <Animated.Text
          style={{ position: "absolute", borderWidth: 1, top: sharedValue }}
        >
          {`using shared value (${sharedValue.value})`}
        </Animated.Text>
        <Animated.Text style={{ position: "absolute", top: 0 }}>
          using absolute number
        </Animated.Text>
      </View>
    </View>
  );
}

当按下“更新状态”按钮时,使用共享值的

Animated.Text
组件会跳转到父组件的中心,就好像
top
属性被忽略一样。即使状态对象完全未使用,并且共享值也从未更改,也是如此。
borderWidth
属性仍然有效。这应该发生吗?我不希望这样的事情发生。难道我做错了什么?我怎样才能让它不发生?

enter image description hereenter image description here

react-native react-native-reanimated
1个回答
0
投票

当您在 React 中更新状态时,它会触发组件及其子组件的重新渲染。此重新渲染可能会影响 Animated.Text 组件的布局。

解决方案 使用 useAnimatedStyle:确保您使用 useAnimatedStyle 来定义依赖于共享值的样式。该钩子确保当共享值更改时正确地重新计算样式。

Memoize Components:使用 React.memo 来记住状态改变时不需要重新渲染的组件。这可以防止不必要的重新渲染和布局重新计算。

分离组件:将内容组件拆分为更小的组件,以将依赖于共享值的部分与依赖于状态的部分隔离。

import "expo-dev-client";
import React, { useState } from "react";
import { Button, View } from "react-native";
import Animated, { useSharedValue, useAnimatedStyle } from "react-native-reanimated";

export default function App() {
  const [a, setA] = useState(false);
  const toggleScreen = () => setA((p) => !p);
  if (a) return <Content toggleScreen={toggleScreen} />;
  else
    return (
      <View style={{ flex: 1, justifyContent: "center" }}>
        <Button title="next" onPress={toggleScreen} />
      </View>
    );
}

const Content = React.memo(({ toggleScreen }) => {
  const [a, setA] = useState(0);
  const sharedValue = useSharedValue(0);

  const animatedStyle = useAnimatedStyle(() => {
    return {
      top: sharedValue.value,
      borderWidth: 1,
    };
  });

  return (
    <View style={{ flexGrow: 1, paddingTop: 50 }}>
      <View style={{ flexDirection: "row" }}>
        <Button title="back" onPress={toggleScreen} />
        <Button
          title={`update state ${a}`}
          onPress={() => setA((p) => p + 1)}
        />
      </View>
      <View style={{ flexGrow: 1, justifyContent: "center" }}>
        <Animated.Text style={[animatedStyle, { position: "absolute" }]}>
          {`using shared value (${sharedValue.value})`}
        </Animated.Text>
        <Animated.Text style={{ position: "absolute", top: 0 }}>
          using absolute number
        </Animated.Text>
      </View>
    </View>
  );
});

export default App;
© www.soinside.com 2019 - 2024. All rights reserved.