Skip to content

项目1:简易待办清单(Todo App)- 项目结构搭建

1. 项目概述

简易待办清单(Todo App)是一个常见的入门级项目,适合 React Native 初学者练习。本项目将帮助你掌握以下技能:

  • React Native 项目初始化和配置
  • 组件的创建和使用
  • 状态管理
  • 本地数据存储
  • 基本的用户界面设计

2. 项目初始化

2.1 使用 Expo 初始化项目

我们将使用 Expo 来初始化项目,这是 React Native 官方推荐的方式,可以快速搭建开发环境。

bash
# 初始化项目
npx create-expo-app todo-app

# 进入项目目录
cd todo-app

# 启动开发服务器
npx expo start

2.2 项目结构

初始化完成后,项目的基本结构如下:

todo-app/
├── App.js                 # 主应用入口
├── app.json               # Expo 配置文件
├── babel.config.js        # Babel 配置
├── package.json           # 项目依赖
└── assets/                # 静态资源目录
    ├── fonts/             # 字体文件
    └── images/            # 图片文件

3. 项目结构优化

为了更好地组织代码,我们将对项目结构进行优化,创建以下目录和文件:

todo-app/
├── App.js                 # 主应用入口
├── app.json               # Expo 配置文件
├── babel.config.js        # Babel 配置
├── package.json           # 项目依赖
├── assets/                # 静态资源目录
├── components/            # 可复用组件
│   ├── TodoItem.js        # 待办项组件
│   └── TodoInput.js       # 待办输入组件
├── screens/               # 页面组件
│   └── TodoListScreen.js  # 待办列表页面
├── utils/                 # 工具函数
│   └── storage.js         # 本地存储工具
└── styles/                # 样式文件
    └── global.js          # 全局样式

3.1 创建目录结构

执行以下命令创建所需的目录:

bash
# 创建目录
mkdir -p components screens utils styles

# 创建文件
touch components/TodoItem.js components/TodoInput.js
touch screens/TodoListScreen.js
touch utils/storage.js
touch styles/global.js

4. 安装必要的依赖

我们需要安装一些必要的依赖来实现待办清单的功能:

bash
# 安装 @react-native-async-storage/async-storage 用于本地存储
npx expo install @react-native-async-storage/async-storage

# 安装 react-native-gesture-handler 用于手势处理
npx expo install react-native-gesture-handler

# 安装 react-native-reanimated 用于动画效果
npx expo install react-native-reanimated

5. 配置项目

5.1 配置 babel.config.js

确保 babel.config.js 配置正确,以支持 react-native-reanimated:

javascript
// babel.config.js
module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: [
      'react-native-reanimated/plugin',
    ],
  };
};

5.2 配置 app.json

更新 app.json 文件,配置应用的基本信息:

json
{
  "expo": {
    "name": "Todo App",
    "slug": "todo-app",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "light",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": true
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#ffffff"
      }
    },
    "web": {
      "favicon": "./assets/favicon.png"
    }
  }
}

6. 创建基础文件

6.1 创建全局样式文件

javascript
// styles/global.js
import { StyleSheet } from 'react-native';

export const globalStyles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    borderRadius: 5,
    backgroundColor: '#fff',
    marginBottom: 10,
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 10,
    borderRadius: 5,
    alignItems: 'center',
    marginBottom: 10,
  },
  buttonText: {
    color: '#fff',
    fontWeight: 'bold',
  },
  list: {
    marginTop: 10,
  },
  emptyText: {
    textAlign: 'center',
    color: '#999',
    marginTop: 20,
  },
});

6.2 创建本地存储工具

javascript
// utils/storage.js
import AsyncStorage from '@react-native-async-storage/async-storage';

const STORAGE_KEY = '@TodoApp:todos';

export const storage = {
  // 存储待办事项
  saveTodos: async (todos) => {
    try {
      const jsonValue = JSON.stringify(todos);
      await AsyncStorage.setItem(STORAGE_KEY, jsonValue);
      return true;
    } catch (error) {
      console.error('保存待办事项失败:', error);
      return false;
    }
  },

  // 获取待办事项
  getTodos: async () => {
    try {
      const jsonValue = await AsyncStorage.getItem(STORAGE_KEY);
      return jsonValue != null ? JSON.parse(jsonValue) : [];
    } catch (error) {
      console.error('获取待办事项失败:', error);
      return [];
    }
  },

  // 清除待办事项
  clearTodos: async () => {
    try {
      await AsyncStorage.removeItem(STORAGE_KEY);
      return true;
    } catch (error) {
      console.error('清除待办事项失败:', error);
      return false;
    }
  },
};

6.3 创建待办项组件

javascript
// components/TodoItem.js
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';

