이야기 정리

리액트로 유튜브 클론 코딩 - 다크모드, 로그인, 데이터 가져오기 본문

Project/React project

리액트로 유튜브 클론 코딩 - 다크모드, 로그인, 데이터 가져오기

jinhistory 2023. 2. 9. 14:54

클론 코딩은 도움이 되지 않는다는 이야기가 있다. 그저 강의만 보고 따라한다면 크게 도움이 되지 않는다. 그러나 이미 잘 만들어진 사이트를 분석해 내 방식으로 코드를 짜보는 건 색다른 경험이며, 추후 실제 사이트를 제작할 때 도움이 된다.

첫 클론 코딩으로는 가장 유명한 사이트 중 하나인 유튜브를 선택했다.

 

만들어진 페이지 보기

https://clone-coding-youtube.netlify.app/

전체 코드는 아래에서 확인할 수 있다.

https://github.com/beren-105/Mini-Project/tree/main/4_youtube

 

제작기간

7일

파일구조

 

사용할 것
  • React
  • Tailwind
  • 유튜브 API
  • Git & GitHub

 

목표
  1. 깃을 사용하여 작성한 코드를 깃허브에 올린다.
  2. 리액트에서 테일윈드를 사용해 반응형 웹과 다크모드를 만든다.
  3. 로그인 기능을 구현하여 로그인 전과 후의 변화가 드러나도록 한다.
  4. 메인 화면의 동영상은 유튜브에서 제공하는 API를 이용한다.

 

발생한 문제 (1) : 테일윈드 외 별도 style을 사용하지 않기


테일윈드로 레이아웃을 짠 다음 처음 시도한 것은 다크모드였다. 유튜브에서 다크모드 설정을 하기 위해서는 설정 버튼을 누르고, 나타난 설정탭 중에서 '기기테마' 버튼을 클릭 해 사용해야한다.

본래 계획은 const로 스타일을 선언하고 display: ${setSettiog ? block : hidden}으로 값을 변경할 생각이었다. 그러나 테일윈드만으로 만들기 위해서는 직접적으로 style 선언을 해선 안됐다. 특정 부분에서만 style을 선언하면 협업 시 혼란이 올 수 있기에 일관성있게 테일윈드만을 사용하고 싶었다.

 

해결

div를 hiddne으로 감추는 것이 아닌 null로 감추었다.

setting이 true일 시 세팅창의 내용이 보이고, 아닐 시 아무것도 아닌 null이 되게 만들었다. 그동안 null을 사용할 일이 없었는데, 리액트에서는 종종 이러한 방식으로 사용하고 있다.

function Setting(props) {
    const setting = props.setting
    return (
        <>
        {setting ?
            <div className="w-56 py-2 absolute botton-0 right-0 bg-white text-left rounded-xl shadow-2xl z-20">
                <ul>
                    <li><a className="block p-4 border-b" href="#">YouTube의 내 데이터</a></li>
                    <li className="flex justify-between">
                        <a className="block p-4" href="#">디자인: 기기테마</a>
                        <svg className="w-10 p-2" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false"><g mirror-in-rtl=""><path d="M9.4,18.4l-0.7-0.7l5.6-5.6L8.6,6.4l0.7-0.7l6.4,6.4L9.4,18.4z"></path></g></svg>
                    </li>
                </ul>
            </div>
        : null}
        </>
    )
}

다 끝난 지금 다시 생각해보면 삼항연산자에 전체를 넣는 것 보다는 아래와 같이 짜는 것이 더 효율적이다. 다음부터는 아래와 같은 방식을 더 애용해야겠다.

 

<div className={`w-56 py-2 absolute botton-0 right-0 ${setting ? 'block' : 'hidden' }`}>

 

 

발생한 문제(2) : 새로고침 시 다크모드 세팅이 초기화됨


1번 문제를 해결해 무사히 세팅창을 여는 기능을 만들 수 있었다. 그러나, useState를 사용할 시 새로고침하면 테마가 초기로 돌아와버린다. 분명 라이트모드에서 다크모드로 설정했는데, 새로고침하면 페이지가 다시 라이트로 돌아가는 것이다.

 

해결

localStorage를 사용해 문제를 해결했다.

useEffect를 사용해 localStorage에 기본적으로 theme를 설정한다. 그 뒤 useState에서 localStorage의 값을 읽어와서 저장된 값에 따라 라이트 모드와 다크 모드를 변경했다. 이 방법으로 새로고침 문제를 해결했다.

 

다크모드에 대해 다룬 상세한 글은 여기다.

