해커 뉴스는 기업가와 개발자 모두를 위한 사이트로, 컴퓨터 과학과 기업가 정신 영역을 중심으로 콘텐츠를 제공합니다.

해커 뉴스의 기본 디자인이 일부 개인에게 적합할 수 있지만, 다른 사람들은 더 시각적으로 만족스럽고 맞춤화된 반복을 갈망할 수 있습니다. 다행히도 유용한 API를 사용하면 자신만의 맞춤형 해커 뉴스 플랫폼을 만들 수 있습니다. 또한, 비슷한 버전의 해커 뉴스를 만들면 React 개발의 숙련도를 높일 수 있습니다.

프로젝트 및 개발 서버 설정

이 작업에 사용된 소스 코드는 GitHub 저장소를 통해 액세스할 수 있으며 MIT 라이선스 조건에 따라 무료로 사용할 수 있습니다.

웹사이트 스타일 지정에 대한 지침은 리포지토리에 첨부된 CSS 파일을 참조하세요. 이 프로젝트의 라이브 버전을 미리 보려면 제공된 데모 링크를 방문하세요.

이 작업에 필요한 패키징은 다음과 같이 구성됩니다:

React 라우터는 단일 페이지 애플리케이션 내에서 탐색을 관리하는 데 사용되며, 웹사이트의 여러 페이지를 탐색할 때 원활한 사용자 경험을 보장합니다.

HTMLReactParser는 애플리케이션 프로그래밍 인터페이스(API)에서 제공하는 HTML 데이터를 분석하고 해석하는 데 활용됩니다.

MomentJS는 API에서 얻은 날짜 및 시간 값을 관리하는 데 사용됩니다.

명령줄 인터페이스를 시작하고 다음 명령을 실행합니다:

 yarn create vite

또는 선호하는 패키지 관리자 대신 노드 패키지 관리자(NPM)를 사용할 수 있습니다. 위에 제공된 지침은 Vitebuild 도구를 활용하여 기본 프로젝트를 생성합니다. 이 작업에 식별자를 할당하고 프레임워크를 선택하라는 요청을 받으면 React를 선택하고 자바스크립트를 그 변형으로 지정하세요.

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

필요한 모든 패키지 설치를 완료하고 개발 서버를 시작하면 적절한 코딩 환경에서 프로젝트에 접속하여 “src” 디렉터리를 찾아주세요. 이 디렉토리 내에 “구성 요소”, “후크”, “페이지”라는 이름의 하위 폴더 3개를 생성합니다.

각 폴더에 다음 파일을 추가하세요: * 컴포넌트 폴더에 Comments.jsx. * Components 폴더의 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 파일에서 불필요한 코드를 모두 제거하여 기능적인 컴포넌트만 남도록 합니다.

 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'

‘/’, ‘/: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 커스텀 훅 생성

본 작업은 두 개의 애플리케이션 프로그래밍 인터페이스(API)를 사용합니다. 초기 API는 특정 분류 내의 기사 모음을 검색하는 작업을 수행하는 반면, 후속 API인 Algolia API는 주석과 함께 특정 기사를 가져오는 작업을 담당합니다.

이 글도 확인해 보세요:  JES에서 사운드를 임포트하고 재생하는 방법

`useFetch.jsx` 파일을 열고 다음 코드를 입력하세요: ”’자바스크립트 ‘react’에서 React, { useState, useEffect }를 가져옵니다; const apiUrl = ‘http://localhost:8080/api/user’; // API URL로 바꾸기 export const useFetch = (params) => { const [data, setData] = useState(null); useEffect(() => { fetch(`${apiUrl}${params.path}`) .then((response) => response.json()) .then((data) => setData(data)) .catch((error) => console.log(error)); }, [params.path]); 데이터를 반환합니다; }; “` 그러면

 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])

