포트폴리오 사이트 만들기 - React
소개
안녕하세요! 웹스토리보이입니다. 이제 하나씩 컴퍼넌트를 완성해 가봅시다. 7개의 컴포넌트를 하나씩 완성하면 기존처럼 멋있는 사이트가 완성될 것입니다. 처음에 만들었던 방식을 토대로 변화하는 과정과 재사용 및 효율적인 코딩방식으로 변화는 과정을 눈여겨 보면 좋습니다. 그럼 시작해보겠습니다.
인덱스
VITE SITE
- 1. 셋팅하기
                                    - 1_1. vite 설치하기
- 1_2. vite 폴더 정리하기
- 1_3. gsap/lenis 설치하기
- 1_4. git 연동하기
 
- 2. 레이아웃
                                    - 2.1 레이아웃 구조 만들기
- 2.2 메인 레이아웃 구조 만들기
- 2.3 CSS 셋팅하기
- 2.4 JavaScript 셋팅하기
 
- 3. 헤더 영역
                                    - 3.1 헤더 구조 잡기
- 3.2 헤더 디자인 설정
- 3.3 반응형 작업하기
- 3.4 메뉴 자바스크립트 설정
 
- 4. 인트로 영역
                                    - 4.1 인트로 구조 잡기
- 4.2 인트로 디자인 설정
- 4.3 반응형 작업하기
 
- 5. 스킬 영역
                                    - 5.1 스킬 구조 잡기
- 5.2 스킬 디자인 설정
- 5.3 반응형 작업하기
 
- 6. 사이트 영역
                                    - 6.1 사이트 구조 잡기
- 6.2 사이트 디자인 설정
- 6.3 반응형 작업하기
 
- 7. 포트폴리오 영역
                                    - 7.1 사이트 구조 잡기
- 7.2 사이트 디자인 설정
- 7.3 반응형 작업하기
- 7.4 스크립트 작업하기
 
- 8. 연락처 영역
                                    - 8.1 연락처 구조 잡기
- 8.2 연락처 디자인 설정
- 8.3 반응형 작업하기
 
- 9. 푸터 영역
                                    - 9.1 푸터 구조 잡기
- 9.2 푸터 디자인 설정
- 9.3 반응형 작업하기
 
- 10. 마무리
                                    - 10.1 스무스 효과주기
- 10.2 링크 연결하기
- 10.3 netlify에 배포하기
 
REACT SITE
- 1. 셋팅하기
                                    - 1_1. React 설치하기
- 1_2. React 폴더 정리하기
- 1_3. 라이브러리 설치하기
- 1_4. git 연동하기
 
- 2. 라우팅 및 컴퍼넌트
                                    - 2_1. 라우팅 설정하기
- 2_2. 컴퍼넌트 설정하기
- 2_3. SCSS 설정하기
 
- 3. 헤더 영역
                                    - 3_1. 헤더 구조잡기
- 3_2. 헤더 디자인 설정
- 3_3. 헤더 데이터 작업
- 3_4. 헤더 토글 메뉴 작업하기
 
3. 헤더 영역
3.1 헤더 구조잡기
                            우선 기본 HTML구조를 리액트 구조에 맞추어서 작업을 해야 합니다. 
                            <a href="#">은 <a href="/">로 변경해야 경고가 없어집니다.
                            또한 리액트에서는 class를 사용하지 않습니다. 
                            className으로 변경해야 합니다.
                        
