Skip to content

多页面路由

在开发个人主页/小工具APP时,多页面路由是一个重要的功能。在这一节中,我们将学习如何使用 React Navigation 实现多页面路由,为应用添加多个页面并实现页面间的导航。

1. 项目初始化

首先,让我们初始化一个新的 React Native 项目:

bash
npx react-native init ToolApp
cd ToolApp

2. 安装必要的依赖

我们需要安装以下依赖:

bash
# 安装导航库
npm install @react-navigation/native @react-navigation/stack @react-navigation/bottom-tabs

# 安装导航依赖
npm install react-native-screens react-native-safe-area-context react-native-gesture-handler

# 安装UI组件库
npm install react-native-paper

# 安装图标库
npm install react-native-vector-icons

# 安装状态管理库
npm install @reduxjs/toolkit react-redux

3. 创建导航结构

3.1 创建底部标签导航

js
// src/navigation/BottomTabNavigator.js
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';
import HomeScreen from '../screens/HomeScreen';
import ToolsScreen from '../screens/ToolsScreen';
import ProfileScreen from '../screens/ProfileScreen';

const Tab = createBottomTabNavigator();

const BottomTabNavigator = () => {
  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: ({ focused, color, size }) => {
          let iconName;

          if (route.name === 'Home') {
            iconName = focused ? 'home' : 'home-outline';
          } else if (route.name === 'Tools') {
            iconName = focused ? 'grid' : 'grid-outline';
          } else if (route.name === 'Profile') {
            iconName = focused ? 'person' : 'person-outline';
          }

          return <Ionicons name={iconName} size={size} color={color} />;
        },
        tabBarActiveTintColor: '#007AFF',
        tabBarInactiveTintColor: 'gray',
        headerShown: false,
      })}
    >
      <Tab.Screen name="Home" component={HomeScreen} options={{ title: '首页' }} />
      <Tab.Screen name="Tools" component={ToolsScreen} options={{ title: '工具' }} />
      <Tab.Screen name="Profile" component={ProfileScreen} options={{ title: '我的' }} />
    </Tab.Navigator>
  );
};

export default BottomTabNavigator;

3.2 创建主导航栈

js
// src/navigation/AppNavigator.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import BottomTabNavigator from './BottomTabNavigator';
import CalculatorScreen from '../screens/CalculatorScreen';
import NotesScreen from '../screens/NotesScreen';
import NoteDetailScreen from '../screens/NoteDetailScreen';
import SettingsScreen from '../screens/SettingsScreen';
import AboutScreen from '../screens/AboutScreen';

const Stack = createStackNavigator();

const AppNavigator = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator
        screenOptions={{
          headerShown: false,
        }}
      >
        <Stack.Screen name="Main" component={BottomTabNavigator} />
        <Stack.Screen name="Calculator" component={CalculatorScreen} />
        <Stack.Screen name="Notes" component={NotesScreen} />
        <Stack.Screen name="NoteDetail" component={NoteDetailScreen} />
        <Stack.Screen name="Settings" component={SettingsScreen} />
        <Stack.Screen name="About" component={AboutScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

export default AppNavigator;

4. 创建页面组件

4.1 创建首页

js
// src/screens/HomeScreen.js
import React from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Image } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { Ionicons } from '@expo/vector-icons';

