본문 바로가기

프로젝트/웹페이지

웹 개발 1도 모르는 사람이 웹페이지 만들기 프로젝트 9일차

728x90
반응형

썸네일 - 뤼튼 생성 AI 이미지

 

 

9일차에는 멤버 탭에 연결 할 멤버 소개 페이지를 만들어 보겠습니다.

 

멤버 소개 페이지 미리보기

 

✍️ React Context 사용하기

 

새로운 멤버 페이지에서도 노션에 있는 데이터 베이스 데이터가 필요했습니다.

기존의 노션 데이터 베이스 데이터의 양이 꽤 많아서 여러 번 호출하는건 비효율적이라고 생각했습니다.

그래서 한 번의 호출로 받아온 데이터를 전역적으로 사용하는 방법에 대해 알아봤습니다.

그게 바로 React Context 였습니다.

 

React Context는 컴포넌트 트리 전체에 데이터를 전역적으로 공유할 수 있도록 도와주는 기능입니다. 

이를 통해 부모-자식 관계에 의존하지 않고 데이터를 전달할 수 있어 props drilling(여러 컴포넌트를 통해 데이터를 계속 전달하는 과정)을 줄여줍니다.

 

작동 원리

  • Context 생성 : createContext를 호출하여 Context 객체를 생성합니다. 이는 데이터를 전달하는 "통로" 역할을 합니다.
  • Provider 설정 : Provider는 Context에서 데이터를 공급(supply)하는 역할을 합니다. Context 데이터를 사용하는 컴포넌트는 반드시 Provider 내부에 있어야 합니다.
  • Consumer 사용 : Context 데이터를 읽는 컴포넌트는 useContext 훅을 사용하여 데이터를 가져옵니다.

 

app 디렉토리에 context 디렉토리를 만들고 js 파일을 생성 후 코드를 작성합니다.

"use client";

import React, { createContext, useContext, useState } from "react";

// context 생성
const MemberContext = createContext();

export function MemberProvider({ children }) {
    const [memberData, setMemberData] = useState(null); // 데이터와 수정하는 함수 정의

	// 공유 할 데이터를 value에 전달
    // children이 context 자식으로 포함되어 children 컴포넌트들이 MemberContext에 접근 할 수 있음
    return (
        <MemberContext.Provider value={{ memberData, setMemberData }}>
            {children}
        </MemberContext.Provider>
    );
}

export function useMemberContext() {
    const context = useContext(MemberContext); // useContext 훅을 사용하여 저장된 데이터를 가져옴
    if (!context) {
        throw new Error("useMemberContext must be used within a MemberProvider");
    }
    return context;
}

그리고 앱 전체를 MemberProvider로 감싸줍니다.

// layout.js (root layout)

import { MemberProvider } from "../app/context/member-context";

export default function RootLayout({ children }) {
  return (
    <html suppressHydrationWarning lang="en">
      <body className={`${geistSans.variable} ${geistMono.variable} antialiased bg-primary`}>
        <ThemeProvider attribute="class">
          <MemberProvider>
            {children}
          </MemberProvider>
        </ThemeProvider>
      </body>
    </html>
  );
}

서버 사이드에서 넘겨준 memberData를 기본값으로 context에 넣어 줍니다.

// layout.js (component)

'use client'

import { useMemberContext } from "../context/member-context";

export default function Layout({ children, memberData }) {
  const { setMemberData } = useMemberContext();

  useEffect(() => {
    if(memberData){
      setMemberData(memberData);
    }
  }, [memberData, setMemberData]);
  
  // 생략
}

이제 멤버 페이지에서 context를 활용하여 데이터를 잘 가져오는지 확인해봅시다.

'use client'

import { useEffect, useState } from "react";
import { useMemberContext } from "../../context/member-context";
import { useParams } from "next/navigation";

export default function MemberPage() {
    const { id } = useParams(); // 비동기로 풀어서 사용
    const { memberData } = useMemberContext();
    const [member, setMember] = useState(null);

    useEffect(() => {
        if (memberData && id) {
            const foundMember = memberData.find(member => member.memberId === id);
            setMember(foundMember);
        }
    }, [id, memberData]);
    
    if (!member) {
        return <div>Loading...</div>;
    }

    return (
        <div>
            <h1>{member.memberName}님의 페이지</h1>
            <p>{member.goal}</p>
            <p className="text-gray-900">{member.position}</p>
            <p className="text-gray-900">총 {Math.floor(member.totalTime)}시간</p>
            <div className="w-40 h-40">
                {member.iconUrl && (
                    <img src={member.iconUrl} alt={`${member.memberName}의 아이콘`} />
                )}
            </div>
        </div>

    );
 }

 

 

