React를 이용하여 배경이미지 랜덤 변경, 인사, 시계 만들기
기존의 Javascript
를 이용해서 만들었던 프로젝트를, React로 포팅하는 과제이다
아마도 이를 수행하면 리액트의 장점과 왜 쓰는지에 대한 아이디어가 생길 것 같다
루트 디렉토리(최상단)에는 index.html
이나 환경 설정에 관한 파일들이 몰려있고, 하위에 /env
, /src
폴더가 위치한다
/env
에는 숨기고 싶은 변수들을 담는다(API_KEY
와 같은)
/src
에는 리액트 파일들(진입점인 main.jsx
, 앱의 전체를 담당하는 App.jsx
)과 /assets
(이미지)들이 들어있다
react
├─ env
│ └─ .env
├─ src
│ ├─ App.jsx
│ ├─ Clock.jsx
│ ├─ Greeting.jsx
│ ├─ TodoList.jsx
│ ├─ Weather.jsx
│ ├─ assets
│ │ ├─ bg-bicycle.jpeg
│ │ ├─ bg-bunny.jpeg
│ │ ├─ bg-forest.jpeg
│ │ ├─ bg-japan.jpeg
│ │ ├─ bg-ocean.jpg
│ │ └─ bg-wall.jpeg
│ ├─ index.css
│ ├─ main.jsx
│ └─ reset.css
├─ index.html
├─ package-lock.json
├─ package.json
├─ .eslintrc.cjs
├─ .gitignore
├─ README.md
└─ vite.config.js
imgPath
들을 담은 배열을 정적인 변수로 선언하고, 앱에 접근할 때 index
를 랜덤으로 뽑아주면 배경이미지가 새로고침(혹은 접속)할 때마다 바뀌게 된다
useState
를 사용하지 않은 이유는, 앱의 구동중에 해당 상태를 변경할 일이 없을 것 같아서 useRef
를 사용했다
// App.jsx
import { useRef } from "react";
import Weather from "./Weather";
import Clock from "./Clock";
import Greeting from "./Greeting";
import TodoList from "./TodoList";
const IMG_PATHS = [
"src/assets/bg-bicycle.jpeg",
"src/assets/bg-bunny.jpeg",
"src/assets/bg-forest.jpeg",
"src/assets/bg-japan.jpeg",
"src/assets/bg-ocean.jpg",
"src/assets/bg-wall.jpeg",
];
function App() {
const imgPathIdx = useRef(Math.floor(Math.random() * IMG_PATHS.length));
return (
<div
style={{
backgroundImage: `linear-gradient(rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.75)), url(${
IMG_PATHS[imgPathIdx.current]
})`,
}}
>
<Weather />
<Clock />
<Greeting />
<TodoList />
</div>
);
}
export default App;
userName
이라는 변수를 useState
로 선언하고 초기값을 로컬스토리지로부터 가져온다form
을 보여주고 값을 입력받는다className
을 붙여주어서 디스플레이 여부를 제어한다userName
이 갱신되면 인사말이 보여진다
input
에서onChange
로 상태를 키보드를 입력할 때마다 갱신해주지 않은 이유는useState
의 상태가 변경되면 그 때마다 리렌더링이 발생하기 때문에form
을 입력하고 전송할때에만 리렌더링을 주고싶어서input
을useRef
로 접근해보았다(순수Javascript
로DOM
에 접근하듯이)
// Greeting.jsx
import { useRef, useState } from "react";
const Greeting = () => {
const [userName, setUserName] = useState(localStorage.getItem("userName"));
const userNameInp = useRef("");
const onSubmitUserName = (e) => {
e.preventDefault();
const enteredUserName = userNameInp.current.value.trim();
console.log(enteredUserName.length);
if (enteredUserName.length > 1) {
localStorage.setItem("userName", userNameInp.current.value);
setUserName(enteredUserName);
userNameInp.current.value = "";
} else {
alert("두 글자 이상의 성함을 입력해주세요(공백제외)");
}
};
return (
<section id="greeting">
<form
className={"login-box" + (userName ? " hide" : " ")}
onSubmit={onSubmitUserName}
>
<div className="input-box">
<input type="text" id="userName" ref={userNameInp} />
<label htmlFor="userName">이름을 입력해주세요</label>
</div>
</form>
{userName ? <h1>{userName}님 안녕하세요!</h1> : null}
</section>
);
};
export default Greeting;
now
라는 현재 시간을 담을 변수를 useState
로 선언하고 1초마다 갱신 해준다
useEffect
에서 dep array
에 now
를 담아서 변할때마다 내부 함수를 실행시킨다
useEffect
내부에서는 setInterval
로 now
를 변동시킨다
useEffect
내부에서의return
이 없다면?
리렌더링 될 때,setInterval
작동을 클리어 시키지 않으므로,setInterval
이 n번식 호출된다
// Clock.jsx
import { useEffect, useRef, useState } from "react";
const Clock = () => {
const [now, setNow] = useState(new Date());
const interval = useRef(null);
useEffect(() => {
// console.log(now);
interval.current = setInterval(() => {
setNow(new Date());
}, 1000);
return () => {
clearInterval(interval.current);
};
}, [now]);
return (
<section id="clock">
<h2>{now.toLocaleTimeString()}</h2>
</section>
);
};
export default Clock;
본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.