const HomeScreen = () => {
  const navigation = useNavigation();

  const tools = [
    {
      id: 'calculator',
      name: '计算器',
      icon: 'calculator',
      description: '简单易用的计算器',
      screen: 'Calculator',
    },
    {
      id: 'notes',
      name: '记事本',
      icon: 'document-text',
      description: '记录你的想法',
      screen: 'Notes',
    },
    {
      id: 'timer',
      name: '计时器',
      icon: 'time',
      description: '精确计时工具',
      screen: 'Timer',
    },
    {
      id: 'weather',
      name: '天气',
      icon: 'partly-sunny',
      description: '实时天气信息',
      screen: 'Weather',
    },
  ];

  return (
    <ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
      <View style={styles.header}>
        <Text style={styles.greeting}>你好,欢迎使用小工具</Text>
        <Text style={styles.subGreeting}>选择一个工具开始使用</Text>
      </View>

      <View style={styles.toolsGrid}>
        {tools.map((tool) => (
          <TouchableOpacity
            key={tool.id}
            style={styles.toolCard}
            onPress={() => navigation.navigate(tool.screen)}
          >
            <View style={styles.toolIconContainer}>
              <Ionicons name={tool.icon} size={32} color="#007AFF" />
            </View>
            <Text style={styles.toolName}>{tool.name}</Text>
            <Text style={styles.toolDescription}>{tool.description}</Text>
          </TouchableOpacity>
        ))}
      </View>

      <View style={styles.section}>
        <Text style={styles.sectionTitle}>最近使用</Text>
        <View style={styles.recentTools}>
          {tools.slice(0, 2).map((tool) => (
            <TouchableOpacity
              key={tool.id}
              style={styles.recentToolCard}
              onPress={() => navigation.navigate(tool.screen)}
            >
              <Ionicons name={tool.icon} size={24} color="#007AFF" />
              <Text style={styles.recentToolName}>{tool.name}</Text>
            </TouchableOpacity>
          ))}
        </View>
      </View>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F5F5',
  },
  header: {
    backgroundColor: 'white',
    paddingHorizontal: 20,
    paddingVertical: 30,
    borderBottomLeftRadius: 20,
    borderBottomRightRadius: 20,
    marginBottom: 20,
  },
  greeting: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 8,
  },
  subGreeting: {
    fontSize: 16,
    color: '#666',
  },
  toolsGrid: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    paddingHorizontal: 16,
    marginBottom: 20,
  },
  toolCard: {
    width: '48%',
    backgroundColor: 'white',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
    marginHorizontal: '1%',
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 1,
    },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  toolIconContainer: {
    width: 60,
    height: 60,
    borderRadius: 12,
    backgroundColor: '#E3F2FD',
    justifyContent: 'center',
    alignItems: 'center',
    marginBottom: 12,
  },
  toolName: {
    fontSize: 16,
    fontWeight: '600',
    color: '#333',
    marginBottom: 8,
  },
  toolDescription: {
    fontSize: 14,
    color: '#666',
  },
  section: {
    paddingHorizontal: 16,
    marginBottom: 20,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: '600',
    color: '#333',
    marginBottom: 12,
  },
  recentTools: {
    flexDirection: 'row',
  },
  recentToolCard: {
    flex: 1,
    backgroundColor: 'white',
    borderRadius: 12,
    padding: 16,
    marginRight: 12,
    flexDirection: 'row',
    alignItems: 'center',
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 1,
    },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  recentToolName: {
    marginLeft: 12,
    fontSize: 16,
    color: '#333',
  },
});

export default HomeScreen;

4.2 创建工具页面

js
// src/screens/ToolsScreen.js
import React from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { Ionicons } from '@expo/vector-icons';

const ToolsScreen = () => {
  const navigation = useNavigation();

  const toolCategories = [
    {
      id: 'calculators',
      name: '计算工具',
      icon: 'calculator',
      tools: [
        { id: 'basic', name: '基础计算器', screen: 'Calculator' },
        { id: 'scientific', name: '科学计算器', screen: 'ScientificCalculator' },
        { id: 'converter', name: '单位转换', screen: 'Converter' },
      ],
    },
    {
      id: 'productivity',
      name: ' productivity',
      icon: 'document-text',
      tools: [
        { id: 'notes', name: '记事本', screen: 'Notes' },
        { id: 'todo', name: '待办清单', screen: 'Todo' },
        { id: 'reminder', name: '提醒', screen: 'Reminder' },
      ],
    },
    {
      id: 'utilities',
      name: '实用工具',
      icon: 'grid',
      tools: [
        { id: 'timer', name: '计时器', screen: 'Timer' },
        { id: 'stopwatch', name: '秒表', screen: 'Stopwatch' },
        { id: 'weather', name: '天气', screen: 'Weather' },
      ],
    },
  ];

  return (
    <ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
      <View style={styles.header}>
        <Text style={styles.title}>工具</Text>
        <Text style={styles.subtitle}>选择一个工具类别</Text>
      </View>

      {toolCategories.map((category) => (
        <View key={category.id} style={styles.category}>
          <View style={styles.categoryHeader}>
            <Ionicons name={category.icon} size={24} color="#007AFF" />
            <Text style={styles.categoryName}>{category.name}</Text>
          </View>

          <View style={styles.toolsList}>
            {category.tools.map((tool) => (
              <TouchableOpacity
                key={tool.id}
                style={styles.toolItem}
                onPress={() => navigation.navigate(tool.screen)}
              >
                <Text style={styles.toolName}>{tool.name}</Text>
                <Ionicons name="chevron-forward" size={20} color="#999" />
              </TouchableOpacity>
            ))}
          </View>
        </View>
      ))}
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F5F5',
  },
  header: {
    backgroundColor: 'white',
    paddingHorizontal: 20,
    paddingVertical: 30,
    borderBottomLeftRadius: 20,
    borderBottomRightRadius: 20,
    marginBottom: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 8,
  },
  subtitle: {
    fontSize: 16,
    color: '#666',
  },
  category: {
    backgroundColor: 'white',
    borderRadius: 12,
    padding: 16,
    marginHorizontal: 16,
    marginBottom: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 1,
    },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  categoryHeader: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 16,
  },
  categoryName: {
    fontSize: 18,
    fontWeight: '600',
    color: '#333',
    marginLeft: 12,
  },
  toolsList: {
    borderTopWidth: 1,
    borderTopColor: '#F0F0F0',
    paddingTop: 12,
  },
  toolItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingVertical: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#F0F0F0',
  },
  toolName: {
    fontSize: 16,
    color: '#333',
  },
});

