之前初步体验了 React 的魅力, 又看文档理解了一下 useState
和 useEffect
, 目前初步理解的概念是:
useState
用来声明在组件中使用并且需要修改的变量
useEffect
用来对 useState
声明的变量进行初始化赋值
可能理解的不太准确, 不过大概差不多是这么个意思. 但是再往后看路由什么的就头大了, 还得手动添加各种其他的 package 进行实现, 显然不符合 OOBE 的体验. 逛了一圈发现基于 React 做的另外一个更 “高级” 的框架 Next.js, 来个快速入门做一下笔记.
计划使用 FastAPI 进行后端的设计, Next.js 做前端. Python, nodejs, VScode 什么的安装环境就不罗嗦了, 直接开干!
# 创建项目文件夹
New-Item -ItemType Directory .\next-study; New-Item -ItemType Directory .\next-study\backend; cd next-study\backend
# 初始化后端 FastAPI 文件夹
virtualenv venv
code .
在 VScode 中打开 Terminal (会自动激活当前的 Virtualenv), 安装依赖:
pip install fastapi uvicorn
创建 main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
app = FastAPI()
# 配置 CORS 跨域允许访问
app.add_middleware(
CORSMiddleware,
allow_origins=['*'],
allow_methods=['*']
)
# 定义要处理的请求中的 JSON 数据格式
class Message(BaseModel):
message: str
@app.get('/')
def get_data():
# GET 数据的路由
return {
'message': 'This is default message from FastAPI'
}
@app.put('/')
def update_data(update_message: Message):
# PUT 更新数据的路由
# 直接在函数定义里面加入参数就能接收和处理请求发来的 JSON 数据了, FastAPI 果然是干这个的, 好用!
return update_message
回到 Terminal 启动:
uvicorn main:app --reload
用 Apipost 测一下, 真 tm 好使~
OK, 后端就先这样
最小化后端的 VScode, 回到 PowerShell 中使用脚手架创建 Next.js 项目
# 返回 next-study 路径
cd ...
# 脚手架走起来
npx create-next-app frontend
# 全用默认回复, 完成项目创建
√ Would you like to use TypeScript? ... No / Yes
√ Would you like to use ESLint? ... No / Yes
√ Would you like to use Tailwind CSS? ... No / Yes
√ Would you like to use `src/` directory? ... No / Yes
√ Would you like to use App Router? (recommended) ... No / Yes
√ Would you like to customize the default import alias (@/*)? ... No / Yes
# 开干
cd frontend
code .
先不整花里胡哨的, 直接清空 app/page.tsx
内容, 重新写一个简单的页面:
"use client"; // 声明这是一个 Client Side Render 的 Component
import { useEffect, useState } from "react";
// 使用接口定义页面中用到的数据格式
interface Data {
message: string;
}
const page = () => {
// 声明页面中要维护的数据及默认值
const [data, setData] = useState<Data>({
message: "Default message of Next useState()",
});
// Component 加载时自动从后端获取数据
const apiUrl = "http://localhost:8000";
useEffect(() => {
fetch(apiUrl)
.then((res) => res.json())
.then((json) => {
setData(json);
});
}, []); // 务必注意加上后面的第二个空数组参数, 否则页面会一直向后端发送请求获取数据
return (
<div>
<p>{data.message}</p>
</div>
);
};
export default page;
npm run dev
跑一下:
如果把 useEffect()
中的 setData()
注释掉, 则会显示 useState
定义的默认内容
接下来添加一个 Button 用来和后端交互更新页面内容
"use client"; // 声明这是一个 Client Side Render 的 Component. Next.js 默认使用 Server Side Render
import { useEffect, useState } from "react";
// 使用接口定义页面中用到的数据格式
interface Data {
message: string;
}
const page = () => {
// 声明页面中要维护的数据及默认值
const [data, setData] = useState<Data>({
message: "Default message of Next useState()",
});
// Component 加载时自动从后端获取数据
const apiUrl = "http://localhost:8000";
useEffect(() => {
fetch(apiUrl)
.then((res) => res.json())
.then((json) => {
setData(json);
});
}, []); // 务必注意加上后面的第二个空数组参数, 否则页面会一直向后端发送请求获取数据
// 按钮点击事件
const handleClick = async () => {
fetch(apiUrl, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
message: "Updated message by Next.js button.",
}),
})
.then((ret) => ret.json())
.then((json) => setData(json)); // 调用 useState 返回的第二个函数进行数据更新
};
return (
<div>
<p>{data.message}</p>
<button onClick={handleClick}>Click me</button>
</div>
);
};
export default page;
还记得开头用 Next.js 脚手架创建项目的时候问题里面包含了 Tailwind CSS
吧, 快速瞅一下文档, 美化一下页面样式
"use client"; // 声明这是一个 Client Side Render 的 Component. Next.js 默认使用 Server Side Render
import { useEffect, useState } from "react";
// 使用接口定义页面中用到的数据格式
interface Data {
message: string;
}
const page = () => {
// 声明页面中要维护的数据及默认值
const [data, setData] = useState<Data>({
message: "Default message of Next useState()",
});
// Component 加载时自动从后端获取数据
const apiUrl = "http://localhost:8000";
useEffect(() => {
fetch(apiUrl)
.then((res) => res.json())
.then((json) => {
setData(json);
});
}, []); // 务必注意加上后面的第二个空数组参数, 否则页面会一直向后端发送请求获取数据
// 按钮点击事件
const handleClick = async () => {
fetch(apiUrl, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
message: "Updated message by Next.js button.",
}),
})
.then((ret) => ret.json())
.then((json) => setData(json));
};
return (
<div>
{/* Hero section */}
<div className="bg-blue-900 text-white py-16">
<div className="container mx-auto text-center">
<h1 className="text-4xl font-bold">{data.message}</h1>
<p className="mt-4 text-lg">
Click the button will change content from FastAPI.
</p>
<button
onClick={handleClick}
className="mt-8 bg-yellow-500 hover:bg-yellow-600 text-blue-900 font-semibold py-2 px-6 rounded-full inline-block transition duration-300 ease-in-out"
>
Click me
</button>
</div>
</div>
</div>
);
};
export default page;