感谢 Randy Lu 的 Remix 入门实战教程,非常 Nice!
围绕一个博客系统,对 Remix 快速入门。最终实现的功能包括:
- 查看文章列表
- 发布、编辑、删除文章
- 用户登录、注册、编辑文章鉴权
新建 Remix 项目
初始化 Remix 项目
# pnpm
pnpm create remix@latest remix-blog
# npm
npm create-remix@latest remix-blog
创建目录后,进入 remix-blog, 安装依赖:
pnpm i
# 或者
npm i
Remix 项目结构一览
- app
- routes
- _index.tsx
- entry.client.tsx
- entry.server.tsx
- root.tsx
- public
在 Remix 项目中,所有的业务代码主要在 app/ 目录中,public/ 用于存放外部直接可以访问的静态文件,比如 favicon.ico。
- app/routes 所有的页面、路由都在这个目录
- entry.client.tsx 和 entry.server.tsx 在日常开发一般不会动它们,是一些 Remix 的固定逻辑。
- root.tsx 整个 web 的入口,每个页面都会继承这个入口作为 layout。
Remix 的路由设计
首先,启动项目
npm run dev
首页对应的就是 app/routes/_index.tsx 所写的 React 页面。
为什么要以下划线(_)开头呢?因为在 Remix 中,页面路由会以文件名自动生成。所以,如果你写了一个 app/routes/index.tsx,它会指向 localhost:3000/index 而不是 localhost:3000/
在一个复杂的 web app 里,会有很多嵌套的路由,比如:
- /posts/:postId
- /tags
- /tags/:tagId
那么在 Remix 中,可以写成:
- app
- routes
- _index.tsx
- posts.$postId.tsx
- tags._index.tsx
- tags.$tagId.tsx
- root.tsx
url | 文件名 | 继承 layout |
---|---|---|
/ | app/routes/_index.tsx | app/root.tsx |
/posts/xxx | app/routes/posts.$postId.tsx | app/root.tsx |
/tags | app/routes/tags._index.tsx | app/root.tsx |
/tags/xxx | app/routes/tags.$tagId.tsx | app/root.tsx |
如果希望 /tags 和 /tags/:tagId 使用同一个 layout,就要再添加一个 app/routes/tags.tsx 文件作为父级 layout
这时,继承的对应关系变为:
url | 文件名 | 继承 layout |
---|---|---|
/ | app/routes/_index.tsx | app/root.tsx |
/posts/xxx | app/routes/posts.$postId.tsx | app/root.tsx |
/tags | app/routes/tags._index.tsx | app/routes/tags.tsx |
/tags/xxx | app/routes/tags.$tagId.tsx | app/routes/tags.tsx |
而在作为 layout 的组件中,我们可以直接用 Remix 的
import { Outlet } from "@remix-run/react";
export default function TagLayout() {
return (
<div>
<h1>Tag Layout</h1>
<div>
<Outlet />
</div>
</div>
)
}
So,为什么需要写成 _index 的形式,因为不带 _index 的页面,都会作为 layout。
这样的情况下,tags.tsx 是作为 layout 存在,而 tags._index.tsx 才是访问 /tags 时的页面。
加入 Tailwind CSS
在写业务代码之前,我们还需要把样式引入进来,这个入门项目使用 TailwindCSS + NextUI 的方案。
Remix 脚手架目前已经自带 Tailwind CSS,如果 package.json 中已经有 tailwindcss postcss autoprefixer 这些依赖了,就可以跳过这个部分。
在项目根目录中,安装Tailwind CSS 必要的依赖:
pnpm i tailwindcss postcss autoprefixer
然后用 tailwindcss 的 CLI 初始化 tailwindcss:
pnpm exec tailwindcss init -p
#npm
npm tailwindcss init -p
然后在创建一个 app/main.css 的 css 文件, 引入 Tailwind CSS 的必要样式:
@tailwind base;
@tailwind components;
@tailwind utilities;
下一步,在 app/root.tsx 中引入 main.css:
import "./main.css";
到这里,Tailwind CSS 就已经可以在你的 Remix app 中使用了。
加入 NextUI
初始化 tailwindcss 后,安装 NextUI 必要的依赖
pnpm i @nextui-org/react framer-motion
然后在 tailwind.config.js 中引入 NextUI:
import { nextui } from "@nextui-org/react";
export default {
content: [
"./app/**/{**,.client,.server}/**/*.{js,jsx,ts,tsx}",
"./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}"
],
plugins: [nextui()],
}
然后在 root.tsx 中加入
import { NextUIProvider } from "@nextui-org/react";
export default function App() {
return (
<NextUIProvider>
<Outlet />
</NextUIProvider>
);
}
无论你用的是什么 UI 库,一般来说你都应该在这个地方套用 UI 库提供的 Provider.
另外,如果你用的是 pnpm,则需要以下步骤:
在根目录创建 .npmrc 文件:
public-hoist-pattern[]=*@nextui-org/*
重新安装依赖:
pnpm i
到这里,NextUI 也安装完毕了。
数据库设计
对于这个博客系统来说,数据库模型不复杂。首先整理出要做的功能:
- post 列表
- 创建、修改、删除 Post
根据这个需求,只需要一张数据表就可以了。这里会使用 Prisma 作为 ORM 工具对数据库进行读写。Prisma 的 Schema 易懂易写,对 TypeScript 的支持也非常好,在写代码时有充分的类型提示。
加入 Prisma
安装 Prisma:
pnpm i prisma @prisma/client
在项目中初始化 prisma:
pnpm exec prisma init
# 或者 npm
npm prisma init
运行命令后,会自动创建一个 prisma/ 目录,里面有 prisma/schema.prisma 文件,就是我们需要编写的数据库模型文件。
把数据库模型写成 schema
打开 prisma/schema.prisma 文件,你会看到:
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
这是初始化 prisma 时的默认模板,为了方便,我们不使用 postgresql,而是使用本地的数据库 sqlite,所以我们要把 provider 改成 sqlite 以及定义数据库文件的地址:
datasource db {
provider = "sqlite"
url = "file:./data.db"
}
接下来,开始编写数据库的模型。现在只需要一张 Post 表来存储博客文章,每个 post 有以下属性:
id
文章的唯一 IDtitle
文章标题content
文章内容created_at
文章创建时间
那么在 prisma 的 schema 中,可以写成:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = "file:./data.db"
}
model Post {
id String @id
title String
content String
created_at DateTime @default(now())
}
@id
用于指定它作为索引值@default
用于指定它的默认值,这里为 now(), 那么在创建数据的时候会以当前时间为默认值
写好 schema 后,需要运行:
pnpm exec prisma db push
# 或 npm
npx prisma db push
这个命令会使 prisma 根据 schema 创建或更新现有的数据库,并且生成对应的 TypeScript 类型。这时你会看到 prisma/data.db sqlite 数据库文件已经被创建了。
在 Remix 项目中引入 Prisma
数据库的准备工作已经完成,现在就来准备在 Node.js 中使用它了。创建一个 app/prisma.server.ts 文件:
import { PrismaClient } from "@prisma/client"
declare global {
var __prisma: PrismaClient
}
if (!global.__prisma) {
global.__prisma = new PrismaClient()
}
global.__prisma.$connect()
export const prisma = global.__prisma