export default ToolsScreen;

4.3 创建个人主页

js
// src/screens/ProfileScreen.js
import React from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Image } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { Ionicons } from '@expo/vector-icons';

const ProfileScreen = () => {
  const navigation = useNavigation();

  const menuItems = [
    {
      id: 'settings',
      name: '设置',
      icon: 'settings',
      screen: 'Settings',
    },
    {
      id: 'about',
      name: '关于',
      icon: 'information-circle',
      screen: 'About',
    },
    {
      id: 'feedback',
      name: '反馈',
      icon: 'mail',
      screen: 'Feedback',
    },
    {
      id: 'share',
      name: '分享',
      icon: 'share-social',
      screen: 'Share',
    },
  ];

  return (
    <ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
      <View style={styles.header}>
        <View style={styles.profileInfo}>
          <Image
            source={{ uri: 'https://randomuser.me/api/portraits/men/32.jpg' }}
            style={styles.avatar}
          />
          <View style={styles.userInfo}>
            <Text style={styles.userName}>用户</Text>
            <Text style={styles.userEmail}>user@example.com</Text>
          </View>
        </View>
      </View>

      <View style={styles.menu}>
        {menuItems.map((item) => (
          <TouchableOpacity
            key={item.id}
            style={styles.menuItem}
            onPress={() => navigation.navigate(item.screen)}
          >
            <View style={styles.menuItemLeft}>
              <Ionicons name={item.icon} size={24} color="#007AFF" />
              <Text style={styles.menuItemName}>{item.name}</Text>
            </View>
            <Ionicons name="chevron-forward" size={20} color="#999" />
          </TouchableOpacity>
        ))}
      </View>

      <View style={styles.footer}>
        <Text style={styles.version}>版本 1.0.0</Text>
      </View>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F5F5',
  },
  header: {
    backgroundColor: 'white',
    paddingHorizontal: 20,
    paddingVertical: 30,
    borderBottomLeftRadius: 20,
    borderBottomRightRadius: 20,
    marginBottom: 20,
  },
  profileInfo: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  avatar: {
    width: 80,
    height: 80,
    borderRadius: 40,
  },
  userInfo: {
    marginLeft: 20,
  },
  userName: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 4,
  },
  userEmail: {
    fontSize: 16,
    color: '#666',
  },
  menu: {
    backgroundColor: 'white',
    borderRadius: 12,
    marginHorizontal: 16,
    marginBottom: 20,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 1,
    },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  menuItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingVertical: 16,
    paddingHorizontal: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#F0F0F0',
  },
  menuItemLeft: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  menuItemName: {
    fontSize: 16,
    color: '#333',
    marginLeft: 16,
  },
  footer: {
    alignItems: 'center',
    paddingVertical: 20,
  },
  version: {
    fontSize: 14,
    color: '#999',
  },
});

export default ProfileScreen;

5. 创建工具页面

