在 React Native Typescript 中定义参数列表

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

我有一个部分屏幕,它从数据库中获取测验数据并将其传递给基于该部分的测验屏幕。 来自数据库的数据结构是:

{
  sections: [
   {
    questions: [
     {
      question: "",
      options: ["", "", ""],
      answer: "",

...

}

因为我使用的是 React Navigation with TypeScript,所以我需要为 Section 和 Quiz 屏幕定义

RootStackParamList
,例如:

type RootStackParamList = {
  Home: undefined;
  Modules: undefined;
  Sections: {data: {}};
  Quiz: {data: {}};
};

跨屏幕导航,如:

{data.sections.slice(1).map((section, index) => (
 <TouchableOpacity
  key={index}
  onPress={() => navigation.navigate('Quiz', {data: section.questions})}
 </TouchableOpacity>
))}

使用参数导航到新屏幕时,我在线上收到以下错误消息

onPress={() => navigation.navigate('Quiz', {data: section.questions})}

Argument of type '{ data: any; }' is not assignable to parameter of type '{ sections: [{ questions: []; }]; }'.
  Object literal may only specify known properties, and 'data' does not exist in type '{ sections: [{ questions: []; }]; }'.ts(2345)

如何定义

RootStackParamList
以修复错误?

模块列表.tsx

import * as React from 'react';
import {
  ScrollView,
  Text,
  TouchableOpacity,
  StyleSheet,
  useColorScheme,
  SafeAreaView,
} from 'react-native';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import {useState, useEffect} from 'react';
import {List} from 'react-native-paper';
import {db} from '../../firebase';
import {ref, onValue} from 'firebase/database';
// import { AdmobContext } from "../components/AdmobController";
import {StackNavigationProp} from '@react-navigation/stack';

type RootStackParamList = {
  Home: undefined;
  Modules: undefined;
  Sections: {data: {}};
  Quiz: undefined;
};
type ModuleListScreenNavigationProp = StackNavigationProp<
  RootStackParamList,
  'Modules'
>;
type Props = {
  navigation: ModuleListScreenNavigationProp;
};

const ModuleList = ({navigation}: Props) => {
  const [quiz, setQuiz] = useState({});
  const isDarkMode = useColorScheme() === 'dark';
  const style = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
    color: isDarkMode ? Colors.lighter : Colors.darker,
    optionBackgroundColor: isDarkMode ? '#333333' : '#D3D3D3',
  };
  // let { renderBanner } = useContext(AdmobContext);

  useEffect(() => {
    return onValue(ref(db, '/'), (querySnapShot: {val: () => {}}) => {
      const data = querySnapShot.val() || {};
      let quizItems = {...data};
      setQuiz(quizItems);
    });
  }, []);

  return (
    <ScrollView
      style={[styles.container, {backgroundColor: style.backgroundColor}]}>
      <SafeAreaView>
        <TouchableOpacity
          onPress={() => navigation.navigate('Sections', {data: quiz})}
          style={[
            styles.listItem,
            {backgroundColor: style.optionBackgroundColor},
          ]}>
          <Text style={[styles.text, {color: style.color}]}>
            Security Guard Training
          </Text>
          <List.Icon color={style.color} icon="security" />
        </TouchableOpacity>
        <TouchableOpacity
          onPress={() => navigation.navigate('Sections', {data: quiz})}
          style={[
            styles.listItem,
            {backgroundColor: style.optionBackgroundColor},
          ]}>
          <Text style={[styles.text, {color: style.color}]}>Useful Links</Text>
          <List.Icon color={style.color} icon="link" />
        </TouchableOpacity>
      </SafeAreaView>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingVertical: 30,
    paddingHorizontal: 20,
    position: 'relative',
  },
  listItem: {
    borderRadius: 10,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginBottom: 1,
    paddingHorizontal: 10,
    paddingVertical: 15,
  },
  text: {
    fontSize: 18,
    marginHorizontal: 15,
  },
});

export default ModuleList;

SectionList.tsx

import * as React from 'react';
import {
  ScrollView,
  StyleSheet,
  Text,
  TouchableOpacity,
  useColorScheme,
} from 'react-native';
import {List} from 'react-native-paper';
// import { AdmobContext } from "../components/AdmobController";
import {StackNavigationProp} from '@react-navigation/stack';
import {RouteProp} from '@react-navigation/native';
import {Colors} from 'react-native/Libraries/NewAppScreen';

type QuestionDataType = {
  question: string;
  options: string[];
  answer: string;
};
type SectionDataType = {
  sections: {
    questions: QuestionDataType[];
  }[];
};
type RootStackParamList = {
  Home: undefined;
  Modules: undefined;
  Sections: {data: QuestionDataType[]};
  Quiz: {data: QuestionDataType[]};
};
type SectionListScreenNavigationProp = StackNavigationProp<
  RootStackParamList,
  'Sections'
>;
type SectionListScreenRouteProp = RouteProp<RootStackParamList, 'Sections'>;
type Props = {
  navigation: SectionListScreenNavigationProp;
  route: SectionListScreenRouteProp;
};

const SectionList = ({navigation, route}: Props) => {
  const data = route.params.data;
  const isDarkMode = useColorScheme() === 'dark';
  const style = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
    color: isDarkMode ? Colors.lighter : Colors.darker,
    optionBackgroundColor: isDarkMode ? '#333333' : '#D3D3D3',
  };
  // let { renderBanner } = useContext(AdmobContext);

  return (
    <ScrollView
      style={[styles.container, {backgroundColor: style.backgroundColor}]}>
      <TouchableOpacity
        key={1}
        onPress={() =>
          navigation.navigate('Quiz', {data: data.sections[0].questions})
        }
        style={[
          styles.listItem,
          {backgroundColor: style.optionBackgroundColor},
        ]}>
        <Text style={[styles.text, {color: style.color}]}>Section 1</Text>
        <List.Icon color={style.color} icon="frequently-asked-questions" />
      </TouchableOpacity>

      {data.sections.slice(1).map((section, index) => (
        <TouchableOpacity
          key={index}
          onPress={() => navigation.navigate('Quiz', {data: section.questions})}
          style={[
            styles.listItem,
            {backgroundColor: style.optionBackgroundColor},
          ]}>
          <Text style={[styles.text, {color: style.color}]}>
            {'Section '}
            {index + 2}
          </Text>
          <List.Icon color={style.color} icon="frequently-asked-questions" />
        </TouchableOpacity>
      ))}
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingVertical: 30,
    paddingHorizontal: 20,
    position: 'relative',
  },
  listItem: {
    borderRadius: 10,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginBottom: 1,
    paddingHorizontal: 10,
    paddingVertical: 15,
  },
  text: {
    fontSize: 18,
    marginHorizontal: 15,
  },
});

