天天看點

Next.js 事件管理應用程式使用基于檔案的路由

作者:qaseven

Next.js 可以說是使用 React 建構 Web 應用程式的最通用的架構。在這裡,學習如何建構 Next.js 事件管理應用程式。

在使用 React 建構 Web 應用程式時,Next.js 可以說是最通用的架構。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 內建進一步增強應用程式,用于靜态和伺服器端呈現和存儲資料。

如果您對這篇文章有任何意見或疑問,請随時在下面的評論部分中提及。

繼續閱讀