5.1 创建计算器页面

js
// src/screens/CalculatorScreen.js
import React, { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { Ionicons } from '@expo/vector-icons';

const CalculatorScreen = () => {
  const navigation = useNavigation();
  const [display, setDisplay] = useState('0');
  const [currentValue, setCurrentValue] = useState('');
  const [operator, setOperator] = useState(null);
  const [previousValue, setPreviousValue] = useState(null);

  const handleNumber = (number) => {
    if (display === '0' || display === 'Error') {
      setDisplay(number.toString());
      setCurrentValue(number.toString());
    } else {
      setDisplay(display + number.toString());
      setCurrentValue(currentValue + number.toString());
    }
  };

  const handleOperator = (op) => {
    if (currentValue) {
      if (previousValue) {
        calculate();
      }
      setOperator(op);
      setPreviousValue(currentValue);
      setCurrentValue('');
      setDisplay(display + ' ' + op + ' ');
    }
  };

  const calculate = () => {
    if (previousValue && currentValue && operator) {
      let result;
      const prev = parseFloat(previousValue);
      const current = parseFloat(currentValue);

      switch (operator) {
        case '+':
          result = prev + current;
          break;
        case '-':
          result = prev - current;
          break;
        case '×':
          result = prev * current;
          break;
        case '÷':
          if (current === 0) {
            setDisplay('Error');
            setCurrentValue('');
            setPreviousValue(null);
            setOperator(null);
            return;
          }
          result = prev / current;
          break;
        default:
          return;
      }

      setDisplay(result.toString());
      setCurrentValue(result.toString());
      setPreviousValue(null);
      setOperator(null);
    }
  };

  const clear = () => {
    setDisplay('0');
    setCurrentValue('');
    setPreviousValue(null);
    setOperator(null);
  };

  const backspace = () => {
    if (display.length > 1) {
      setDisplay(display.slice(0, -1));
      if (currentValue) {
        setCurrentValue(currentValue.slice(0, -1));
      }
    } else {
      setDisplay('0');
      setCurrentValue('');
    }
  };

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <TouchableOpacity
          style={styles.backButton}
          onPress={() => navigation.goBack()}
        >
          <Ionicons name="arrow-back" size={24} color="#333" />
        </TouchableOpacity>
        <Text style={styles.title}>计算器</Text>
        <View style={styles.placeholder} />
      </View>

      <View style={styles.displayContainer}>
        <Text style={styles.display}>{display}</Text>
      </View>

      <View style={styles.buttonsContainer}>
        <TouchableOpacity style={[styles.button, styles.clearButton]} onPress={clear}>
          <Text style={styles.clearButtonText}>C</Text>
        </TouchableOpacity>
        <TouchableOpacity style={[styles.button, styles.operatorButton]} onPress={backspace}>
          <Ionicons name="backspace-outline" size={24} color="#333" />
        </TouchableOpacity>
        <TouchableOpacity style={[styles.button, styles.operatorButton]} onPress={() => handleOperator('%')}>
          <Text style={styles.operatorButtonText}>%</Text>
        </TouchableOpacity>
        <TouchableOpacity style={[styles.button, styles.operatorButton]} onPress={() => handleOperator('÷')}>
          <Text style={styles.operatorButtonText}>÷</Text>
        </TouchableOpacity>

        <TouchableOpacity style={styles.button} onPress={() => handleNumber(7)}>
          <Text style={styles.buttonText}>7</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.button} onPress={() => handleNumber(8)}>
          <Text style={styles.buttonText}>8</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.button} onPress={() => handleNumber(9)}>
          <Text style={styles.buttonText}>9</Text>
        </TouchableOpacity>
        <TouchableOpacity style={[styles.button, styles.operatorButton]} onPress={() => handleOperator('×')}>
          <Text style={styles.operatorButtonText}>×</Text>
        </TouchableOpacity>

        <TouchableOpacity style={styles.button} onPress={() => handleNumber(4)}>
          <Text style={styles.buttonText}>4</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.button} onPress={() => handleNumber(5)}>
          <Text style={styles.buttonText}>5</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.button} onPress={() => handleNumber(6)}>
          <Text style={styles.buttonText}>6</Text>
        </TouchableOpacity>
        <TouchableOpacity style={[styles.button, styles.operatorButton]} onPress={() => handleOperator('-')}>
          <Text style={styles.operatorButtonText}>-</Text>
        </TouchableOpacity>

        <TouchableOpacity style={styles.button} onPress={() => handleNumber(1)}>
          <Text style={styles.buttonText}>1</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.button} onPress={() => handleNumber(2)}>
          <Text style={styles.buttonText}>2</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.button} onPress={() => handleNumber(3)}>
          <Text style={styles.buttonText}>3</Text>
        </TouchableOpacity>
        <TouchableOpacity style={[styles.button, styles.operatorButton]} onPress={() => handleOperator('+')}>
          <Text style={styles.operatorButtonText}>+</Text>
        </TouchableOpacity>

        <TouchableOpacity style={[styles.button, styles.zeroButton]} onPress={() => handleNumber(0)}>
          <Text style={styles.buttonText}>0</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.button} onPress={() => handleNumber('.')}>
          <Text style={styles.buttonText}>.</Text>
        </TouchableOpacity>
        <TouchableOpacity style={[styles.button, styles.equalsButton]} onPress={calculate}>
          <Text style={styles.equalsButtonText}>=</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F5F5',
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingHorizontal: 16,
    paddingVertical: 16,
    backgroundColor: 'white',
    borderBottomWidth: 1,
    borderBottomColor: '#E0E0E0',
  },
  backButton: {
    padding: 8,
  },
  title: {
    fontSize: 18,
    fontWeight: '600',
    color: '#333',
  },
  placeholder: {
    width: 40,
  },
  displayContainer: {
    backgroundColor: 'white',
    paddingHorizontal: 20,
    paddingVertical: 40,
    alignItems: 'flex-end',
  },
  display: {
    fontSize: 48,
    fontWeight: '300',
    color: '#333',
  },
  buttonsContainer: {
    flex: 1,
    flexDirection: 'row',
    flexWrap: 'wrap',
    padding: 10,
  },
  button: {
    width: '25%',
    height: '20%',
    justifyContent: 'center',
    alignItems: 'center',
  },
  zeroButton: {
    width: '50%',
  },
  buttonText: {
    fontSize: 24,
    color: '#333',
  },
  clearButton: {
    backgroundColor: '#F0F0F0',
  },
  clearButtonText: {
    fontSize: 24,
    color: '#007AFF',
  },
  operatorButton: {
    backgroundColor: '#F0F0F0',
  },
  operatorButtonText: {
    fontSize: 24,
    color: '#333',
  },
  equalsButton: {
    backgroundColor: '#007AFF',
  },
  equalsButtonText: {
    fontSize: 24,
    color: 'white',
  },
});