콜백 함수 내에 전달된 파라미터의 특성에 따라 지정된 두 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”으로 이동할 때마다 “ListPage” 구성 요소의 렌더링을 보장하는 솔루션이 요구됩니다. 이 목표를 달성하기 위해서는 필요한 컴포넌트와 모듈을 가져오는 것이 필수적입니다.

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

동적 매개변수의 데이터 유형을 확인하려면 먼저 기능 구성 요소를 정의하고 유형 변수를 할당하여 그 특성을 식별해야 합니다. 동적 매개변수가 없거나 정의되지 않은 경우 유형 변수의 기본값으로 ‘뉴스’로 대체할 수 있습니다. 궁극적으로 ‘useFetch’ 훅을 사용하여 외부 소스에서 관련 정보를 가져올 수 있습니다.

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

“loading”, “error” 또는 “data” 변수의 값에 따라 해당 JSX 코드를 생성하고 UI 컴포넌트에 렌더링합니다.

 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” 변수를 활용하여 해당 엔티티에 식별자를 할당합니다. 마지막으로, 디스트럭처링을 통해 반환된 응답을 이해하면서 “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>
}

중첩된 답글이 있는 댓글 섹션 렌더링

필요한 모듈 ‘parse’ 및 ‘moment’를 임포트하세요. 그런 다음 ‘Comments’라는 이름의 함수형 컴포넌트를 생성하고, 이 컴포넌트는 ‘commentsData’라는 제목의 소품 배열을 받습니다. 이 컴포넌트는 해당 배열을 순회하며 각 요소에 대한 노드 컴포넌트를 렌더링합니다.

 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 컴포넌트 아래에 그려집니다. 이 컴포넌트는 재귀 반복을 통해 댓글, 메타데이터, 모든 댓글(있는 경우)에 대한 응답을 표시합니다.

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

앞서 언급한 코드 블록은 commentData.text 변수에 저장된 HTML 데이터를 분석하는 작업을 파싱 함수에 맡기고, 각 댓글과 관련된 타임스탬프를 해석하고 fromNow() 메서드를 활용하여 사람이 읽을 수 있는 표현을 생성하는 작업은 모멘트 라이브러리에 속해 있습니다.

이 글도 확인해 보세요:  웹 개발을 위한 가장 인기 있는 8가지 백엔드 프레임워크

Navbar 컴포넌트 생성하기

Navbar.jsx 파일에서 react-router-dom 패키지에서 NavLink 모듈을 임포트합니다. 그런 다음, 서로 다른 카테고리로 이동하는 5개의 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>
}

해커뉴스 플랫폼의 프런트엔드 사용자 인터페이스를 성공적으로 구축한 것을 진심으로 축하드립니다.

클론 애플리케이션을 구축하여 React 스킬을 강화하세요

React를 활용하여 해커뉴스의 복제본을 구축하는 과정은 React에 대한 숙련도를 높일 뿐만 아니라 기능적인 싱글 페이지 애플리케이션을 작업할 수 있는 기회를 제공합니다. 또한 애플리케이션 내에서 게시물과 사용자 프로필을 모두 검색할 수 있는 기능을 통합하는 등 확장할 수 있는 범위가 넓습니다.

테스트되지 않은 방법을 사용하여 라우터를 처음부터 구축하는 대신 단일 페이지 애플리케이션(SPA)의 개발 프로세스를 간소화하는 전문가가 만든 사전 구축 솔루션을 활용하는 것이 더 유리할 수 있습니다.

By 최은지

윈도우(Windows)와 웹 서비스에 대한 전문 지식을 갖춘 노련한 UX 디자이너인 최은지님은 효율적이고 매력적인 디지털 경험을 개발하는 데 탁월한 능력을 발휘합니다. 사용자의 입장에서 생각하며 누구나 쉽게 접근하고 즐길 수 있는 콘텐츠를 개발하는 데 주력하고 있습니다. 사용자 경험을 향상시키기 위해 연구를 거듭하는 은지님은 All Things N 팀의 핵심 구성원으로 활약하고 있습니다.