export default SectionList;

reactjs typescript react-native react-native-navigation react-typescript
2个回答
0
投票

您可以首先为您的部分和问题定义类型,如下所示:

type QuestionDataType = {
  question: string,
  options: string[],
  answer: string,
}
type SectionDataType = {
 sections: {
   questions: QuestionDataType[];
 }[];
}

然后在 RootStackParamList 中输入:

type RootStackParamList = {
  Home: undefined;
  Modules: undefined;
  Sections: {data: QuestionDataType[]};
  Quiz: {data: {}};
};

请记住,当您从服务器(您的数据库)获取数据时,为存储响应的变量/状态定义SectionDataType,直到使用它在导航中传递数据时一切正常。 最后,您的导航部分将无误地执行:

{data.sections.slice(1).map((section, index) => (
 <TouchableOpacity
  key={index}
  onPress={() => navigation.navigate('Quiz', {data: section.questions})}
 </TouchableOpacity>
))}

0
投票

我有一个方便的方法来声明我的 React-Navigation 东西。

在你的

AppNavigator.tsx

里面
// Telling TypeScript what to expect as "route" identifier
export interface AppRoutes {
  home: "home";
  settings: "settings";
}

// Telling TypeScript what every screen will expect as ParamTypes
//  - home screen will ask for 'HomeProps', but it's optional
//  - settings screen will ask for 'SettingsProps', required!
export type AppNavigationScreenProps = {
  home?: HomeProps;
  settings: SettingsProps;
}

// Black magic
export type AppStackParamList = {
  [key in keyof AppRoutes]: AppNavigationScreenProps[key];
};

// Stack navigator for this route that uses black magic
const Stack = createNativeStackNavigator<AppStackParamList>();


// Interface for my component (empty, but one does not know when he will need it)
interface AppNavigatorProps {}

// Navigator component, home of all navigation
const AppNavigator: React.FC<AppNavigatorProps> = (_: AppNavigatorProps) => {
    // ... things
    return (
        <NavigationContainer>
            <Stack.Navigator>
                <Stack.Screen
                    name="home" // This will be restricted to "home" and "settings"
                    component={Home}  // Only "Home" component can fit here, see Home.tsx
                    options={/* Things */}
                />
                <Stack.Screen
                    name="settings" // This will be restricted to "home" and "settings"
                    component={Settings} // Only "Settings" component can fit here, see Settings.tsx
                    options={/* Things */}
                />
            </Stack.Navigator>
        </NavigationContainer>
    )
}

然后在您的

Home.tsx
Settings.tsx
组件中:

// ---------------------------------------
// Home.tsx
// ---------------------------------------

// This is the interface that AppNavigation.tsx was referencing!
export interface HomeProps {
    myData: string[]
    myOptionalData?: number;
    // ...and so on
}

// Black Magic 2.0 that will make "Home" fit as "home" reference
type Props = NativeStackScreenProps<AppNavigationScreenProps, "home">;

// Home component!
const Home: React.FC<Props> = ({ navigation, route }: Props) => {
    // Can be undefined, since HomeProps is optional!
    const params = route.params; 
    const myData = params?.myData;

    // ....things and black magic
}

// ---------------------------------------
// Settings.tsx
// ---------------------------------------

// This is the interface that AppNavigation.tsx was referencing!
export interface SettingsProps {
    name: string;
    myOtherData: MyDataType;
}

// Black Magic 2.0 that will make "Settings" fit as "settings" reference
type Props = NativeStackScreenProps<AppNavigationScreenProps, "settings">;

// Setting component!
const Settings: React.FC<Props> = ({ navigation, route }: Props) => {
    // It's not optional can use it directly!
    const { name, myOtherData } = route.params; 

    // ....things and black magic
}

希望它能帮助确保你的数据在你制作的每个屏幕上都被正确输入。

确保你的数据是可序列化的(你不应该将函数作为参数传递),否则你会收到一些关于屏幕到屏幕通信的警告。

© www.soinside.com 2019 - 2024. All rights reserved.