import React from "react";
const Header = () => {
    return (
        <header id="header" role="banner">
            <div className="header__inner">
                <div className="header__logo">
                    <a href="/">portfolio<em>vite</em></a>
                </div>
                <nav className="header__nav" role="navigation" aria-label="메인 메뉴">
                    <ul>
                        <li><a href="#intro">intro</a></li>
                        <li><a href="#skill">skill</a></li>
                        <li><a href="#site">site</a></li>
                        <li><a href="#port">portfolio</a></li>
                        <li><a href="#contact">contact</a></li>
                    </ul>
                </nav>
                <div 
                    className="header__nav__mobile" 
                    id="headerToggle" 
                    aria-controls="primary-menu" 
                    aria-expanded="false" 
                    role="button" 
                    tabindex="0"
                >
                    <span></span>
                </div>
            </div>
        </header>
    );
};
export default Header;
3.2 헤더 디자인 설정
기존에 CSS 방식보다는 어려울 수 있지만 사용하다 보면 더 편함을 느낄 수 있습니다. 반응형도 따로 작업하지 않고 해당 영역에 동시에 작업을 하였습니다.
#header {
    @include position-fixed;
    z-index: 10000;
}
.header__inner {
    @include flex-between;
    background-color: rgba(116, 99, 99, 0.1);
    backdrop-filter: blur(15px);
    padding: 1rem;
    .header__logo {
        font-size: 0.9rem;
        text-align: center;
        text-transform: uppercase;
        line-height: 1;
        em {
            font-size: 10px;
            display: block;
            color: var(--black200);
        }
    }
    .header__nav {
        @media (max-width: 800px){
            display: none;
            &.show {
                display: block;
                ul {
                    display: block;
                    position: absolute;
                    right: 0;
                    top: 68px;
                    background-color: rgba(116,99,99,0.1);
                    backdrop-filter: blur(15px);
                    z-index: 10000;
                    min-width: 150px;
                    padding: 20px 0;
                    li {
                        display: block;
                        text-align: right;
                        a {
                            display: inline-block;
                            padding: 10px;
                        }
                    }
                }
            }
            &.show + .header__nav__mobile span::before {
                width: 20px;
            }
            &.show + .header__nav__mobile span::after {
                width: 20px;
            }
        }
        
        li {
            display: inline;
    
            a {
                text-transform: uppercase;
                font-size: 14px;
                padding: 14px;
                position: relative;
    
                &::before {
                    content: '';
                    width: calc(100% - 28px);
                    height: 1px;
                    background-color: var(--black);
                    position: absolute;
                    left: 14px;
                    bottom: 10px;
                    transform: scaleX(0);
                    transition: all 0.3s;
                }
                &:hover::before {
                    transform: scaleX(1);
                }
            }
        }
    }
    
    .header__nav__mobile {
        display: none;
        width: 40px;
        height: 40px;
        cursor: pointer;
        @media (max-width: 800px){
            display: block;
        }
        span {
            display: block;
            width: 40px;
            height: 2px;
            background-color: var(--black);
            margin-top: 19px;
            position: relative;
            &::before {
                content: "";
                width: 40px;
                height: 2px;
                background-color: var(--black);
                position: absolute;
                right: 0;
                top: 6px;
                transition: width 0.3s;
            }
            &::after {
                content: "";
                width: 40px;
                height: 2px;
                background-color: var(--black);
                position: absolute;
                left: 0;
                bottom: 6px;
                transition: width 0.3s;
            }
        }
    }
} 
3.3 헤더 데이터 작업
리액트에서 데이터를 따로 작업하는 이유는 코드를 더 구조적이고 관리하기 쉽게 만들기 위해서입니다. 데이터를 따로 작업하여 관리함으로써 코드의 가독성과 유지보수성을 향상시킬 수 있습니다. 규모가 작은 사이트를 라면 불편할 수도 있는 작업이지만 규모가 커질 수록 관리하기가 쉬어집니다.
const headerNav = [
    {
        title: "intro",
        url: "#intro",
    },
    {
        title: "skill",
        url: "#skill",
    },
    {
        title: "site",
        url: "#site",
    },
    {
        title: "portfolio",
        url: "#port",
    },
    {
        title: "contact",
        url: "#contact",
    },
];
                            반복적으로 처리되는 부분을 map() 메서드를 통해 작업해보겠습니다.
                            map()은 JavaScript의 배열 메서드 중 하나로, 배열의 각 요소를 변환하여 새로운 배열을 반환하는 함수입니다. 
                            React에서도 map()을 많이 활용하여 배열 데이터를 가지고 JSX를 생성하거나 처리하는 데 사용됩니다.
                        
