我们要建什么
留言板:React 里的表单把消息发到服务器,服务器保存,给每个后来的访客看。还记得第 2 级没地方发的表单吗?今天还债。
跟我看:留言板后端
在 my-server 文件夹,先再装一个包:
npm install cors
编辑 server.js:
const express = require("express");
const cors = require("cors");
const app = express();
app.use(cors()); // allow the frontend on a different port to ask questions
app.use(express.json()); // understand JSON in incoming requests
const messages = [
{ name: "Martin", text: "First message! Long live the guestbook. 📖" },
];
// GET – hand out messages
app.get("/api/messages", (request, response) => {
response.json(messages);
});
// POST – accept a new message
app.post("/api/messages", (request, response) => {
const { name, text } = request.body;
// Never trust input: check it on the server too!
if (!name || !text || name.length > 50 || text.length > 500) {
return response.status(400).json({ error: "Invalid message." });
}
messages.push({ name, text });
response.json({ saved: true });
});
app.listen(3900, () => {
console.log("Server running at http://localhost:3900");
});
新东西:
cors()——前端在 3901,服务器在 3900。浏览器通常会阻止这种”跨源”请求(CORS 安全规则);服务器上这样允许。app.post(...)——第一个 POST endpoint!表单数据在request.body里。- 服务器上检查——前端已经检查表单,但攻击者可以绕过表单直接发请求。所以铁律:后端永远检查,永远不信任前端。
status(400)意思是”坏请求”。
跟我看:留言板前端
在 React 项目创建 src/GuestBook.jsx:
import { useState, useEffect } from 'react';
function GuestBook() {
const [messages, setMessages] = useState([]);
const [name, setName] = useState("");
const [text, setText] = useState("");
// On first render: download the messages from the server
useEffect(() => {
fetch("http://localhost:3900/api/messages")
.then((response) => response.json())
.then((data) => setMessages(data));
}, []);
function submit(event) {
event.preventDefault();
fetch("http://localhost:3900/api/messages", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, text }),
})
.then((response) => response.json())
.then(() => {
setMessages([...messages, { name, text }]); // add it to the screen too
setName("");
setText("");
});
}
return (
<div>
<h2>Guestbook 📖</h2>
{messages.map((message, index) => (
<div className="card" key={index}>
<strong>{message.name}</strong>
<p>{message.text}</p>
</div>
))}
<form onSubmit={submit}>
<input
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="Your name"
/>
<textarea
value={text}
onChange={(event) => setText(event.target.value)}
placeholder="Write a message..."
/>
<button type="submit">Send</button>
</form>
</div>
);
}
export default GuestBook;
React 里最后三样新东西:
useEffect(() => {...}, [])——“组件第一次出现时运行一次。” 下载数据的理想位置。- 带
method: "POST"的fetch——我们在发数据;JSON.stringify打包成 JSON。 - 受控表单——字段从 state 读值(
value={name}),输入时写回(onChange)。表单和 state 始终同步。
轮到你了 💪
- 两个都跑起来:一个终端服务器(
node server.js),另一个 React(npm run dev -- --port 3901)。两个终端,两个程序——欢迎全栈! - 在应用里加
<GuestBook />试试:写留言,刷新页面——留言还在(只要服务器还在跑)。 - 按项目改留言板:🏫 给学校的留言 · 🙋 朋友留言板 · 🌍 访客”该去哪?“建议 · ⚽ 球迷口号。
- 试发空留言——前端会放行(没检查),服务器拒绝。然后前端也加检查,像第 2 级那样礼貌。
💡
服务器重启留言就消失——它们只存在内存数组里。真正应用存进 数据库,什么都能撑过重启。完成这一级后第一件事就是学——关键词:SQLite、PostgreSQL。
✅
检查一下: 表单的留言到服务器、被保存、刷新后还在。你刚刚建了 全栈应用——前端、后端、API。专业开发者每天就这样做。
这堂课你要记住
useEffect(..., [])首次渲染下载数据;带 POST 的fetch发送数据。- 安全铁律:服务器检查每个输入,即使前端也检查了。
- 全栈 = 两个跑着的程序(3901 + 3900)通过 API 对话。
© 2026 Ing. Martin Polak / AlgoRhino · 内容使用条款