Next.js 可以說是使用 React 建構 Web 應用程式的最通用的架構。在這裡,學習如何建構 Next.js 事件管理應用程式。
在使用 React 建構 Web 應用程式時,Next.js 可以說是最通用的架構。Next.js 使建構生産就緒的應用程式變得容易。在這篇文章中,我們将着眼于建構Next.js 事件管理應用程式。
完成後,我們的應用程式将如下圖所示:
這個應用程式将是純 Next.js 元件與其他 React 元件的混合。此外,還會使用一些 CSS 來設定元件的樣式。最後,我們将使用Next.js 基于檔案的路由來連接配接應用程式。
最終,這個應用程式的想法是展示我們如何使用 Next.js 和普通 React 建構一個更大的應用程式。
1. 建立一個新的 Next.js 項目
作為使用 Next.js 的先決條件,我們需要在系統上安裝 Node.js。您可以從官方網站為您的作業系統安裝 Node.js。
設定好 Node.js 後,我們可以使用 Next.js 開始我們的事件管理應用程式。
首先,我們将使用以下指令建立一個新的 Next.js 項目。
$ npx create-next-app
上面的指令會生成一個啟動項目。
無論如何,這裡要注意的重要一點是package.json檔案。如果打開檔案,您将能夠看到各種腳本和依賴項。
{
"name": "nextjs-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "12.2.3",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"eslint": "8.20.0",
"eslint-config-next": "12.2.3"
}
}
2. 建立虛拟事件管理資料
雖然一個典型的 prod 應用程式可能有一個資料庫來存儲資訊,但我們将使用一些虛拟事件資料來讓我們的應用程式運作。通常,開發人員更喜歡以這種方式開始,以便他們可以在幹預與資料庫相關的更改等之前先了解應用程式。
在我們項目的根目錄下,我們建立了一個檔案dummy-data.js. 見下文:
const DUMMY_EVENTS = [
{
id: 'event1',
title: 'Programming for Everyone',
description: 'Everyone can learn to code! Yes, everyone! Live Event',
location: 'A street 25, San Francisco',
date: '2022-06-14',
image: 'images/coding-event.jpg',
isFeatured: false
},
{
id: 'event2',
title: 'Networking Basics',
description: 'Making networking for introverts fun',
location: 'Street 47, New York',
date: '2022-06-21',
image: 'images/network-event.jpg',
isFeatured: true
},
{
id: 'event2',
title: 'Networking Advanced',
description: 'Making networking for advanced use-cases',
location: 'Street 47, New York',
date: '2022-07-25',
image: 'images/network-event-advanced.jpg',
isFeatured: true
}
]
export function getFeaturedEvents() {
return DUMMY_EVENTS.filter((event) => event.isFeatured);
}
export function getAllEvents() {
return DUMMY_EVENTS;
}
export function getFilteredEvents(dateFilter) {
const { year, month } = dateFilter;
let filteredEvents = DUMMY_EVENTS.filter((event) => {
const eventDate = new Date(event.date);
return eventDate.getFullYear() === year && eventDate.getMonth() === month - 1;
})
return filteredEvents;
}
export function getEventById(id) {
return DUMMY_EVENTS.find((event) => event.id === id);
}
如您所見,我們有DUMMY_EVENTS一個包含事件清單及其各種詳細資訊的數組。此外,我們從這個檔案中導出了一堆函數。
基本上,這些函數用于從事件數組中擷取或過濾事件。以下是每個功能的詳細資訊。
getFeaturedEvents()– 此函數傳回一個事件清單,其isFeatured标志設定為 true。
getAllEvents()– 此函數傳回所有事件。
getFilteredEvents()– 此函數根據過濾條件傳回事件清單。在目前的實作中,我們支援按年和月過濾。
getEventById()– 最後,此函數為輸入事件 ID 傳回單個事件。
您可以将這些函數視為擷取事件日期的接口。我們沒有将這些函數公開為外部 REST API,因為我們隻會在應用程式内部使用它們。
3. 使用基于檔案的路由建立 Next.js 路由
此時,我們可以開始為我們的事件管理應用程式的各個頁面建構 Next.js 路由。
從廣義上講,我們将為我們的申請提供以下途徑:
The root path (/) – 這是起始頁面,将顯示特色事件清單。換句話說,那些isFeatured标志設定為真的事件。
All events page (/events)– 此頁面将顯示所有事件的清單。
Single Event (/events/<some_id>) – 此頁面将根據輸入 id 顯示單個事件的詳細資訊。
Filtered Events (/events/…slug) – 此頁面将顯示基于條件的過濾事件清單。例如,如果我們通路 /events/2022/06,它應該顯示 2022 年 6 月的事件清單。
對于上述每個路徑,讓我們建立适當的 Next.js 元件。
3.1:首頁
Next.js 有一個特殊的系統來處理路由。
基本上,pages我們的項目中有一個特定的檔案夾。我們在這個檔案夾中建立的任何元件都會被 Next.js 公開為路由。這也稱為 Next.js 基于檔案的路由。
在pages目錄中,我們将建立一個名為index.js. 這是用于渲染我們應用程式首頁的檔案。
見下文:
import { getFeaturedEvents } from '../dummy-data';
import EventList from '../components/events/EventList';
function HomePage() {
const featuredEvents = getFeaturedEvents();
return (
<div>
<EventList items={featuredEvents} />
</div>)
}
export default HomePage;
導出 預設 首頁;
如您所見,這是一個普通的 React 元件。它從作為dummy-data.js檔案一部分公開的适當函數中擷取特色事件清單。
一旦獲得資料,它就會将清單傳遞給另一個 React 元件EventList。下一節将介紹 React 元件的詳細資訊以供參考。
3.2:所有活動頁面
此頁面顯示所有事件的清單。為了更好的隔離,我們将此頁面的元件檔案放在目錄内的檔案夾events中pages。
import { useRouter } from 'next/router';
import EventList from "../../components/events/EventList";
import EventSearch from "../../components/events/EventSearch";
import { getAllEvents } from "../../dummy-data";
function AllEventsPage() {
const router = useRouter();
const events = getAllEvents();
function findEventsHandler(year, month) {
const fullPath = `/events/${year}/${month}`;
router.push(fullPath);
}
return (
<div>
<EventSearch onSearch={findEventsHandler} />
<EventList items={events} />
</div>
)
}
export default AllEventsPage;
導出 預設 AllEventsPage;
在這個元件中有幾個要點需要注意:
首先,我們使用該getAllEvents()函數從虛拟事件資料中擷取所有事件。EventList該清單使用通用元件呈現。
其次,我們還具有根據此頁面上的過濾條件搜尋事件的功能。為此,我們有一個EventSearch元件。onSearch這個元件接受一個指向findEventsHandler函數的 prop 。基本上,EventSearch元件将過濾年份和過濾月份傳遞給AllEventsPage元件。使用過濾器年份和過濾器月份,我們建構了一個路由路徑并使用該router.push()實用程式以程式設計方式更改我們應用程式的路由。
您可以在下面檢視 EventSearch 元件的代碼:
component/events/EventSearch.jsimport { useRef } from 'react';
import Button from "../ui/Button";
import classes from "./event-search.module.css";
function EventSearch(props) {
const yearInputRef = useRef();
const monthInputref = useRef();
function submitHandler(event) {
event.preventDefault();
const selectedYear = yearInputRef.current.value;
const selectedMonth = monthInputref.current.value;
props.onSearch(selectedYear, selectedMonth);
}
return (
<form className={classes.form} onSubmit={submitHandler}>
<div className={classes.controls}>
<div className={classes.control}>
<label htmlFor="year">Year</label>
<select id="year" ref={yearInputRef}>
<option value="2021">2021</option>
<option value="2022">2022</option>
</select>
</div>
<div className={classes.control}>
<label htmlFor="month">Month</label>
<select id="month" ref={monthInputref}>
<option value="1">January</option>
<option value="2">February</option>
<option value="3">March</option>
<option value="4">April</option>
<option value="5">May</option>
<option value="6">June</option>
<option value="7">July</option>
<option value="8">August</option>
<option value="9">September</option>
<option value="10">October</option>
<option value="11">November</option>
<option value="12">December</option>
</select>
</div>
</div>
<Button>Find Events</Button>
</form>
)
}
export default EventSearch;
基本上,這個元件隻是簡單地處理過濾年份和過濾月份的表單字段。
目前,根據此檔案,我們僅支援 2021 年和 2022 年。在實際應用程式中,我們将在此元件中有一個月曆小部件。
當使用者通過單擊表單按鈕送出表單時,我們稱之為props.onSearch将selectedYearand傳遞selectedMonth給父元件。
3.3:單一事件頁面
單一事件頁面基本上是特定事件的頁面。它隻顯示所選事件的詳細資訊。
從實作的角度來看,它是一個極其簡單的元件。見下文:
import { useRouter } from 'next/router';
import EventItem from '../../components/events/EventItem';
import { getEventById } from '../../dummy-data';
function EventDetailPage() {
const router = useRouter();
const eventId = router.query.eventId;
const event = getEventById(eventId);
if (!event) {
return <p>No Event Found</p>
}
return (
<EventItem
id={event.id}
title={event.title}
location={event.location}
date={event.date}
image={event.image} />
)
}
export default EventDetailPage;
導出 預設 EventDetailPage;
唯一需要注意的是,這是一個動态頁面。換句話說,内容取決于eventId路徑中的。
在 Next.js 中,我們建立了這樣的元件,其命名約定為[eventId].js. 基本上,這表示它eventId是動态的并且在浏覽器路徑中可用。為了提取eventId,我們利用useRouter()鈎子然後調用getEventById()函數。
這個頁面也使用了一個通用元件EventItem。我們将在下一節中介紹它。
3.4:過濾事件頁面
最後,我們還可以為過濾後的事件建立一個頁面。
由于這也是一個取決于年月值的動态頁面,我們将檔案命名為[...slug].js. 在這裡,slug 将包含路徑所有部分的清單。
例如,如果我們通路/events/2022/06,則 slug 數組将包含 values ['2022', '06']。
檢視以下實作:
import { useRouter } from 'next/router';
import EventList from '../../components/events/EventList';
import { getFilteredEvents } from '../../dummy-data';
function FilteredEventsPage() {
const router = useRouter();
const filterData = router.query.slug;
if (!filterData) {
return <p className='center'>Loading...</p>
}
const filteredYear = filterData[0];
const filteredMonth = filterData[1];
const numYear = +filteredYear;
const numMonth = +filteredMonth;
if (isNaN(numYear) || isNaN(numMonth)) {
return <p className='center'>Invalid Filter Criteria. Please check...</p>
}
const filteredEvents = getFilteredEvents({
year: numYear,
month: numMonth
});
if (!filteredEvents || filteredEvents.length === 0) {
return <p>No Events Found!!</p>
}
return(
<div>
<EventList items={filteredEvents} />
</div>
)
}
export default FilteredEventsPage;
導出 預設 FilteredEventsPage;
就像之前的元件一樣,這裡我們也使用useRouter()鈎子來提取slug. 然後,我們確定年份和月份具有數值。如果值沒問題,我們隻需調用該getFilteredEvents()函數。
再一次,我們使用相同的EventList元件來呈現事件清單。
4. 處理事件資料的常用 React 元件
現在我們的應用程式的主要頁面已經完成,讓我們看看我們在應用程式中使用的常見 React 元件。
為了更好地管理我們的源代碼,我們将通用元件儲存在一個名為components. 請注意,我們不能将這些元件保留在pages目錄中。這是因為pagesNext.js 使用目錄中的任何内容來建立路由。
在該components目錄中,我們為與事件相關的元件建立一個檔案夾。
第一個重要的元件是EventList元件。
components/events/EventList.jsimport EventItem from './EventItem';
import classes from './event-list.module.css';
function EventList(props) {
const { items } = props;
return (
<ul className={classes.list}>
{items.map(event => <EventItem key={event.id}
id={event.id}
title={event.title}
location={event.location}
date={event.date}
image={event.image} />)}
</ul>
)
}
export default EventList;
基本上,該元件接收事件清單并生成無序清單。它還使用EventItem元件。見下文:
components/events/EventItem.jsimport Link from 'next/link';
import Button from '../ui/Button';
import classes from './event-item.module.css';
function EventItem(props) {
const { title, image, date, location, id } = props;
const humanReadableDate = new Date(date).toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric'
});
const formattedAddress = location.replace(', ', '\n')
const exploreLink = `/events/${id}`
return (
<li className={classes.item}>
<img src={'/' + image} alt={title} />
<div className={classes.content}>
<div className={classes.summary}>
<h2>{title}</h2>
<div className={classes.date}>
<time>{humanReadableDate}</time>
</div>
</div>
<div className={classes.address}>
<address>{formattedAddress}</address>
</div>
</div>
<div className={classes.actions}>
<Button link={exploreLink}>Explore Event</Button>
</div>
</li>
)
}
export default EventItem;
基本上,該元件接收單個事件的資料。它重新格式化資料以用于示範目的:例如,将日期更改為人類可讀的格式并格式化位址。此外,它為Explore Event的按鈕構造了适當的連結。
此外,我們還有一個特殊的Button元件。
components/ui/Button.jsimport Link from 'next/link';
import classes from './button.module.css';
function Button(props) {
if (props.link) {
return <Link href={props.link}>
<a className={classes.btn}>{props.children}</a>
</Link>
}
return <button className={classes.btn} onClick={props.onClick}>{props.children}</button>
}
export default Button;
基本上,該Button元件處理它充當Link. 此外,如果props.link未定義,則它充當普通按鈕。
5. Next.js 事件管理應用導航
雖然我們的應用程式顯示了各個頁面,但沒有适當的導航欄。我們在每個頁面上都需要這個導航欄。
是以,我們為此建立了另一個通用元件。
首先是Layout元件。
components/layout/Layout.jsimport { Fragment } from "react";
import MainHeader from "./MainHeader";
function Layout(props) {
return <Fragment>
<MainHeader />
<main>
{props.children}
</main>
</Fragment>
}
export default Layout;
第二個是MainHeader元件。
components/events/MainHeader.jsimport Link from 'next/link';
import classes from './main-header.module.css';
function MainHeader() {
return (
<header className={classes.header}>
<div className={classes.logo}>
<Link href="/">Next Events</Link>
</div>
<nav className={classes.navigation}>
<Link href="/events">All Events</Link>
</nav>
</header>
)
}
export default MainHeader;
基本上,這是我們定義應用程式徽标和導航到AllEvents頁面的連結的地方。為了導航,我們使用LinkNext.js 附帶的特殊元件。Next.js Link 元件在不使用 React 路由器的情況下幫助應用導航。
最後,為了在每個頁面上顯示此内容,我們将應用程式的主要元件,即目錄中的MyApp元件(_app.js 檔案)包裝起來pages。
import Layout from '../components/layout/layout'
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
return <Layout>
<Component {...pageProps} />
</Layout>
}
export default MyApp
請注意,此檔案存在于啟動項目中。我們隻需要修改相同的。
6. 為 Next.js 應用程式設計樣式
最後,您可能已經注意到我們在各種元件中使用了一堆 CSS 類。為了将 CSS 限定到特定元件,我們使用了 CSS 子產品系統。
雖然 CSS 是完全可選的,但它确實有助于我們項目的外觀和感覺。
下面是各種元件的 CSS 檔案。
.item {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3), 0 1px 12px 2px rgba(0, 0, 0, 0.2);
border-radius: 8px;
overflow: hidden;
background-color: white;
margin: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.item img {
width: 100%;
object-fit: cover;
height: 10rem;
}
.content {
width: 100%;
padding: 0 1rem;
text-align: center;
}
.content h2 {
margin: 0.5rem 0;
}
.date,
.address {
display: flex;
gap: 0.5rem;
align-items: center;
}
.date svg,
.address svg {
width: 1.25rem;
height: 1.25rem;
color: #666666;
}
.content time {
color: #666666;
font-weight: bold;
}
.content address {
margin: 0.5rem 0;
color: #666666;
white-space: pre;
}
.actions {
display: flex;
flex-direction: column;
padding: 1rem;
}
.actions a {
display: block;
}
.actions a span {
vertical-align: middle;
}
.icon {
margin-left: 0.5rem;
display: inline-flex;
justify-content: center;
align-items: center;
}
.icon svg {
width: 1.25rem;
height: 1.25rem;
}
@media (min-width: 768px) {
.item {
flex-direction: row;
}
.item img {
width: 40%;
height: 14rem;
}
.content {
width: 60%;
padding: 0;
text-align: left;
}
.content h2 {
margin: 1rem 0;
}
.actions {
flex-direction: row;
justify-content: flex-end;
}
}
您可以将這些 CSS 檔案放在項目層次結構中的元件檔案旁邊。這将有助于輕松參考和維護。
結論
有了這個,我們的Next.js 事件管理應用程式就準備好了。
我們使用基于檔案的路由來使進階頁面工作。但是,對于個别功能,我們利用了基本的 React 元件。将 Next.js 概念與 React 結合使用,這使得 Next.js 成為建構複雜應用程式的絕佳工具。
此應用程式的代碼可在GitHub上找到。
我們可以使用Next.js Firebase 內建進一步增強應用程式,用于靜态和伺服器端呈現和存儲資料。
如果您對這篇文章有任何意見或疑問,請随時在下面的評論部分中提及。