Appearance
项目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 start2.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.js4. 安装必要的依赖
我们需要安装一些必要的依赖来实现待办清单的功能:
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-reanimated5. 配置项目
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
# 在网页中运行
# 按 w8. 项目结构说明
- components/:包含可复用的组件,如 TodoItem 和 TodoInput
- screens/:包含应用的页面组件,如 TodoListScreen
- utils/:包含工具函数,如 storage.js 用于本地存储
- styles/:包含样式文件,如 global.js 定义全局样式
这种结构有助于代码的组织和维护,使项目更加清晰和易于理解。
9. 下一步
在接下来的章节中,我们将:
- 实现待办事项的增删改查功能
- 完善本地数据存储
- 美化应用的用户界面
通过这个项目,你将掌握 React Native 开发的基本技能,为更复杂的应用开发打下基础。
