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주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.