Next.js 13이상에서는 params가 더 이상 바로 객체처럼 사용되지 않고 Promise로 반환된다는 경고가 뜹니다.

이를 해결하기 위해서 params를 직접 사용하는 대신 useParams 훅을 사용하여 비동기적으로 풀어줘야 합니다.

A param property was accessed directly with `params.id`. `params` is now a Promise and should be unwrapped with `React.use()` before accessing properties of the underlying params object. In this version of Next.js direct access to param properties is still supported to facilitate migration but in a future version you will be required to unwrap `params` with `React.use()`.

 

 

성공적으로 데이터를 가져왔다면 각 멤버 페이지마다 알맞게 출력되는 것을 확인할 수 있습니다!!!😎

 

 

⭐ 레이아웃 설정하기

 

새로 만든 멤버 소개 페이지도 기본적인 레이아웃을 설정해봅시다.

멤버 소개 페이지에 적용시킬 레이아웃은 member/[id] 디렉토리에 layout.js를 만들면 됩니다.

우선 멤버 소개 페이지 헤더는 기존의 헤더에서 제목과 테마 전환 버튼을 가져와 적용시켜보겠습니다.

'use client'

import Header from "../../components/member-header"

export default function MemberLayout({ children }) {
    return (
        <div className="bg-primary">
            <Header />
            <div>
                {children}
            </div>
        </div>
    );
}
import ThemeButton from "./theme-button";

export default function MemberHeader() {
    
    return (
        <header className="text-gray-600 body-font">
            <div className="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center">
                <a className="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0">
                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" className="w-10 h-10 text-white p-2 bg-yellow-500 rounded-full" viewBox="0 0 24 24">
                        <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path>
                    </svg>
                    <span className="ml-3 text-xl">멤버 소개 페이지</span>
                </a>
                <nav className="md:ml-auto flex flex-wrap items-center text-base justify-center">
                </nav>
                <ThemeButton />
            </div>
        </header>
    );
}

 

메인 레이아웃 부분은 https://tailblocks.cc/에서 아래의 이미지 컴포넌트 부분을 사용하겠습니다.

 

 

return (
    <section className="text-gray-600 body-font">
        <div className="container px-5 py-24 mx-auto flex flex-col">
            <div className="lg:w-4/6 mx-auto">
                <div className="flex flex-col sm:flex-row mt-10">
                    <div className="sm:w-1/3 text-center sm:pr-8 sm:py-8">
                        <div className="w-40 h-40 rounded-full inline-flex items-center justify-center">
                            {member.iconUrl && (
                                <img src={member.iconUrl} alt={`${member.memberName}의 아이콘`} />
                            )}
                        </div>
                        <div className="flex flex-col items-center text-center justify-center">
                            <h2 className="font-medium title-font mt-4 text-gray-900 text-lg">{member.memberName}</h2>
                            <div className="w-12 h-1 bg-yellow-500 rounded mt-2 mb-4"></div>
                            <p className="text-gray-900">{member.goal}</p>
                            <p className="text-gray-900">{member.position}</p>
                            <p className="text-gray-900">총 {Math.floor(member.totalTime)}시간</p>
                        </div>
                    </div>
                    <div className="sm:w-2/3 sm:pl-8 sm:py-8 sm:border-l border-gray-200 sm:border-t-0 border-t mt-4 pt-4 sm:mt-0 text-center sm:text-left">
                        <h2 className="text-xl font-semibold mt-4">소제목</h2>
                        <p className="text-gray-900">정보 1</p>
                        <h2 className="text-xl font-semibold mt-4">소제목</h2>
                        <p className="text-gray-900">정보 2</p>
                        <h2 className="text-xl font-semibold mt-4">소제목</h2>
                        <p className="text-gray-900">정보 3</p>
                        <h2 className="text-xl font-semibold mt-4">소제목</h2>
                        <p className="text-gray-900">정보 4</p>
                    </div>
                </div>
            </div>
        </div>
    </section>
);

 

그러면 미리보기 이미지처럼 나오는 페이지 모습을 확인 할 수 있습니다!

 

이제 홈페이지 모양새가 그럴싸해졌습니다.

앞으로는 팀원들과 의논 후 디테일한 부분을 수정해야겠습니다!

 

https://github.com/JoHyeonJi0408/Study-Introduction-Website/pull/3/commits/b304ce2c3ec9656ce1a918bfbe88618575dc958c

 

멤버 페이지 추가 by JoHyeonJi0408 · Pull Request #3 · JoHyeonJi0408/Study-Introduction-Website

 

github.com

728x90
반응형