const Header = () => {
    return (
        <header id="header" role="banner">
            <div className="header__inner">
                <div className="header__logo">
                    <a href="/">portfolio<em>vite</em></a>
                </div>
                <nav className="header__nav" role="navigation" aria-label="메인 메뉴">
                    <ul>
                        {headerNav.map((nav, key) => (
                            <li key={key}>
                                <a href={nav.url}>{nav.title}</a>
                            </li>
                        ))}
                    </ul>
                </nav>
                <div 
                    className="header__nav__mobile" 
                    id="headerToggle" 
                    aria-controls="primary-menu" 
                    aria-expanded="false" 
                    role="button" 
                    tabIndex="0"
                >
                    <span></span>
                </div>
            </div>
        </header>
    );
};
                            잠깐 map() 메서드에 대해서 알아보고 가겠습니다.
                            currentValue : 현재 배열 요소의 값, index : 현재 배열 요소의 인덱스, array : 원본 배열을 의미합니다.
                        
const newArray = array.map((currentValue, index, array) => {
    // 변환 로직을 작성하여 currentValue를 새로운 값으로 변환 후 반환
    return newValue;
});
map()을 사용하면 원본 배열의 각 요소들을 순회하면서 새로운 배열을 만들 수 있습니다. 즉, 기존 배열을 변형하지 않고 새로운 배열을 생성합니다
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map((number) => {
    return number * 2;
});
console.log(doubledNumbers); // [2, 4, 6, 8, 10]
배열 요소들을 JSX 엘리먼트로 변환하는 경우 (React에서 주로 사용하는 방법)
const names = ["Alice", "Bob", "Charlie"];
const nameList = names.map((name, index) => {
    return <li key={index}>{name}</li>;
});
3.4 헤더 토글 메뉴 작업하기
반응형이 되었을 때 메뉴를 클릭하면 서브 메뉴가 나오도록 작업하겠습니다. 자바스크립트에서 썼던 방법을 써도 되지만, 효율성있고 리액트에 더 친화적인 방법을 쓰겠습니다.
import React, { useState } from "react";
const headerNav = [
    {
        title: "intro",
        url: "#intro"
    },
    {
        title: "skill",
        url: "#skill"
    },
    {
        title: "site",
        url: "#site"
    },
    {
        title: "portfolio",
        url: "#port"
    },
    {
        title: "contact",
        url: "#contact"
    }
];
const Header = () => {
    const [show, setShow] = useState(false);
    const toggleMenu = () => {
        setShow((prevShow) => !prevShow);
    };
    return (
        <header id="header" role="banner">
            <div className="header__inner">
                <div className="header__logo">
                    <a href="/">portfolio<em>react.js</em></a>
                </div>
                <nav className={`header__nav ${show ? "show" : ""}`} role="navigation" aria-label="메인 메뉴">
                    <ul>
                        {headerNav.map((nav, key) => (
                            <li key={key}>
                                <a href={nav.url}>{nav.title}</a>
                            </li>
                        ))}
                    </ul>
                </nav>
                <div
                    className="header__nav__mobile"
                    id="headerToggle"
                    aria-controls="primary-menu"
                    aria-expanded={show ? "true" : "false"}
                    role="button" 
                    tabIndex="0"
                    onClick={toggleMenu}
                >
                    <span></span>
                </div>
            </div>
        </header>
    );
};
export default Header;
- const [show, setShow] = useState(false); : show라는 상태 변수를 생성하고 useState 훅으로 초기값을 false로 설정합니다. 이 변수는 모바일 메뉴의 표시 여부를 관리합니다. show가 true이면 모바일 메뉴가 나타나고, false이면 숨겨집니다.
- const toggleMenu = () => { ... }: toggleMenu라는 함수를 정의합니다. 이 함수는 모바일 메뉴를 표시하거나 숨기기 위해 show 상태를 변경하는 역할을 합니다. setShow 함수를 사용하여 show 상태를 업데이트합니다.
3. 마무리
이제 모든 코딩 준비가 되었습니다. 그럼 깃에 올립시다.
git add .
git commit -m "🥳 헤더영역 완료"
git push -u origin main
처음 배우는 개념들이 있으면 혼란스러울 수 있습니다. 하지만 몇 번 따라하면서 익히면 재미가 있습니다. 포기하지 말고 끝까지 잘 따라해주시면 감사하겠습니다. 그럼 이번 리액트도 끝까지 완성해봅시다.!
😘 이런 에러가 난다면...
                            리액트에서는 class 대신에 className을 사용합니다. 
                            class를 썼는지 확인해봐야 합니다.
                        
