我有一个部分屏幕,它从数据库中获取测验数据并将其传递给基于该部分的测验屏幕。 来自数据库的数据结构是:
{
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;
您可以首先为您的部分和问题定义类型,如下所示:
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>
))}
我有一个方便的方法来声明我的 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
}
希望它能帮助确保你的数据在你制作的每个屏幕上都被正确输入。
确保你的数据是可序列化的(你不应该将函数作为参数传递),否则你会收到一些关于屏幕到屏幕通信的警告。