const TodoItem = ({ todo, onPress, onDelete }) => {
  return (
    <TouchableOpacity style={styles.container} onPress={() => onPress(todo.id)}>
      <View style={[styles.checkbox, todo.completed && styles.checkboxCompleted]}>
        {todo.completed && <Text style={styles.checkmark}>✓</Text>}
      </View>
      <Text style={[styles.text, todo.completed && styles.textCompleted]}>
        {todo.text}
      </Text>
      <TouchableOpacity onPress={() => onDelete(todo.id)} style={styles.deleteButton}>
        <Text style={styles.deleteText}>×</Text>
      </TouchableOpacity>
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#fff',
    padding: 15,
    borderRadius: 5,
    marginBottom: 10,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.1,
    shadowRadius: 3.84,
    elevation: 5,
  },
  checkbox: {
    width: 20,
    height: 20,
    borderWidth: 2,
    borderColor: '#007AFF',
    borderRadius: 4,
    marginRight: 10,
    justifyContent: 'center',
    alignItems: 'center',
  },
  checkboxCompleted: {
    backgroundColor: '#007AFF',
  },
  checkmark: {
    color: '#fff',
    fontWeight: 'bold',
  },
  text: {
    flex: 1,
    fontSize: 16,
  },
  textCompleted: {
    textDecorationLine: 'line-through',
    color: '#999',
  },
  deleteButton: {
    padding: 5,
  },
  deleteText: {
    fontSize: 24,
    color: '#ff3b30',
    fontWeight: 'bold',
  },
});

export default TodoItem;

6.4 创建待办输入组件

javascript
// components/TodoInput.js
import React, { useState } from 'react';
import { View, TextInput, TouchableOpacity, Text, StyleSheet } from 'react-native';

const TodoInput = ({ onAddTodo }) => {
  const [text, setText] = useState('');

  const handleAdd = () => {
    if (text.trim()) {
      onAddTodo(text.trim());
      setText('');
    }
  };

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        placeholder="添加待办事项..."
        value={text}
        onChangeText={setText}
        onSubmitEditing={handleAdd}
        returnKeyType="done"
      />
      <TouchableOpacity style={styles.button} onPress={handleAdd}>
        <Text style={styles.buttonText}>添加</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    marginBottom: 20,
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    borderRadius: 5,
    backgroundColor: '#fff',
    marginRight: 10,
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 10,
    borderRadius: 5,
    justifyContent: 'center',
    alignItems: 'center',
  },
  buttonText: {
    color: '#fff',
    fontWeight: 'bold',
  },
});

export default TodoInput;

6.5 创建待办列表页面

javascript
// screens/TodoListScreen.js
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
import TodoItem from '../components/TodoItem';
import TodoInput from '../components/TodoInput';
import { storage } from '../utils/storage';
import { globalStyles } from '../styles/global';

const TodoListScreen = () => {
  const [todos, setTodos] = useState([]);

  // 加载待办事项
  useEffect(() => {
    loadTodos();
  }, []);

  const loadTodos = async () => {
    const savedTodos = await storage.getTodos();
    setTodos(savedTodos);
  };

  const saveTodos = async (newTodos) => {
    setTodos(newTodos);
    await storage.saveTodos(newTodos);
  };

  // 添加待办事项
  const handleAddTodo = (text) => {
    const newTodo = {
      id: Date.now().toString(),
      text,
      completed: false,
    };
    saveTodos([...todos, newTodo]);
  };

  // 切换待办事项状态
  const handleToggleTodo = (id) => {
    const updatedTodos = todos.map(todo => 
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    );
    saveTodos(updatedTodos);
  };

  // 删除待办事项
  const handleDeleteTodo = (id) => {
    const updatedTodos = todos.filter(todo => todo.id !== id);
    saveTodos(updatedTodos);
  };

  // 渲染待办项
  const renderItem = ({ item }) => (
    <TodoItem
      todo={item}
      onPress={handleToggleTodo}
      onDelete={handleDeleteTodo}
    />
  );

  return (
    <View style={globalStyles.container}>
      <Text style={globalStyles.title}>待办清单</Text>
      
      <TodoInput onAddTodo={handleAddTodo} />
      
      <FlatList
        data={todos}
        renderItem={renderItem}
        keyExtractor={item => item.id}
        style={globalStyles.list}
        ListEmptyComponent={
          <Text style={globalStyles.emptyText}>暂无待办事项</Text>
        }
      />
    </View>
  );
};

export default TodoListScreen;

6.6 更新 App.js

最后,更新 App.js 文件,将其作为应用的入口点:

javascript
// App.js
import React from 'react';
import { SafeAreaView } from 'react-native-safe-area-context';
import TodoListScreen from './screens/TodoListScreen';

const App = () => {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <TodoListScreen />
    </SafeAreaView>
  );
};

export default App;

7. 运行项目

现在,我们已经完成了待办清单应用的基本结构搭建。执行以下命令运行项目:

bash
# 启动开发服务器
npx expo start

# 在 iOS 模拟器中运行
# 按 i

# 在 Android 模拟器中运行
# 按 a

# 在网页中运行
# 按 w

8. 项目结构说明

  • components/:包含可复用的组件,如 TodoItem 和 TodoInput
  • screens/:包含应用的页面组件,如 TodoListScreen
  • utils/:包含工具函数,如 storage.js 用于本地存储
  • styles/:包含样式文件,如 global.js 定义全局样式

这种结构有助于代码的组织和维护,使项目更加清晰和易于理解。

9. 下一步

在接下来的章节中,我们将:

  1. 实现待办事项的增删改查功能
  2. 完善本地数据存储
  3. 美化应用的用户界面

通过这个项目,你将掌握 React Native 开发的基本技能,为更复杂的应用开发打下基础。

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