일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- javascript
- localStorage
- 브라우저
- darknode
- calender
- todolist
- stopwatch
- Typescript
- DOM
- Project
- React
- 웹사이트
- Calculator
- Timer
- CSS
- html
- mini_project
- Today
- Total
이야기 정리
리액트로 유튜브 클론 코딩 - 다크모드, 로그인, 데이터 가져오기 본문
클론 코딩은 도움이 되지 않는다는 이야기가 있다. 그저 강의만 보고 따라한다면 크게 도움이 되지 않는다. 그러나 이미 잘 만들어진 사이트를 분석해 내 방식으로 코드를 짜보는 건 색다른 경험이며, 추후 실제 사이트를 제작할 때 도움이 된다.
첫 클론 코딩으로는 가장 유명한 사이트 중 하나인 유튜브를 선택했다.
만들어진 페이지 보기
https://clone-coding-youtube.netlify.app/
전체 코드는 아래에서 확인할 수 있다.
https://github.com/beren-105/Mini-Project/tree/main/4_youtube
제작기간
7일
사용할 것
- React
- Tailwind
- 유튜브 API
- Git & GitHub
목표
- 깃을 사용하여 작성한 코드를 깃허브에 올린다.
- 리액트에서 테일윈드를 사용해 반응형 웹과 다크모드를 만든다.
- 로그인 기능을 구현하여 로그인 전과 후의 변화가 드러나도록 한다.
- 메인 화면의 동영상은 유튜브에서 제공하는 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
'Project > React project' 카테고리의 다른 글
[리액트] 제주 관광사이트 만들기 (0) | 2023.03.05 |
---|