React教程:从项目搭建到任务管理应用
React 是一个用于构建用户界面的 JavaScript 库,适合开发后台系统、管理面板、内容网站和交互式Web应用。本教程将从环境准备开始,依次介绍 JSX、组件、Props、状态、Hooks、表单、路由和 API 请求,最后完成一个可以部署上线的任务管理应用。
如果需要将React项目部署到公网,可以使用具备独立资源和管理员权限的 Hostinger VPS,在服务器上安装Node.js、构建项目并通过Nginx提供访问。
Hostinger官网:点击直达
一、准备React开发环境
学习本教程前,建议了解基础的HTML、CSS和JavaScript,尤其是变量、函数、数组、对象、解构赋值、模块以及 map() 和 filter() 等数组方法。
React项目需要使用Node.js和npm。Node.js用于运行Vite等开发工具,npm则用于安装和管理项目依赖。
安装Node.js后,在终端检查版本:
node --version
npm --version
如果两个命令都返回版本号,说明环境已经准备完成。
二、使用Vite创建React项目
本教程使用Vite创建项目。Vite启动速度快,配置简单,适合新建React应用。
执行以下命令:
npm create vite@latest react-task-tracker -- --template react
cd react-task-tracker
npm install
npm run dev
其中:
npm create vite@latest用于创建Vite项目。--template react表示使用React模板。npm install安装项目依赖。npm run dev启动本地开发服务器。
启动成功后,终端通常会显示类似以下地址:
http://localhost:5173
在浏览器中打开该地址,即可查看React初始页面。
三、了解React项目结构
创建项目后,主要文件通常位于 src 目录中:
react-task-tracker/
├── package.json
├── index.html
└── src/
├── main.jsx
├── App.jsx
├── App.css
└── index.css
各文件的作用如下:
package.json:记录项目依赖和运行命令。index.html:提供React应用挂载的HTML页面。src/main.jsx:启动React应用并加载根组件。src/App.jsx:保存主要应用组件。src/App.css:保存应用相关样式。src/index.css:保存全局样式。
后续可以在 src 目录中创建:
src/components
src/pages
components 用于保存可复用组件,pages 用于保存不同页面组件。
文件名称的大小写需要保持一致。例如,App.jsx 和 app.jsx 在部分系统中会被视为两个不同的文件。
四、理解JSX
JSX是React使用的一种JavaScript语法扩展,可以在JavaScript代码中编写类似HTML的结构。
打开 src/App.jsx,替换为:
const taskName = '学习 JSX';
function App() {
return (
<main className="task-list">
<h1>任务管理器</h1>
<p>{taskName}</p>
</main>
);
}
export default App;
JSX与HTML有一些区别:
- 多个元素需要放在同一个父元素或片段中。
- 所有标签都必须闭合。
- 使用
className代替HTML中的class。 - 事件属性通常使用驼峰形式,例如
onClick。 - JavaScript表达式需要放在
{}中。
例如:
<p>{taskName}</p>
这里的 {taskName} 会显示变量中的内容。
保存文件后,浏览器页面会自动更新。可以修改任务名称,再增加一个状态文本:
<p>未开始</p>
五、创建React组件
React组件通常是返回JSX的JavaScript函数。将页面拆分成多个组件后,代码更容易维护。
先创建目录:
mkdir src/components
在 src/components 中创建 Header.jsx:
function Header() {
return (
<header className="header">
<h1>任务管理器</h1>
</header>
);
}
export default Header;
然后修改 src/App.jsx:
import Header from './components/Header.jsx';
function App() {
return (
<main className="app">
<Header />
</main>
);
}
export default App;
export default Header 允许其他文件导入该组件。<Header /> 则表示在页面中使用Header组件。
六、使用Props传递数据
Props用于父组件向子组件传递数据。
创建 src/components/TaskItem.jsx:
function TaskItem({ title, done }) {
return (
<li className="task-item">
{done ? '✓ ' : '○ '}
{title}
</li>
);
}
export default TaskItem;
这里的 title 和 done 就是组件接收的Props。
修改 src/App.jsx:
import Header from './components/Header.jsx';
import TaskItem from './components/TaskItem.jsx';
function App() {
return (
<main className="app">
<Header />
<ul>
<TaskItem title="学习 JSX" done={true} />
<TaskItem title="创建React组件" done={false} />
</ul>
</main>
);
}
export default App;
两个任务接收了不同的 done 值,因此页面会显示不同的完成状态。
Props是只读数据,子组件可以使用父组件传入的值,但不应该直接修改它。
七、渲染任务列表
接下来创建 TaskList.jsx,用于循环显示多个任务。
import TaskItem from './TaskItem.jsx';
function TaskList({ tasks }) {
if (tasks.length === 0) {
return <p>当前没有任务。</p>;
}
return (
<ul className="task-list">
{tasks.map((task) => (
<TaskItem
key={task.id}
title={task.title}
done={task.done}
/>
))}
</ul>
);
}
export default TaskList;
map() 会为数组中的每个任务生成一个 TaskItem。
key 用于帮助React识别列表中的每一项。每个任务都应使用稳定且唯一的值作为 key。
修改 src/App.jsx:
import Header from './components/Header.jsx';
import TaskList from './components/TaskList.jsx';
const tasks = [
{ id: 1, title: '学习 JSX', done: true },
{ id: 2, title: '创建React组件', done: false },
];
function App() {
return (
<>
<Header />
<main className="app">
<TaskList tasks={tasks} />
</main>
</>
);
}
export default App;
现在,页面结构已经拆分为Header、TaskList和TaskItem三个组件。
八、使用State实现交互
如果任务状态需要在页面操作后发生变化,就需要使用State。
修改 src/App.jsx:
import { useState } from 'react';
import Header from './components/Header.jsx';
import TaskList from './components/TaskList.jsx';
function App() {
const [tasks, setTasks] = useState([
{ id: 1, title: '学习 JSX', done: false },
{ id: 2, title: '创建React组件', done: false },
]);
function toggleTask(taskId) {
setTasks((currentTasks) =>
currentTasks.map((task) =>
task.id === taskId
? { ...task, done: !task.done }
: task
)
);
}
return (
<>
<Header />
<main className="app">
<TaskList
tasks={tasks}
onToggleTask={toggleTask}
/>
</main>
</>
);
}
export default App;
然后修改 TaskList.jsx:
import TaskItem from './TaskItem.jsx';
function TaskList({ tasks, onToggleTask }) {
if (tasks.length === 0) {
return <p>当前没有任务。</p>;
}
return (
<ul className="task-list">
{tasks.map((task) => (
<TaskItem
key={task.id}
title={task.title}
done={task.done}
onToggle={() => onToggleTask(task.id)}
/>
))}
</ul>
);
}
export default TaskList;
最后修改 TaskItem.jsx:
function TaskItem({ title, done, onToggle }) {
return (
<li className="task-item">
<button type="button" onClick={onToggle}>
{done ? '✓' : '○'}
</button>
<span>{title}</span>
</li>
);
}
export default TaskItem;
点击任务前面的按钮后,父组件中的State会更新,React随后重新渲染任务列表。
九、添加任务表单
创建 src/components/TaskForm.jsx:
import { useState } from 'react';
function TaskForm({ onAddTask }) {
const [title, setTitle] = useState('');
function handleSubmit(event) {
event.preventDefault();
const trimmedTitle = title.trim();
if (!trimmedTitle) {
return;
}
onAddTask(trimmedTitle);
setTitle('');
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={title}
onChange={(event) => setTitle(event.target.value)}
placeholder="输入任务名称"
/>
<button type="submit">添加任务</button>
</form>
);
}
export default TaskForm;
在 App.jsx 中增加添加任务逻辑:
import { useState } from 'react';
import Header from './components/Header.jsx';
import TaskForm from './components/TaskForm.jsx';
import TaskList from './components/TaskList.jsx';
function App() {
const [tasks, setTasks] = useState([
{ id: 1, title: '学习 JSX', done: false },
{ id: 2, title: '创建React组件', done: false },
]);
function addTask(title) {
setTasks((currentTasks) => [
...currentTasks,
{
id: Date.now(),
title,
done: false,
},
]);
}
function toggleTask(taskId) {
setTasks((currentTasks) =>
currentTasks.map((task) =>
task.id === taskId
? { ...task, done: !task.done }
: task
)
);
}
return (
<>
<Header />
<main className="app">
<TaskForm onAddTask={addTask} />
<TaskList
tasks={tasks}
onToggleTask={toggleTask}
/>
</main>
</>
);
}
export default App;
这个表单属于受控组件,输入框的值由React State管理。提交时,程序会先阻止页面刷新,再检查任务名称是否为空,最后将新任务添加到列表中。
十、使用useEffect获取API数据
useEffect适合处理组件渲染之外的操作,例如获取接口数据、监听事件和更新浏览器标题。
import { useEffect, useState } from 'react';
function RemoteTasks() {
const [tasks, setTasks] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
async function loadTasks() {
try {
const response = await fetch(
'https://jsonplaceholder.typicode.com/todos?_limit=5'
);
if (!response.ok) {
throw new Error('请求失败');
}
const data = await response.json();
setTasks(
data.map((item) => ({
id: item.id,
title: item.title,
done: item.completed,
}))
);
} catch (requestError) {
setError(requestError.message);
} finally {
setLoading(false);
}
}
loadTasks();
}, []);
if (loading) {
return <p>正在加载任务...</p>;
}
if (error) {
return <p>任务加载失败:{error}</p>;
}
return (
<ul>
{tasks.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
);
}
export default RemoteTasks;
空数组 [] 表示组件首次加载时执行一次请求。
实际项目中,还应处理请求取消、接口鉴权、分页和数据缓存等问题。
十一、使用React Router创建多个页面
安装React Router:
npm install react-router-dom
创建页面目录:
mkdir src/pages
创建 src/pages/HomePage.jsx:
function HomePage() {
return <h2>任务首页</h2>;
}
export default HomePage;
创建 src/pages/AboutPage.jsx:
function AboutPage() {
return <h2>关于任务管理器</h2>;
}
export default AboutPage;
然后修改 src/main.jsx:
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import {
BrowserRouter,
} from 'react-router-dom';
import './index.css';
import App from './App.jsx';
createRoot(document.getElementById('root')).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
);
在 App.jsx 中配置路由:
import {
Link,
Route,
Routes,
} from 'react-router-dom';
import HomePage from './pages/HomePage.jsx';
import AboutPage from './pages/AboutPage.jsx';
function App() {
return (
<>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
</nav>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
</Routes>
</>
);
}
export default App;
开发服务器运行时,可以访问:
http://localhost:5173/
http://localhost:5173/about
部署到Nginx时,需要将未知路径回退到 index.html,否则直接刷新子页面可能出现404。
十二、构建React生产版本
开发完成后,在项目目录执行:
npm run build
Vite会将生产文件输出到:
dist/
可以在本地预览生产版本:
npm run preview
部署前建议检查:
- 页面刷新是否正常。
- 路由跳转是否正常。
- API地址是否使用生产环境配置。
- 图片和静态文件路径是否正确。
- 移动端页面是否能够正常显示。
- 生产环境是否启用HTTPS。
十三、使用Hostinger VPS部署React项目
如果需要把React任务管理器发布到公网,可以使用 Hostinger VPS 部署Node.js环境、Nginx和项目文件。
基本流程如下:
- 准备一台Linux VPS。
- 通过SSH登录服务器。
- 安装Node.js和npm。
- 上传React项目代码。
- 执行
npm install。 - 执行
npm run build。 - 将
dist目录配置为Nginx站点根目录。 - 配置HTTPS和域名解析。
- 设置React Router的历史路径回退规则。
- 通过浏览器访问正式域名。
如果应用需要运行Node.js后端,还需要使用进程管理工具保持服务运行,并通过Nginx反向代理后端接口。
Hostinger VPS适合部署React静态网站、Node.js应用、API服务和开发测试环境。选择配置时,应根据项目访问量、构建任务和后端服务数量预留CPU、内存及磁盘空间。
十四、常见问题
React项目一定要使用Vite吗?
不一定,但Vite提供了较简洁的开发环境和较快的启动速度,适合新项目使用。
JSX是不是HTML?
JSX看起来类似HTML,但它最终会转换为JavaScript,并遵循React自己的语法规则。
Props和State有什么区别?
Props由父组件传给子组件,主要用于传递数据;State由组件自己管理,可以随着用户操作发生变化。
为什么列表需要key?
key帮助React识别列表中的每一项。使用稳定且唯一的值,可以减少列表更新时的渲染问题。
React项目可以部署到VPS吗?
可以。完成 npm run build 后,React项目通常会生成静态文件,可以通过Nginx部署到Hostinger VPS等Linux服务器上。
React Router部署后刷新页面出现404怎么办?
需要在Web服务器中配置路径回退,让未匹配的前端路径返回 index.html,再由React Router处理页面显示。