はじめに
こんにちは!WEBエンジニア転職目指しているK.Yです!
コメントいただけると嬉しいです
前回は、記事一覧を作成した内容でした!
今回は、記事詳細ページの作成となります!
Reactの基本的な機能と特徴を理解するのに役立つ要素がありますので、
Reactこれから学ぶ方にも良いインプットになってくれれば幸いです!
前回の記事と重複している部分は省略しております!!
対象者
・ React初心者
・ フロントエンドに興味がある人
バージョン
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.1"
JavaScript ES6以降
記事詳細ページ
コード
DetailsPage.js
import { Link, useParams } from 'react-router-dom';
import './App.css';
import { posts } from './data/posts';
const DetailsPage = () => {
const { id } = useParams();
const post = posts.find(post => post.id === parseInt(id));
const formatDate = (dateString) => {
const date = new Date(dateString);
const options = { year: 'numeric', month: 'numeric', day: 'numeric' };
return date.toLocaleDateString('ja-JP', options);
}
if (!post) return <div>投稿が見つかりません</div>;
return (
<div className='App'>
<header className="header-App">
<Link to="/" className="link">Blog</Link>
<Link to="/" className="link" >お問い合わせ</Link>
</header>
<div style={{ border: 'none' }} className="posts-info">
<ul>
<li>
<div><img src={post.thumbnailUrl} alt="img" /></div>
<div className="date">{formatDate(post.createdAt)}</div>
<div className="programming-language">
{post.categories.map((category, idx) => (
<span key={idx} className="category-box">{category}</span>
))}
</div>
<div className="title">{post.title}</div>
<div style={{ display: 'block' }} className="content" dangerouslySetInnerHTML={{ __html: post.content }}></div>
</li>
</ul>
</div>
</div>
);
}
export default DetailsPage;
PostsList.js
mport React from 'react'
import './App.css'
import { Link } from 'react-router-dom';
import { posts } from './data/posts';
const PostsList = () => {
// formatDate 関数を定義し、日付文字列をフォーマットする処理を関数内で行う
const formatDate = (dateString) => {
// '2023-09-11T09:00:00.000Z'を Dateオブジェクトに変換
const date = new Date(dateString);
// 年、月、日を数値形式で表示するためのオプションで定義する
const options = { year: 'numeric', month: 'numeric', day: 'numeric' };
// `Date` オブジェクトをローカルの日付文字列に変換
return date.toLocaleDateString('ja-JP', options);
};
return (
<div className="App">
<header className="header-App">
<Link className="link" to="/">Blog</Link>
<Link className="link" to="/">お問い合わせ</Link>
</header>
{
posts.map((elem) => (
<div key={elem.id} className="posts-info">
<ul >
<li>
<Link to={`/post/${elem.id}`}>
<div className="date">{formatDate(elem.createdAt)}</div>
<div className="programming-language">{elem.categories.map((category, idx) => (
<span key={idx} className="category-box">{category}</span>
))}</div>
<div className="title">{elem.title}</div>
<div className="content" dangerouslySetInnerHTML={{ __html: elem.content }}>
</div>
</Link>
</li>
</ul>
</div>
))}
</div>
);
}
export default PostsList ;
App.js
import React from 'react';
import './App.css';
import { Routes, Route } from 'react-router-dom';
import PostsList from './PostsList';
import DetailsPage from './DetailsPage';
const App = () => {
return (
<Routes>
<Route path="/" element={<PostsList />} />
<Route path="/post/:id" element={<DetailsPage />} />
</Routes>
);
};
posts.js
export const posts = [
{
id: 1,
title: 'APIで取得した記事タイトル1',
thumbnailUrl: 'https://placehold.jp/800x400.png',
createdAt: '2023-09-11T09:00:00.000Z',
categories: ['React', 'TypeScript'],
content: `
本文です。本文です。本文です。本文です。本文です。本文です。<br/>本文です。本文です。本文です。本文です。本文です。<br/><br/>本文です。本文です。本文です。本文です。本文です。本文です。本文です。本文です。本文です。<br/><br/><br/>本文です。本文です。本文です。本文です。本文です。本文です。<br/>`,
},
{
id: 2,
title: 'APIで取得した記事タイトル2',
thumbnailUrl: 'https://placehold.jp/800x400.png',
createdAt: '2023-09-10T09:00:00.000Z',
categories: ['HTML', 'CSS'],
content: `
本文です。本文です。本文です。本文です。本文です。本文です。<br/>本文です。本文です。本文です。本文です。本文です。<br/><br/>本文です。本文です。本文です。本文です。本文です。本文です。本文です。本文です。本文です。<br/><br/><br/>本文です。本文です。本文です。本文です。本文です。本文です。<br/>`,
},
{
id: 3,
title: 'APIで取得した記事タイトル3',
thumbnailUrl: 'https://placehold.jp/800x400.png',
createdAt: '2023-09-09T09:00:00.000Z',
categories: ['JavaScript'],
content: `
本文です。本文です。本文です。本文です。本文です。本文です。<br/>本文です。本文です。本文です。本文です。本文です。<br/><br/>本文です。本文です。本文です。本文です。本文です。本文です。本文です。本文です。本文です。<br/><br/><br/>本文です。本文です。本文です。本文です。本文です。本文です。<br/>`,
},
]
DetailsPage.js (詳細記事ページ)
import { Link, useParams } from 'react-router-dom';
import './App.css';
import { posts } from './data/posts';
・import { Link, useParams } from 'react-router-dom';
Link
は、ReactRouterで定義されたリンクを作成するためのコンポーネント。
これにより、ページ間のナビゲーションが可能となります。
・useParams
useParams
は、URLのパラメーターを取得するためのフック。
フックとは、関数コンポーネントで状態管理や副作用を扱うためのツール。
フックは、トップレベルで呼び出すというルールがあり、ブッロク内部等から呼び出せない。
・import { posts } from './data/posts';
posts
データをインポートしています。
このデータは投稿詳細を表示するために使用します。
コンポーネントの定義
const DetailsPage = () => {
・const DetailsPage = () => {
DetailsPage
関数コンポーネントを定義しています。
特定の詳細のページを表示させます。
urlパラメータの取得
const { id } = useParams();
const post = posts.find(post => post.id === parseInt(id));
・useParams
フックを使って、URLからidのパラメータを取得します。
・const post = posts.find(post => post.id === parseInt(id));
posts
配列の中から、idに一致する投稿を検索します。
parseInt(id)
を使って、文字列として取得したid
を整数に変換する。
一致する投稿が見つかると、post
へ格納する。
投稿が見つからなかった場合の処理
if (!post) return <div>投稿が見つかりません</div>;
・if (!post) return <div>投稿が見つかりません</div>;
一致する投稿が見つからなかった場合、投稿が見つかりません
というメッセージを表示させる。
投稿の詳細を表示
<header className="header-App">
return (
<div className='App'>
<header className="header-App">
<Link to="/" className="link">Blog</Link>
<Link to="/" className="link" >お問い合わせ</Link>
</header>
<div style={{ border: 'none' }} className="posts-info">
<ul>
<li>
<div><img src={post.thumbnailUrl} alt="img" /></div>
<div className="date">{formatDate(post.createdAt)}</div>
<div className="programming-language">
{post.categories.map((category, idx) => (
<span key={idx} className="category-box">{category}</span>
))}
</div>
<div className="title">{post.title}</div>
<div style={{ display: 'block' }} className="content" dangerouslySetInnerHTML={{ __html: post.content }}></div>
</li>
</ul>
</div>
</div>
);
</header>
・<header className="header-App">
ヘッダー部分を定義。
Link
コンポーネントを使って、ブログとお問い合わせへのリンクを作成しています。
・<div style={{ border: 'none' }} className="posts-info">
投稿の詳細情報を表示するdiv
要素です。
style={{ border: 'none' }}
はスタイルを直接設定しています。
・<div><img src={post.thumbnailUrl} alt="img" /></div>
投稿のサムネイル画像を表示している。
・<div className="programming-language"> {post.categories.map((category, idx) => ( <span key={idx} className="category-box">{category}</span> ))} </div>
投稿カテゴリ(プログラミング言語)を表示させます。
post.categories
配列をループして各カテゴリを表示します。
PostsList.js (記事一覧ページ)
PostsList.js
mport React from 'react'
import './App.css'
import { Link } from 'react-router-dom';
import { posts } from './data/posts';
const PostsList = () => {
// formatDate 関数を定義し、日付文字列をフォーマットする処理を関数内で行う
const formatDate = (dateString) => {
// '2023-09-11T09:00:00.000Z'を Dateオブジェクトに変換
const date = new Date(dateString);
// 年、月、日を数値形式で表示するためのオプションで定義する
const options = { year: 'numeric', month: 'numeric', day: 'numeric' };
// `Date` オブジェクトをローカルの日付文字列に変換
return date.toLocaleDateString('ja-JP', options);
};
return (
<div className="App">
<header className="header-App">
<Link className="link" to="/">Blog</Link>
<Link className="link" to="/">お問い合わせ</Link>
</header>
{
posts.map((elem) => (
<div key={elem.id} className="posts-info">
<ul >
<li>
<Link to={`/post/${elem.id}`}>
<div className="date">{formatDate(elem.createdAt)}</div>
<div className="programming-language">{elem.categories.map((category, idx) => (
<span key={idx} className="category-box">{category}</span>
))}</div>
<div className="title">{elem.title}</div>
<div className="content" dangerouslySetInnerHTML={{ __html: elem.content }}>
</div>
</Link>
</li>
</ul>
</div>
))}
</div>
);
}
export default PostsList ;
投稿リストのレンダリング
{
posts.map((elem) => (
<div key={elem.id} className="posts-info">
<ul >
<li>
<Link to={`/post/${elem.id}`}>
<div className="date">{formatDate(elem.createdAt)}</div>
<div className="programming-language">{elem.categories.map((category, idx) => (
<span key={idx} className="category-box">{category}</span>
))}</div>
<div className="title">{elem.title}</div>
<div className="content" dangerouslySetInnerHTML={{ __html: elem.content }}></div>
</Link>
</li>
</ul>
</div>
))
}
・<div key={elem.id} className="posts-info">
各投稿をdiv
要素で囲み、key
属性にはユニークなid
を設定しています。
・Link
コンポーネントを使って、各投稿の詳細ページへのリンクを作成します。
to={/post/${elem.id}}
は、投稿のidに基づいたURLを表示させます。
App.js (アプリを起動するエントリーポイント)
App.js
import React from 'react';
import './App.css';
import { Routes, Route } from 'react-router-dom';
import PostsList from './PostsList';
import DetailsPage from './DetailsPage';
const App = () => {
return (
<Routes>
<Route path="/" element={<PostsList />} />
<Route path="/post/:id" element={<DetailsPage />} />
</Routes>
);
};
・App.js
このファイルは、Reactアプリを実行する際、一番早く呼び出させるファイル(エントリーポイント)です!
・import DetailsPage from './DetailsPage';
PostsList(記事一覧ページ)
と同じ様に、DetailsPage
もインポートさせます。
・ルーティングの設定
return (
<Routes>
<Route path="/" element={<PostsList />} />
<Route path="/post/:id" element={<DetailsPage />} />
</Routes>
);
・<Route path="/post/:id" element={<DetailsPage />} />
path="/post/:id"
は、特定の投稿idに基づいたパスです。
id
はパスパラメータで、任意のidを受け取ることができます。
このパスにアクセルすると、DetailsPage
コンポーネントが表示させます。
ポイント
・React Routerを使ったルーティング
react-router-dom
は、Reactアプリケーションでルーティングを管理するもの。
・フック(Hooks)
Reactのフックは、関数パラメータの状態管理や副作用処理を行う機能。
今回は、useParams
というフックを使って、URLパラメータを取得しました。
・条件付きレンダリング
Reactでは、条件に基づいてコンポーネントや要素をレンダリングすることができます。
まとめ
今回は、記事詳細ページ作成についての内容でした
次回は、APIで記事データを取得し表示させるという内容です!
今までは、手書きでデータを表示させましたが、次はAPIからデータを取得し、
表示させるという内容です!
お楽しみに!