Cách tạo bản sao Hacker News bằng React

Bạn đang muốn nâng cấp kỹ năng lập trình React của bản thân? Vậy thì hãy thử xây dựng phiên bản riêng của Hacker News với sự trợ giúp của hướng dẫn dưới đây.

Lập trình trang giống Hacker News

Hacker News là một web phổ biến trong giới doanh nhân và lập trình viên. Trang tin này tập trung vào khoa học máy tính và kinh doanh.

Bố cục đơn giản của Hacker News có thể phù hợp với một số cá nhân. Tuy nhiên, nếu muốn phiên bản hấp dẫn và được cá nhân hóa hơn, bạn có thể dùng API hữu ích để tạo trải nghiệm Hacker News được tùy biến. Ngoài ra, xây dựng bản sao Hacker News có thể giúp bạn củng cố kỹ năng React.

Thiết lập dự án và server phát triển

Các package cần trong lập trình bao gồm:

  • React Router để xử lý định tuyến trong Single Page Application (SPA).
  • HTMLReactParser để phân tích HTML được trả về bởi Application Programming Interface (API).
  • MomentJS để xử lý ngày tháng được trả về bởi API.

Mở terminal này và chạy:

yarn create vite

Bạn cũng có thể dùng Node Package Manager (NPM) nếu thích nó trên yarn. Lệnh trên sẽ dùng công cụ Vite để dàn dựng một dự án cơ bản. Đặt tên cho dự án và khi được nhắc cho framework, chọn React và đặt biến sang JavaScript.

Giờ đưa cd vào thư mục dự án và cài đặt các gói được đề cập trước đó bằng cách chạy lệnh sau trong terminal:

yarn add html-react-parser
yarn add react-router-dom
yarn add moment
yarn dev

Sau khi cài đặt tất cả các gói và bắt đầu server lập trình, mở dự án trong trình chỉnh sửa code bất kỳ và tạo 3 thư mục trong src có tên lần lượt là: components, hooks, và pages.

Trong thư mục components, thêm hai file: Comments.jsxNavbar.jsx. Trong thư mục hooks, thêm một file useFetch.jsx. Sau đó, trong thư mục pages, thêm 2 file ListPage.jsxPostPage.jsx.

Xóa file App.css và thay nội dung của file main.jsx bằng:

yarn add html-react-parser
yarn add react-router-dom
yarn add moment
yarn dev

Trong file App.jsx, loại bỏ tất cả code soạn sẵn và chỉnh sửa file sao cho bạn chỉ còn thành phần chức năng:

function App() {
  return (
    <>
    </>
  )
}

export default App

Nhập các mô đun cần thiết:

import { Routes, Route } from 'react-router-dom'
import ListPage from './pages/ListPage'
import Navbar from './components/Navbar'
import PostPage from './pages/PostPage'

Trong fragment React, thêm các thành phần Routes với 3 thành phần con Route cùng đường dẫn: /, /:type,và /item/:id tương ứng.

<Routes>
    <Route path='/' 
    element={<> <Navbar /><ListPage /></>}>
   </Route>
    <Route path='/:type' 
    element={<> <Navbar /><ListPage /></>}>
   </Route>
    <Route path='/item/:id' 
    element={<PostPage />}>
   </Route>
</Routes>

Tạo hook tùy biến useFetch

Dự án này dùng 2 API. API đầu tiên chịu trách nhiệm tìm nạp danh sách bài viết trong thư mục được cung cấp, còn API thứ hai là Algolia API, chịu trách nhiệm tìm nạp một bài viết cụ thể và bình luận của nó.

Mở file useFetch.jsx, xác định hook là lựa chọn xuất mặc định và nhập hook useStateuseEffect.

import { useState, useEffect } from "react";
export default function useFetch(type, id) {

}

Xác định 3 biến trạng thái tên: data, error loading với các chức năng thiết lập tương ứng của chúng.

const [data, setData] = useState();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(true);

Sau đó, thêm hook useEffect với các phần phụ thuộc: id type.

useEffect(() => {
}, [id, type])

Tiếp theo trong hàm callback, thêm hàm fetchData() để tìm nạp dữ liệu từ API phù hợp. Nếu tham số được chuyển là type, dùng API đầu tiên. Nếu không, dùng API thứ hai.

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();

Cuối cùng, trả về các biến trạng thái loading, error, data dưới dạng một đối tượng.

return { loading, error, data };

Hiển thị danh sách bài viết theo danh mục được truy vấn

Bất cứ khi nào người dùng điều hướng tới / hoặc /:type, React sẽ hiện thành phần ListPage. Để triển khai tính năng này, đầu tiên, nhập các mô đun cần thiết:

import { useNavigate, useParams } from "react-router-dom";
import useFetch from "../hooks/useFetch";

Sau đó, xác định thành phần hàm, rồi gắn tham số động, type cho biến type. Nếu không có sẵn tham số động, đặt biến type cho news. Sau đó, gọi hook useFetch.

export default function ListPage() {
    let { type } = useParams();
    const navigate = useNavigate();
    if (!type) type = "news";
    const { loading, error, data } = useFetch(type, null);
}

Tiếp theo, trả về code JSX phù hợp tùy thuộc vào một trong số biến trạng thái loading, error hay data true.

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>
}

Tạo thành phần PostPage

Đầu tiên, nhập các mô đun và thành phần phù hợp, sau đó xác định thành phần hàm mặc định, gắn tham số động id sang biến id, gọi hook useFetch. Đảm bảo bạn hủy cấu trúc phản hồi.

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);
}

Và giống như thành phần ListPage, hiện JSX phù hợp dựa trên trạng thái của các biến sau: loading, error data.

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>
}

Hiện phần bình luận với các câu trả lời lồng nhau

Nhập mô đun parsemoment. Xác định thành phần chức năng mặc định Comments, lấy mảng commentsData làm thuộc tính, duyệt qua các mảng và hiện thành phần Node cho từng nhân tố.

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}
        />)}
    </>
}

Tiếp theo, xác định thành phần chức năng Node ngay bên dưới Comments. Thành phần Node hiện nhận xét, metadata và câu trả lời cho từng bình luận (nếu có) bằng cách hiện đệ quy chính nó.

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>
}

Trong khối code trên, parse chịu trách nhiệm phân tích HTML được lưu trong commentData.text, còn moment chịu trách nhiệm phân tích thời gian bình luận và trả về thời gian liên quan bằng phương thức fromNow().

Tạo thành phần Navbar

Mở file Navbar.jsx và nhập mô đun NavLink từ react-router-dom. Cuối cùng, xác định thành phần hàm và trả về một nav cha với 5 thành phần NavLink trỏ tới các danh mục phù hợp (hoặc kiểu).

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>
}

Chúc mừng! Bạn vừa xây dựng được client front-end cho Hacker News.

Bản sao trang Hacker News

Hi vọng bài viết hữu ích với các bạn!

Thứ Tư, 26/07/2023 16:29
51 👨 133
0 Bình luận
Sắp xếp theo