به دنبال ارتقاء مهارت های توسعه React خود هستید؟ با کمک این راهنما نسخه خود از Hacker News را بسازید.
Hacker News یک وب سایت محبوب در بین کارآفرینان و توسعه دهندگان است. این دارای محتوای متمرکز بر علوم کامپیوتر و کارآفرینی است.
طرح ساده Hacker News ممکن است برای افراد خاصی مناسب باشد. با این حال، اگر میخواهید نسخه جذابتر و شخصیشدهتری داشته باشید، میتوانید از APIهای مفید برای ایجاد تجربه سفارشیشده Hacker News خود استفاده کنید. همچنین، ساختن کلون Hacker News می تواند به شما در تقویت مهارت های React کمک کند.
راه اندازی سرور پروژه و توسعه
کد مورد استفاده در این پروژه در یک مخزن GitHub موجود است و برای استفاده شما تحت مجوز MIT رایگان است.
برای استایل، محتویات فایل index.css را از مخزن کپی کرده و در فایل index.css خود قرار دهید. اگر میخواهید نگاهی به نسخه زنده این پروژه بیندازید، میتوانید این دمو را مشاهده کنید.
پکیج های مورد نیاز برای این پروژه عبارتند از:
- روتر React برای مدیریت مسیریابی در برنامه یک صفحه (SPA).
- HTMLReactParser برای تجزیه HTML بازگردانده شده توسط Application Programming Interface (API).
- MomentJS برای رسیدگی به تاریخ های بازگشتی توسط API.
ترمینال را باز کنید و اجرا کنید:
yarn create vite
همچنین می توانید از Node Package Manager (NPM) استفاده کنید اگر آن را به نخ ترجیح می دهید. دستور بالا باید از ابزار Vite build برای داربست یک پروژه اساسی استفاده کند. پروژه خود را نامگذاری کنید و هنگامی که فریمورک از شما خواسته شد، React را انتخاب کنید و نوع آن را روی JavaScript تنظیم کنید.
اکنون در پوشه پروژه سی دی قرار دهید و با اجرای دستورات زیر در ترمینال بسته های ذکر شده را نصب کنید:
yarn add html-react-parser
yarn add react-router-dom
yarn add moment
yarn dev
پس از نصب تمام بسته ها و راه اندازی سرور توسعه، پروژه را در هر ویرایشگر کد باز کنید و سه پوشه در پوشه src ایجاد کنید: اجزا، قلاب ها و صفحات.
در پوشه اجزاء، دو فایل Comments.jsx و Navbar.jsx اضافه کنید. در پوشه hooks، یک فایل useFetch.jsx اضافه کنید. سپس در پوشه pages دو فایل ListPage.jsx و PostPage.jsx را اضافه کنید.
فایل App.css را حذف کنید و محتوای فایل main.jsx را با موارد زیر جایگزین کنید:
import React from 'react'
import { BrowserRouter } from 'react-router-dom'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
)
در فایل App.jsx، تمام کدهای boilerplate را حذف کنید و فایل را طوری تغییر دهید که فقط جزء کاربردی باقی مانده باشد:
function App() {
return (
<>
</>
)
}
export default App
ماژول های لازم را وارد کنید:
import { Routes, Route } from 'react-router-dom'
import ListPage from './pages/ListPage'
import Navbar from './components/Navbar'
import PostPage from './pages/PostPage'
در قطعه React، اجزای Routes را با سه مؤلفه فرزند مسیر با مسیرهای: /، /:type و /item/:id اضافه کنید.
<Routes>
<Route path='/'
element={<> <Navbar /><ListPage /></>}>
</Route>
<Route path='/:type'
element={<> <Navbar /><ListPage /></>}>
</Route>
<Route path='/item/:id'
element={<PostPage />}>
</Route>
</Routes>
ایجاد useFetch Custom Hook
این پروژه از دو API استفاده می کند. اولین API مسئول واکشی لیست پست ها در یک دسته بندی خاص (نوع) است، در حالی که API دوم Algolia API است که مسئول واکشی یک پست خاص و نظرات آن است.
فایل useFetch.jsx را باز کنید، قلاب را به عنوان یک صادرات پیشفرض تعریف کنید و قلابهای useState و useEffect را وارد کنید.
import { useState, useEffect } from "react";
export default function useFetch(type, id) {
}
سه متغیر حالت به نام های داده، خطا و بارگذاری را با توابع تنظیم کننده مربوطه تعریف کنید.
const [data, setData] = useState();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(true);
سپس یک قلاب useEffect با وابستگی ها اضافه کنید: id و type.
useEffect(() => {
}, [id, type])
بعد در تابع callback، تابع fetchData() را اضافه کنید تا داده ها از API های مناسب واکشی شوند. اگر پارامتر ارسال شده از نوع است، از اولین API استفاده کنید. در غیر این صورت از API دوم استفاده کنید.
async function fetchData() {
let response, url, parameter;
if (type) {
url = "https://node-hnapi.herokuapp.com/";
parameter = type.toLowerCase();
}
else if (id) {
url = "https://hn.algolia.com/api/v1/items/";
parameter = id.toLowerCase();
}
try {
response = await fetch(`${url}${parameter}`);
} catch (error) {
setError(true);
}
if (response) if (response.status !== 200) {
setError(true);
} else {
let data = await response.json();
setLoading(false);
setData(data);
}
}
fetchData();
در نهایت، متغیرهای بارگذاری، خطا و وضعیت داده را به عنوان یک شی برگردانید.
return { loading, error, data };
ارائه لیست پست ها بسته به رده درخواستی
هر زمان که کاربر به / یا /:type می رود، React باید مولفه ListPage را رندر کند. برای پیاده سازی این قابلیت، ابتدا ماژول های لازم را وارد کنید:
import { useNavigate, useParams } from "react-router-dom";
import useFetch from "../hooks/useFetch";
سپس جزء تابعی را تعریف کرده و سپس پارامتر دینامیک را به متغیر type تایپ کنید. اگر پارامتر پویا در دسترس نیست، متغیر نوع را روی news تنظیم کنید. سپس، قلاب useFetch را فراخوانی کنید.
export default function ListPage() {
let { type } = useParams();
const navigate = useNavigate();
if (!type) type = "news";
const { loading, error, data } = useFetch(type, null);
}
در مرحله بعد، بسته به اینکه کدام یک از متغیرهای بارگذاری، خطا یا داده درست است، کد JSX مناسب را برگردانید.
if (error) {
return <div>Something went wrong!</div>
}
if (loading) {
return <div>Loading</div>
}
if (data) {
document.title = type.toUpperCase();
return <div>
<div className='list-type'>{type}</div>
<div>{data.map(item =>
<div key={item.id} className="item">
<div className="item-title"
onClick={() => navigate(`/item/${item.id}`)}>
{item.title}
</div>
{item.domain &&
<span className="item-link"
onClick={() => open(`${item.url}`)}>
({item.domain})</span>}
</div>)}
</div>
</div>
}
ایجاد کامپوننت PostPage
ابتدا ماژول ها و کامپوننت های مناسب را وارد کنید، سپس مولفه عملکردی پیش فرض را تعریف کنید، پارامتر id dynamic را به متغیر id اختصاص دهید و قلاب useFetch را فراخوانی کنید. مطمئن شوید که پاسخ را تخریب کرده اید.
import { Link, useParams } from "react-router-dom";
import parse from 'html-react-parser';
import moment from "moment";
import Comments from "../components/Comments";
import useFetch from "../hooks/useFetch";
export default function PostPage() {
const { id } = useParams();
const { loading, error, data } = useFetch(null, id);
}
و درست مانند مؤلفه ListPage، JSX مناسب را بر اساس وضعیت متغیرهای زیر ارائه دهید: بارگذاری، خطا و داده.
if (error) {
return <div>Something went wrong!</div>
}
if (loading) {
return <div>Loading</div>
}
if (data) {
document.title=data.title;
return <div>
<div className="post-title">{data.title}</div>
<div className="post-metadata">
{data.url &&
<Link to={data.url}
className="post-link">Visit Website</Link>}
<span className="post-author">{data.author}</span>
<span className="post-time">
{moment(data.created_at).fromNow()}
</span>
</div>
{data.text &&
<div className="post-text">
{parse(data.text)}</div>}
<div className="post-comments">
<div className="comments-label">Comments</div>
<Comments commentsData={data.children} />
</div>
</div>
}
ارائه بخش نظرات با پاسخ های تودرتو
ماژول تجزیه و ماژول لحظه را وارد کنید. کامپوننت های تابعی پیش فرض را تعریف کنید که آرایه commentsData را به عنوان یک پایه می گیرد، از میان آرایه ها عبور می کند و برای هر عنصر یک مولفه Node ارائه می دهد.
import parse from 'html-react-parser';
import moment from "moment";
export default function Comments({ commentsData }) {
return <>
{commentsData.map(commentData => <Node commentData={commentData} key={commentData.id}
/>)}
</>
}
بعد، کامپوننت تابعی Node را درست در زیر کامپوننت Comments تعریف کنید. مؤلفه Node کامنت، ابرداده و پاسخ به هر نظر (در صورت وجود) را با رندر بازگشتی خود ارائه می دهد.
function Node({ commentData }) {
return <div className="comment">
{
commentData.text &&
<>
<div className='comment-metadata'>
<span>{commentData.author}</span>
<span>
{moment(commentData.created_at).fromNow()}
</span>
</div>
<div className='comment-text'>
{parse(commentData.text)}</div>
</>
}
<div className='comment-replies'>
{(commentData.children) &&
commentData.children.map(child =>
<Node commentData={child} key={child.id}/>)}
</div>
</div>
}
در بلوک کد بالا، parse مسئول تجزیه HTML ذخیره شده در commentData.text است، در حالی که moment مسئول تجزیه زمان نظر و برگرداندن زمان نسبی با استفاده از متد fromNow() است.
ایجاد مولفه Navbar
فایل Navbar.jsx را باز کنید و ماژول NavLink را از ماژول react-router-dom وارد کنید. در نهایت، کامپوننت عملکردی را تعریف کنید و یک عنصر nav والدین را با پنج عنصر NavLink که به دستهها (یا انواع) مناسب اشاره میکنند، برگردانید.
import { NavLink } from "react-router-dom"
export default function Navbar() {
return <nav>
<NavLink to="/news">Home</NavLink>
<NavLink to="/best">Best</NavLink>
<NavLink to="/show">Show</NavLink>
<NavLink to="/ask">Ask</NavLink>
<NavLink to="/jobs">Jobs</NavLink>
</nav>
}
تبریک میگم شما به تازگی مشتری front-end خود را برای Hacker News ساخته اید.
مهارت های React خود را با ساختن اپلیکیشن Clone تقویت کنید
ساختن یک کلون اخبار هکر با React می تواند به تقویت مهارت های React شما کمک کند و یک برنامه کاربردی Single Page برای کار بر روی آن ارائه دهد. راه های زیادی وجود دارد که می توانید کارها را جلوتر ببرید. برای مثال، میتوانید قابلیت جستجوی پستها و کاربران را به برنامه اضافه کنید.
به جای تلاش برای ساخت روتر خود از ابتدا، بهتر است از ابزاری استفاده کنید که توسط متخصصانی ساخته شده است که برای آسان کردن ایجاد SPA اختصاص داده شده اند.