export default CalculatorScreen;

5.2 创建记事本页面

js
// src/screens/NotesScreen.js
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, FlatList, TouchableOpacity, TextInput, Alert } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { Ionicons } from '@expo/vector-icons';
import AsyncStorage from '@react-native-async-storage/async-storage';

const NotesScreen = () => {
  const navigation = useNavigation();
  const [notes, setNotes] = useState([]);
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');

  useEffect(() => {
    loadNotes();
  }, []);

  const loadNotes = async () => {
    try {
      const notesStr = await AsyncStorage.getItem('notes');
      if (notesStr) {
        setNotes(JSON.parse(notesStr));
      }
    } catch (error) {
      console.error('加载笔记失败:', error);
    }
  };

  const saveNotes = async (newNotes) => {
    try {
      await AsyncStorage.setItem('notes', JSON.stringify(newNotes));
    } catch (error) {
      console.error('保存笔记失败:', error);
    }
  };

  const handleAddNote = () => {
    if (title.trim()) {
      const newNote = {
        id: Date.now().toString(),
        title: title.trim(),
        content: content.trim(),
        createdAt: new Date().toISOString(),
      };
      const newNotes = [newNote, ...notes];
      setNotes(newNotes);
      saveNotes(newNotes);
      setTitle('');
      setContent('');
    } else {
      Alert.alert('提示', '请输入标题');
    }
  };

  const handleNotePress = (note) => {
    navigation.navigate('NoteDetail', { note });
  };

  const renderNote = ({ item }) => (
    <TouchableOpacity 
      style={styles.noteCard}
      onPress={() => handleNotePress(item)}
    >
      <Text style={styles.noteTitle} numberOfLines={1}>{item.title}</Text>
      <Text style={styles.noteContent} numberOfLines={2}>{item.content}</Text>
      <Text style={styles.noteDate}>{new Date(item.createdAt).toLocaleString()}</Text>
    </TouchableOpacity>
  );

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <TouchableOpacity
          style={styles.backButton}
          onPress={() => navigation.goBack()}
        >
          <Ionicons name="arrow-back" size={24} color="#333" />
        </TouchableOpacity>
        <Text style={styles.title}>记事本</Text>
        <View style={styles.placeholder} />
      </View>

      <View style={styles.form}>
        <TextInput
          style={styles.titleInput}
          placeholder="标题"
          placeholderTextColor="#999"
          value={title}
          onChangeText={setTitle}
        />
        <TextInput
          style={styles.contentInput}
          placeholder="内容"
          placeholderTextColor="#999"
          value={content}
          onChangeText={setContent}
          multiline
          numberOfLines={3}
        />
        <TouchableOpacity style={styles.addButton} onPress={handleAddNote}>
          <Text style={styles.addButtonText}>添加笔记</Text>
        </TouchableOpacity>
      </View>

      <FlatList
        data={notes}
        renderItem={renderNote}
        keyExtractor={item => item.id}
        contentContainerStyle={styles.notesList}
        showsVerticalScrollIndicator={false}
        ListEmptyComponent={
          <View style={styles.emptyContainer}>
            <Ionicons name="document-text-outline" size={64} color="#E0E0E0" />
            <Text style={styles.emptyText}>还没有笔记</Text>
            <Text style={styles.emptySubText}>添加你的第一条笔记</Text>
          </View>
        }
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F5F5',
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingHorizontal: 16,
    paddingVertical: 16,
    backgroundColor: 'white',
    borderBottomWidth: 1,
    borderBottomColor: '#E0E0E0',
  },
  backButton: {
    padding: 8,
  },
  title: {
    fontSize: 18,
    fontWeight: '600',
    color: '#333',
  },
  placeholder: {
    width: 40,
  },
  form: {
    backgroundColor: 'white',
    padding: 16,
    marginBottom: 16,
  },
  titleInput: {
    fontSize: 18,
    fontWeight: '600',
    color: '#333',
    marginBottom: 8,
    paddingVertical: 8,
    borderBottomWidth: 1,
    borderBottomColor: '#E0E0E0',
  },
  contentInput: {
    fontSize: 16,
    color: '#333',
    marginBottom: 16,
    paddingVertical: 8,
    minHeight: 80,
  },
  addButton: {
    backgroundColor: '#007AFF',
    paddingVertical: 12,
    borderRadius: 8,
    alignItems: 'center',
  },
  addButtonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: '600',
  },
  notesList: {
    padding: 16,
  },
  noteCard: {
    backgroundColor: 'white',
    padding: 16,
    borderRadius: 8,
    marginBottom: 12,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 1,
    },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  noteTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#333',
    marginBottom: 8,
  },
  noteContent: {
    fontSize: 14,
    color: '#666',
    marginBottom: 8,
  },
  noteDate: {
    fontSize: 12,
    color: '#999',
  },
  emptyContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    paddingVertical: 80,
  },
  emptyText: {
    fontSize: 16,
    color: '#999',
    marginTop: 16,
    marginBottom: 8,
  },
  emptySubText: {
    fontSize: 14,
    color: '#999',
  },
});