react-dom.development.js:86 Warning: Invalid DOM property `class`. Did you mean `className`?
                            리액트에서는 tabindex 대신에 tabIndex을 사용합니다. 
                            tabindex를 썼는지 확인해봐야 합니다.
                        
Warning: Invalid DOM property `tabindex`. Did you mean `tabIndex`?예제 목록
- 1. 포트폴리오 사이트 만들기 : Vite-Site : 셋팅하기
- 2. 포트폴리오 사이트 만들기 : Vite-Site : 레이아웃 설정
- 3. 포트폴리오 사이트 만들기 : Vite-Site : 헤더 영역
- 4. 포트폴리오 사이트 만들기 : Vite-Site : 인트로 영역
- 5. 포트폴리오 사이트 만들기 : Vite-Site : 스킬 영역
- 6. 포트폴리오 사이트 만들기 : Vite-Site : 사이트 영역
- 7. 포트폴리오 사이트 만들기 : Vite-Site : 포트폴리오 영역
- 8. 포트폴리오 사이트 만들기 : Vite-Site : 연락처 영역
- 9. 포트폴리오 사이트 만들기 : Vite-Site : 푸터 영역
- 10. 포트폴리오 사이트 만들기 : Vite-Site : 마무리
- 11. 포트폴리오 사이트 만들기 : React-Site : 셋팅하기
- 12. 포트폴리오 사이트 만들기 : React-Site : 컨퍼넌트 설정
- 13. 포트폴리오 사이트 만들기 : React-Site : 헤더 영역
- 14. 포트폴리오 사이트 만들기 : React-Site : 인트로 영역
- 15. 포트폴리오 사이트 만들기 : React-Site : 스킬 영역
- 16. 포트폴리오 사이트 만들기 : React-Site : 사이트 영역
- 17. 포트폴리오 사이트 만들기 : React-Site : 포트폴리오 영역
- 18. 포트폴리오 사이트 만들기 : React-Site : 연락처 영역
- 19. 포트폴리오 사이트 만들기 : React-Site : 푸터 영역
- 20. 포트폴리오 사이트 만들기 : React-Site : 마무리
- 21. 포트폴리오 사이트 만들기 : Vue-Site : 셋팅하기
- 22. 포트폴리오 사이트 만들기 : Vue-Site : 컨퍼넌트 설정
- 23. 포트폴리오 사이트 만들기 : Vue-Site : 헤더 영역
- 24. 포트폴리오 사이트 만들기 : Vue-Site : 인트로 영역
- 25. 포트폴리오 사이트 만들기 : Vue-Site : 스킬 영역
- 26. 포트폴리오 사이트 만들기 : Vue-Site : 사이트 영역
- 27. 포트폴리오 사이트 만들기 : Vue-Site : 포트폴리오 영역
- 28. 포트폴리오 사이트 만들기 : Vue-Site : 연락처 영역
- 29. 포트폴리오 사이트 만들기 : Vue-Site : 푸터 영역
- 30. 포트폴리오 사이트 만들기 : Vue-Site : 마무리
- 31. 포트폴리오 사이트 만들기 : Next-Site : 셋팅하기
- 32. 포트폴리오 사이트 만들기 : Next-Site : 컨퍼넌트 설정
- 33. 포트폴리오 사이트 만들기 : Next-Site : 헤더 영역
- 34. 포트폴리오 사이트 만들기 : Next-Site : 인트로 영역
- 35. 포트폴리오 사이트 만들기 : Next-Site : 스킬 영역
- 36. 포트폴리오 사이트 만들기 : Next-Site : 사이트 영역
- 37. 포트폴리오 사이트 만들기 : Next-Site : 포트폴리오 영역
- 38. 포트폴리오 사이트 만들기 : Next-Site : 연락처 영역
- 39. 포트폴리오 사이트 만들기 : Next-Site : 푸터 영역
- 40. 포트폴리오 사이트 만들기 : Next-Site : 마무리
 
										
									 
										
									 
										
									 
										
									
댓글