React Native:将 useState() 数据传递给不相关的屏幕

React Native: Passing useState() data to unrelated screens

说明:我正在创建一个健身应用程序,我的健身应用程序有一个名为 WorkoutTimer 的组件,它连接到锻炼屏幕,并且可以通过主屏幕访问该屏幕。在 WorkoutTimer 中,我有一个 exerciseCount useState(),它会在每次计时器执行完整循环(进入下一个练习)时计数。我有一个名为 StatsScreen 的不同屏幕,可通过 HomeScreen 选项卡访问,我计划显示(并保存)已完成的练习数。

我所做的:我确实花了一整天的时间来研究这个问题,但对于不相关的屏幕来说似乎有点困难。我看到我可能不得不使用 useContext() 但这似乎非常困难。我对原生反应还很陌生,所以我正在尽我最大的努力哈哈!我附上了我认为需要的每个屏幕的代码,并附上了我的主屏幕选项卡的屏幕截图,以便您了解我的应用程序的工作原理。

WorkoutTimer.js

import React, { useState, useEffect, useRef } from "react";
import {
  StyleSheet,
  Text,
  View,
  TouchableOpacity,
  Button,
  Animated,
  Image,
  SafeAreaView,
} from "react-native";

import { CountdownCircleTimer } from "react-native-countdown-circle-timer";
import { Colors } from "../colors/Colors";