export default NotesScreen;

5.3 创建笔记详情页

js
// src/screens/NoteDetailScreen.js
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, TextInput, TouchableOpacity, Alert } from 'react-native';
import { useRoute, useNavigation } from '@react-navigation/native';
import { Ionicons } from '@expo/vector-icons';
import AsyncStorage from '@react-native-async-storage/async-storage';

const NoteDetailScreen = () => {
  const route = useRoute();
  const navigation = useNavigation();
  const { note } = route.params;
  const [title, setTitle] = useState(note.title);
  const [content, setContent] = useState(note.content);

  const handleSave = async () => {
    try {
      const notesStr = await AsyncStorage.getItem('notes');
      if (notesStr) {
        const notes = JSON.parse(notesStr);
        const updatedNotes = notes.map(item => 
          item.id === note.id 
            ? { ...item, title: title.trim(), content: content.trim() }
            : item
        );
        await AsyncStorage.setItem('notes', JSON.stringify(updatedNotes));
        Alert.alert('成功', '笔记已保存');
        navigation.goBack();
      }
    } catch (error) {
      console.error('保存笔记失败:', error);
      Alert.alert('错误', '保存笔记失败');
    }
  };

  const handleDelete = async () => {
    Alert.alert(
      '删除笔记',
      '确定要删除这篇笔记吗?',
      [
        { text: '取消', style: 'cancel' },
        {
          text: '删除',
          style: 'destructive',
          onPress: async () => {
            try {
              const notesStr = await AsyncStorage.getItem('notes');
              if (notesStr) {
                const notes = JSON.parse(notesStr);
                const updatedNotes = notes.filter(item => item.id !== note.id);
                await AsyncStorage.setItem('notes', JSON.stringify(updatedNotes));
                navigation.goBack();
              }
            } catch (error) {
              console.error('删除笔记失败:', error);
              Alert.alert('错误', '删除笔记失败');
            }
          },
        },
      ],
      { cancelable: true }
    );
  };

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <TouchableOpacity
          style={styles.backButton}
          onPress={() => navigation.goBack()}
        >
          <Ionicons name="arrow-back" size={24} color="#333" />
        </TouchableOpacity>
        <Text style={styles.title}>编辑笔记</Text>
        <TouchableOpacity style={styles.deleteButton} onPress={handleDelete}>
          <Ionicons name="trash-outline" size={24} color="#FF3B30" />
        </TouchableOpacity>
      </View>

      <View style={styles.content}>
        <TextInput
          style={styles.titleInput}
          value={title}
          onChangeText={setTitle}
          placeholder="标题"
          placeholderTextColor="#999"
        />
        <TextInput
          style={styles.contentInput}
          value={content}
          onChangeText={setContent}
          placeholder="内容"
          placeholderTextColor="#999"
          multiline
          textAlignVertical="top"
        />
      </View>

      <TouchableOpacity style={styles.saveButton} onPress={handleSave}>
        <Text style={styles.saveButtonText}>保存</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F5F5',
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingHorizontal: 16,
    paddingVertical: 16,
    backgroundColor: 'white',
    borderBottomWidth: 1,
    borderBottomColor: '#E0E0E0',
  },
  backButton: {
    padding: 8,
  },
  title: {
    fontSize: 18,
    fontWeight: '600',
    color: '#333',
  },
  deleteButton: {
    padding: 8,
  },
  content: {
    flex: 1,
    backgroundColor: 'white',
    margin: 16,
    padding: 16,
    borderRadius: 8,
  },
  titleInput: {
    fontSize: 20,
    fontWeight: '600',
    color: '#333',
    marginBottom: 16,
    paddingVertical: 8,
    borderBottomWidth: 1,
    borderBottomColor: '#E0E0E0',
  },
  contentInput: {
    flex: 1,
    fontSize: 16,
    color: '#333',
    paddingVertical: 8,
  },
  saveButton: {
    backgroundColor: '#007AFF',
    margin: 16,
    paddingVertical: 16,
    borderRadius: 8,
    alignItems: 'center',
  },
  saveButtonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: '600',
  },
});

export default NoteDetailScreen;

6. 更新 App.js

js
// App.js
import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { Provider } from 'react-redux';
import { store } from './src/redux/store';
import AppNavigator from './src/navigation/AppNavigator';

export default function App() {
  return (
    <Provider store={store}>
      <SafeAreaProvider>
        <AppNavigator />
      </SafeAreaProvider>
    </Provider>
  );
}

7. 总结

通过本章节的学习,我们掌握了如何使用 React Navigation 实现多页面路由,包括:

  1. 如何创建底部标签导航
  2. 如何创建主导航栈
  3. 如何创建首页、工具页面和个人主页
  4. 如何创建计算器和记事本等工具页面
  5. 如何实现页面间的导航

这些技能将帮助我们构建更加复杂、功能丰富的 React Native 应用。

8. 下一步

接下来,我们将学习如何实现表单提交功能,进一步增强应用的功能。

© 2026 编程马·菜鸟教程 版权所有