function DarkMode(props) {
    const openSettion = props.openSettion
    const mode = props.mode

    
    const [theme, setTheme] = useState(localStorage.theme)
    const mainTheme = theme === 'dark' ? 'light' : 'dark'
    
    useEffect(() => {
        const root = window.document.documentElement
        root.classList.remove(mainTheme)
        root.classList.add(theme)
        localStorage.setItem('theme', theme)
    }, [theme, mainTheme])

    const [modeSetting, setModeSetting] = useState(mainTheme === 'light' ? true : false)

    function clickDarkmode() {
        setModeSetting(false)
        setTheme('dark')
    }

    function clickLoghtmode() {
        setModeSetting(true)
        setTheme('light')
    }

    return (
        <>
            {openSettion && mode ?
                <div className="w-72 absolute botton-0 right-0 bg-white text-left rounded-xl shadow-2xl z-20 overflow-hidden dark:bg-zinc-800">
                    <div className="flex items-center p-2 border-b dark:border-b-zinc-600">
                        <button className="hover:bg-zinc-200 rounded-full transition mr-2 dark:hover:bg-zinc-600" onClick={() => props.modeBack()}>
                            <svg className="w-10 p-2 dark:fill-white" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false"><g mirror-in-rtl=""><path d="M21,11v1H5.64l6.72,6.72l-0.71,0.71L3.72,11.5l7.92-7.92l0.71,0.71L5.64,11H21z"></path></g></svg>
                        </button>
                        <h4>디자인</h4>
                    </div>
                    <p className="text-xs text-zinc-400 p-4 pb-2">
                        이 브라우저에만 설정이 적용됩니다.
                        </p>
                    <div>
                        <label className="flex items-center px-2 my-2 hover:bg-zinc-200 dark:hover:bg-zinc-600">
                        <svg className={`w-10 p-2 mr-2 dark:fill-white ${mainTheme==='dark' ? 'invisible' : null}`} viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false"><g><path d="M9,18.7l-5.4-5.4l0.7-0.7L9,17.3L20.6,5.6l0.7,0.7L9,18.7z"></path></g></svg>
                        <input
                            checked={modeSetting}
                            onChange={clickDarkmode}
                            className="hidden"
                            type='checkbox'
                            />
                            <span>어두운 테마</span>
                        </label>
                        <label className="flex items-center px-2 my-2 hover:bg-zinc-200 dark:hover:bg-zinc-600">
                        <svg className={`w-10 p-2 mr-2 dark:fill-white ${mainTheme==='light' ? 'invisible' : null}`} viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false"><g><path d="M9,18.7l-5.4-5.4l0.7-0.7L9,17.3L20.6,5.6l0.7,0.7L9,18.7z"></path></g></svg>
                        <input
                            checked={modeSetting}
                            onChange={clickLoghtmode}
                            className="hidden"
                            type='checkbox'
                            />
                            <span>밝은 테마</span>
                        </label>
                    </div>
                </div>
            : null}
        </>
    )
}

 

 

발생한 문제(3) : 로그인 후 페이지 돌아가기


인증이된 라우터를 구현한다고 해도 로그인 후 따로 설정을 해주지 않으면 로그인 화면에서 멈추고 만다. Link 로 단순히 돌아가면 로그인이 적용되지 않는 문제가 발생했다. 이번에도 해결법은 간단했다. Link가 아닌 다른 것을 사용하면 됐다.

 

해결

useNavigate를 사용했다. 특정 작업 완료 후 페이지를 옮길 수 있는 Hook이다. 이에 대한 자세한 내용은 여기에 있다.

당장은 서버로부터 받은 비밀번호나 아이디가 없어 state를 사용하지 않았다.

function passwordSubmit(e) {
        e.preventDefault()
        const formData = new FormData(e.target)
        auth.signInPaddword(formData.get('password'))
        navigate('/')
    }

 

 

발생한 문제(4) : 코드 공개와 인증키


깃허브에 데이터를 보내며 발생한 문제는 인증키의 보안이다. 인증키를 그대로 적어보내면 개인정보가 노출될 수 있다. 인증키가 담긴 파일을 .gitignore에 넣자니 App에 담겨 할 수 없었다. 그렇다면 어떻게 인증키를 암호화 할 것인가?

 

해결

.env 파일을 만들어 해당 파일에 인증키를 올리고, .gitignore 파일에서 git이 .env를 관리하지 못하도록 했다.

.env 파일에 정보를 적을 때는 꼭 앞이 REACT_APP로 시작해야한다. JSX파일에서 사용하려면 {process.env.REACT_APP_인증키이름}으로 하고, HTML에서 사용할 시 src="주소key=%REACT_APP_인증키이름%"으로 설정하면 된다. 이때, .env 파일은 반드시 리액트 폴더 최상단에 두어야하고, .env는 기본 파일이기 때문에 .env.development 등 다른 파일들보다는 우선 순위가 떨어진다.

 

 

데이터 불러오기 :  게시글 올린 시간과 현재 시간 차 구하기


유튜브 API에서 데이터를 가져와 레이아웃을 만드는 과정은 순조롭게 진행됐다.

골머리를 썩힌 것은 의외로 시간차를 계산해 넣는 것이었다. 유튜브 영상을 보면 동영상이 올라온 시간이 3시 15분이라고 뜨는 것이 아닌, 1시간전 / 3일 전 / 13일전과 같이 현재 시간과의 차이가 나타난다.

 

시간 차를 나타내기 위해 유튜브 API에서 가져온 시간을 new Date로 선언하고 현재 시간에서 빼주었다. 그리고 if 문을 이용해 ~시간 전이라는 문구가 나타나게 했다.

const data = props.data.items
useEffect (()=>{
    days()
}, [])

function days(publishedAt) {

    let day = new Date()
    let atDay = new Date(`${publishedAt}`)

    const timeDay = Math.floor((day.getTime()-atDay.getTime())/(1000*60*60*24))
    const timeHour = Math.floor((day.getTime()-atDay.getTime())/(1000*60*60))
    const timeminute = Math.floor((day.getTime()-atDay.getTime())/(1000*60))
    const timesecond = Math.floor((day.getTime()-atDay.getTime())/(1000))
    
    if (0 < timeDay) {return (`${timeDay}일 전`)}
    else if (0 < timeHour) {return (`${timeHour}시간 전`)}
    else if (0 < timeminute) {return (`${timeminute}분 전`)}
    else if (0 < timesecond) {return (`${timesecond}초 전`)}
}

 

 

 

참조

https://hianna.tistory.com/697

https://choisuhyeok.tistory.com/m/95

https://talkwithcode.tistory.com/29

'Project > React project' 카테고리의 다른 글

[리액트] 제주 관광사이트 만들기  (0) 2023.03.05
Comments