export default function WorkoutTimer() {
  const [count, setCount] = useState(1);
  const [exerciseCount, setExerciseCount] = useState(0);
  const [workoutCount, setWorkoutCount] = useState(0);

  const exercise = new Array(21);
  exercise[1] = require("../assets/FR1.png");
  exercise[2] = require("../assets/FR2.png");
  exercise[3] = require("../assets/FR3.png");
  exercise[4] = require("../assets/FR4.png");
  exercise[5] = require("../assets/FR5.png");
  exercise[6] = require("../assets/FR6.png");
  exercise[7] = require("../assets/FR7.png");
  exercise[8] = require("../assets/FR8.png");
  exercise[9] = require("../assets/S1.png");
  exercise[10] = require("../assets/S2.png");
  exercise[11] = require("../assets/S3.png");
  exercise[12] = require("../assets/S4.png");
  exercise[13] = require("../assets/S5.png");
  exercise[14] = require("../assets/S6.png");
  exercise[15] = require("../assets/S7.png");
  exercise[16] = require("../assets/S8.png");
  exercise[17] = require("../assets/S9.png");
  exercise[18] = require("../assets/S10.png");
  exercise[19] = require("../assets/S11.png");
  exercise[20] = require("../assets/S12.png");
  exercise[21] = require("../assets/S13.png");

  return (
    <View style={styles.container}>
      <View style={styles.timerCont}>
        <CountdownCircleTimer
          isPlaying
          duration={45}
          size={240}
          colors={"#7B4FFF"}
          onComplete={() => {
            setCount((prevState) => prevState + 1);
            setExerciseCount((prevState) => prevState + 1);

            if (count == 21) {
              return [false, 0];
            }
            return [(true, 1000)]; // repeat animation for one second
          }}
        >
          {({ remainingTime, animatedColor }) => (
            <View>
              <Image
                source={exercise[count]}
                style={{
                  width: 150,
                  height: 150,
                }}
              />
              <View style={styles.timeOutside}>
                <Animated.Text
                  style={{
                    color: animatedColor,
                    fontSize: 18,
                    position: "absolute",
                    marginTop: 67,
                    marginLeft: 35,
                  }}
                >
                  {remainingTime}
                </Animated.Text>
                <Text style={styles.value}>seconds</Text>
              </View>
            </View>
          )}
        </CountdownCircleTimer>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({})
    

WorkoutScreen.js

import React, { useState } from "react";
import { StyleSheet, Text, View } from "react-native";

import WorkoutTimer from "../components/WorkoutTimer";

export default function WorkoutScreen() {
  return (
    <View style={styles.container}>
      <WorkoutTimer />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});
    

HomeScreen.js

import React from "react";
import { StyleSheet, Text, View, SafeAreaView, Button } from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import { AntDesign } from "@expo/vector-icons";

import { Colors } from "../colors/Colors";

export default function HomeScreen({ navigation }) {
  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles.pageRef}>SUMMARY</Text>
      <Text style={styles.heading}>STRETCH & ROLL</Text>
      <View style={styles.content}>
        <TouchableOpacity
          style={styles.timerDefault}
          onPress={() => navigation.navigate("WorkoutScreen")}
        >
          <Button title="START WORKOUT" color={Colors.primary} />
        </TouchableOpacity>
        <TouchableOpacity
          style={styles.statContainer}
          onPress={() => navigation.navigate("StatsScreen")}
        >
          <AntDesign name="barschart" size={18} color={Colors.primary} />
          <Text style={{ color: Colors.primary }}>Statistics</Text>
          <AntDesign name="book" size={18} color={Colors.primary} />
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({})
    

StatsScreen.js

import React from "react";
import { StyleSheet, Text, View } from "react-native";
import { exerciseCount, workoutCount } from "../components/WorkoutTimer";

export default function StatsScreen() {
  return (
    <View style={styles.container}>
      <Text display={exerciseCount} style={styles.exerciseText}>
        {exerciseCount}
      </Text>
      <Text display={workoutCount} style={styles.workoutText}>
        {workoutCount}
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({});

Home Screen Image

据我所知,您快完成了!你试图让你的 2 状态 来自 WorkoutTimer 的变量如下所示:

import { exerciseCount, workoutCount } from "../components/WorkoutTimer";

不幸的是,这行不通:( 。这两个变量在您的整个过程中都会发生变化 应用程序的 life-time 有点 makes them "special"

在React中,这些变量需要在parent组件中声明 并传递给所有对它们感兴趣的 children。 因此,在您当前的设置中,您有一个 parent child 关系,例如:

HomeScreen -> WorkoutScreen -> WorkoutTimer.

如果将变量移动到主屏幕 (HomeScreen.js)

export default function HomeScreen({ navigation }) {
  const [exerciseCount, setExerciseCount] = useState(0);
  const [workoutCount, setWorkoutCount] = useState(0);

然后您可以将它们传递给 WorkoutScreenStatsScreen 喜欢:

navigation.navigate("WorkoutScreen", { exerciseCount })
navigation.navigate("StatsScreen", { exerciseCount })

您可能需要阅读 react-navigation 的 .navigate 文档我不确定我是否记得正确。

为了读取变量,您可以:

  export default function WorkoutScreen({ navigation }) {
    const exerciseCount  = navigation.getParam(exerciseCount);

    return (
      <View style={styles.container}>
        <WorkoutTimer exerciseCount={exerciseCount} />
      </View>
    );
  }

最后在 WorkoutTimer 中显示:

export default function WorkoutTimer({ exerciseCount }) {

当然这只是解决方案的一部分,因为您还必须通过 沿途更新您的变量(setExerciseCountsetWorkoutCount)。

我鼓励您通读我发布的链接并尝试让它发挥作用。 在你积累了一些这样的有状态变量之后,你可能还想看看 Redux,但现在这有点多了。

你的应用看起来很酷,坚持下去!

如果有人好奇,我最终用 useContext 解决了这个问题,最初很难解决。但是一旦我理解了它,它就不难理解了。

我使用以下代码创建了另一个名为 exerciseContext 的文件:

import React, { useState, createContext } from "react";

const ExerciseContext = createContext([{}, () => {}]);

const ExerciseProvider = (props) => {
  const [state, setState] = useState(0);
  //{ exerciseCount: 0, workoutCount: 0 }

  return (
    <ExerciseContext.Provider value={[state, setState]}>
      {props.children}
    </ExerciseContext.Provider>
  );
};

export { ExerciseContext, ExerciseProvider };

在 App.js 中,我使用了 ExerciseProvider,它允许我在屏幕上传递数据。

  if (fontsLoaded) {
    return (
      <ExerciseProvider>
        <NavigationContainer>
          <MyTabs />
        </NavigationContainer>
      </ExerciseProvider>
    );
  } else {
    return (
      <AppLoading startAsync={getFonts} onFinish={() => setFontsLoaded(true)} />
    );
  }
}

我可以这样调用它:

import { ExerciseContext } from "../components/ExerciseContext";

const [exerciseCount, setExerciseCount] = useContext(ExerciseContext);

这意味着我也可以改变状态!轰,解决了!如果有人需要解释,请告诉我!

我认为你必须使用 Mobx 或 Redux 进行状态管理。这对你来说会更有效率 built-in state.