瞭解如何使用 LeanCloud 創建數據庫並使用 Next.js 創建帶有服務端的應用程序。
前言
通過本教程您將瞭解到:
- 使用 LeanCloud 作為免費數據庫
- 使用 Next.js 開發一個包含前後端的應用
- 將應用發佈到 Vercel
- 使用 Tailwind 輕鬆設置樣式
我們將創建一個用於影視劇打分應用,我將它部署在 rec.zehao.me,完整源碼我放在 2eha0/records
創建 Next.js 應用
使用 Next.js 官方模板創建項目
& npx create-next-app --example with-tailwindcss my-app
該目標已經為您配置好以下內容:
- Next.js 最新版本
- TypeScript
- Tailwind CSS & 自動去除未使用的類名
- Next.js API 路由示例
創建前端組件
現在我們可以開始創建組件了,pages/index.tsx 是應用的入口文件,我們先來修改整體佈局
// pages/index.tsx
import type { NextPage } from 'next'
import Head from 'next/head'
const Home: NextPage = () => {
return (
<div className='mx-[3.5rem] min-w-[15rem] max-w-full sm:mx-auto sm:w-[30rem] font-sans'>
<Head>
<title>我看過的</title>
<meta name="viewport" content="width=device-width" />
<link rel="icon" href="/favicon.ico" />
</Head>
<h1 className='text-slate-300 flex justify-between items-center text-xl sm:text-5xl my-8 sm:my-20'>
<span>我看過的</span>
<span className='text-xs sm:text-xl'>電影 / 動漫 / 劇 / 書</span>
</h1>
</div>
)
}
export default Home
接下來,我們需要為應用添加一個卡片組件,用於顯示影視作品的信息,新建 components/card.tsx 文件
// components/card.tsx
import Image from 'next/image'
export const Card: React.FC<Props> = (props) => {
return (
<section className='relative before:content-[""] before:border-l-2 before:absolute before:inset-y-0 before:-left-9 before:translate-x-[0.44em] pb-10 first:before:top-1 last:before:bottom-10'>
<p className='text-slate-400 text-xs mb-2 sm:text-base sm:mb-3 relative'>
2022/04/02
<i className='absolute w-4 h-4 rounded-full bg-slate-200 -left-9 top-1/2 translate-y-[-50%]' />
</p>
<div className="flex items-start">
<div className="flex-1 mr-2">
<p className='text-md mb-2 sm:text-2xl sm:mb-3 leading-6 text-slate-900'>
鬼滅之刃
<span className='text-slate-400'>(2019)</span>
</p>
<p className='text-xs sm:text-base text-slate-700'>
<span className='text-slate-400'>評分:</span>
<big className='font-bold text-blue-500'>🤔 還行</big>
</p>
<p className='text-xs sm:text-base text-slate-700'>
<span className='text-slate-400'>分類:</span>
動漫
</p>
<div className="bg-white text-xs text-slate-500 leading-2 mt-4 sm:text-base">
老套的升級打怪式劇情,但動畫製作的質量還不錯,適合下飯
</div>
</div>
<div className='flex-none w-1/6 rounded-md sm:w-[5rem] sm:rounded-xl overflow-hidden bg-slate-100 relative aspect-[85/113]'>
<Image
src='https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2551222097.webp'
layout='fill'
objectFit="cover"
alt='鬼滅之刃'
className="hover:opacity-75 duration-300 ease-in-out"
/>
</div>
</div>
</section>
)
}
圖片我們使用了 next/image 組件,我們需要修改一下 next.config.js 文件,添加圖片域名配置
// next.config.js
module.exports = {
reactStrictMode: true,
images: {
domains: [
'img1.doubanio.com',
'img2.doubanio.com',
'img3.doubanio.com',
'img9.doubanio.com',
],
},
}
然後我們可以添加 <Card /> 組件到 pages/index.tsx 中,看一下效果
// pages/index.tsx
import type { NextPage } from 'next'
import Head from 'next/head'
import { Card } from '../components/card'
const Home: NextPage = () => {
return (
<div className='mx-[3.5rem] min-w-[15rem] max-w-full sm:mx-auto sm:w-[30rem] font-sans'>
<Head>
<title>我看過的</title>
<meta name="viewport" content="width=device-width" />
<link rel="icon" href="/favicon.ico" />
</Head>
<h1 className='text-slate-300 flex justify-between items-center text-xl sm:text-5xl my-8 sm:my-20'>
<span>我看過的</span>
<span className='text-xs sm:text-xl'>電影 / 動漫 / 劇 / 書</span>
</h1>
<div>
<Card />
<Card />
<Card />
</div>
</div>
)
}
export default Home
至此應用的外觀已經初見雛形了,接下來我們為 Card 添加一些 props,首先我們來定義 props 的類型,我們在根目錄下新建一個 types.ts 文件
// types.ts
export type Record = {
date: string
title: string
score: 1 | 2 | 3 | 4 | 5
comment?: string
year: number
img: string
type: 'movie' | 'tv' | 'anime' | 'book'
}
之所以放在根目錄,是因為等一下創建 API 時也會用到這個類型
接下來我們修改一下 Card 組件,將數據部分替換成 props
// components/card.tsx
import Image from 'next/image'
import { Record } from '../types'
type Props = Record
const Score: React.FC<Pick<Props, 'score'>> = ({ score }) => {
switch (score) {
case 1:
return <big className='font-bold text-black-500'>😡 不看也罷</big>
case 2:
return <big className='font-bold text-green-500'>🥱 無聊</big>
case 3:
return <big className='font-bold text-blue-500'>🤔 還行</big>
case 4:
return <big className='font-bold text-violet-500'>🤩 值得一看</big>
case 5:
return <big className='font-bold text-orange-500'>💯 神作!</big>
}
}
const renderType = (type: Props['type']) => {
const typeMap = {
movie: '電影',
tv: '劇',
book: '書',
anime: '動漫',
}
return typeMap[type] ?? '未知'
}
export const Card: React.FC<Props> = (props) => {
return (
<section className='relative before:content-[""] before:border-l-2 before:absolute before:inset-y-0 before:-left-9 before:translate-x-[0.44em] pb-10 first:before:top-1 last:before:bottom-10'>
<p className='text-slate-400 text-xs mb-2 sm:text-base sm:mb-3 relative'>
{ new Date(props.date).toLocaleDateString() }
<i className='absolute w-4 h-4 rounded-full bg-slate-200 -left-9 top-1/2 translate-y-[-50%]' />
</p>
<div className="flex items-start">
<div className="flex-1 mr-2">
<p className='text-md mb-2 sm:text-2xl sm:mb-3 leading-6 text-slate-900'>
{ props.title }
<span className='text-slate-400'>({props.year})</span>
</p>
<p className='text-xs sm:text-base text-slate-700'>
<span className='text-slate-400'>評分:</span>
<Score score={ props.score } />
</p>
<p className='text-xs sm:text-base text-slate-700'>
<span className='text-slate-400'>分類:</span>
{ renderType(props.type) }
</p>
<div className="bg-white text-xs text-slate-500 leading-2 mt-4 sm:text-base">
{ props.comment }
</div>
</div>
<div className='flex-none w-1/6 rounded-md sm:w-[5rem] sm:rounded-xl overflow-hidden bg-slate-100 relative aspect-[85/113]'>
<Image
src={ props.img }
layout='fill'
objectFit="cover"
alt={ props.title }
className="hover:opacity-75 duration-300 ease-in-out"
/>
</div>
</div>
</section>
)
}
設置 LeanCloud Storage
LeanCloud 是一個 BaaS(Backend as a Service)^Backend as a Service: [後端即服務] 平台,建議註冊國際版 LeanCloud,可免實名認證
首先,我們需要在 Data Storage 中創建一個 Class
- 將 Class 命名為
Records - 添加
img、title、type、comment和type字段,它們的類型都是String - 添加
year、score字段,將他們的類型設置為Number
創建讀取數據 API
現在我們來創建一個 API 用於讀取 LeanCloud 中的數據
首先我們需要安裝 LeanCloud JS SDK
$ npm install leancloud-storage --save
然後我們需要將 LeanCloud 的配置信息添加到 .env.local 中,配置信息可以在 "Settings" -> "App keys" 中找到
LEANCLOUD_APP_ID="{replace-your-app-id}"
LEANCLOUD_APP_KEY="{replace-to-your-app-key}"
LEANCLOUD_SERVER_URL="{replace-to-your-server-url}"
新建 pages/api/records.ts
// pages/api/records.ts
import AV from 'leancloud-storage'
import { Record } from '../../types'
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
_req: NextApiRequest,
res: NextApiResponse<Record[]>
) {
try {
const {
LEANCLOUD_APP_ID: appId,
LEANCLOUD_APP_KEY: appKey,
LEANCLOUD_SERVER_URL: serverURL,
} = process.env
if (!appId || !appKey || !serverURL) {
res.status(500).json({ error: 'Missing Leancloud config' } as any)
return
}
AV.init({ appId, appKey, serverURL })
const query = new AV.Query('Record')
const data = await query.find()
const records: Record[] = data.reverse().map(x => {
const json = x.toJSON()
return {
date: json.createdAt,
title: json.title,
score: json.score,
comment: json.comment,
year: json.year,
img: json.img,
type: json.type,
}
})
res.status(200).json(records)
} catch (e: any) {
res.status(500).json(e)
}
}
接着我們修改一下 pages/index.tsx,從 /api/records 接口獲取數據
// pages/index.tsx
import type { NextPage } from 'next'
import Head from 'next/head'
import { useEffect, useState } from 'react'
import { Card } from '../components/card'
import { Record } from '../types'
const Home: NextPage = () => {
const [ records, setRecords ] = useState<Record[] | null>(null)
useEffect(() => {
fetch('/api/records')
.then(res => res.json())
.then(setRecords)
}, [])
const renderCards = () => {
if (!records) {
return null
}
return records.map(x => <Card key={ `${x.date}${x.title}${x.year}` } { ...x } />)
}
return (
<div className='mx-[3.5rem] min-w-[15rem] max-w-full sm:mx-auto sm:w-[30rem] font-sans'>
<Head>
<title>我看過的</title>
<meta name="viewport" content="width=device-width" />
<link rel="icon" href="/favicon.ico" />
</Head>
<h1 className='text-slate-300 flex justify-between items-center text-xl sm:text-5xl my-8 sm:my-20'>
<span>我看過的</span>
<span className='text-xs sm:text-xl'>電影 / 動漫 / 劇 / 書</span>
</h1>
<div>
{ renderCards() }
</div>
</div>
)
}
export default Home
部署到 Vercel
我們的應用已經可以在本地運行了,下一步讓我們把它部署到 Vercel 上。
- 將我們的代碼提交到 git 倉庫(如 Github、GitLab、BitBucket)
- 將 Next.js 項目導入 Vercel
- 在導入期間設置環境變量
- 點擊“Deploy”
Vercel 將自動檢測您正在使用 Next.js 併為您的部署啓用正確的設置。最後,您的應用程序部署在類似 xxx.vercel.app 的 URL 上。
添加數據
現在我們的應用已經運行在公網上了,我們可以在 LeanCloud 上嘗試添加幾條數據,然後刷新頁面看看是否能夠正常顯示。
總結
在本教程中,我們能夠創建一個 Next.js 應用程序,通過 Tailwind CSS 美化界面,顯示從 LeanCloud 動態獲取的數據列表。
- 查看源碼
- 查看演示
本文轉載自我的博客 https://www.zehao.me/full-sta...