과제

요구사항

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을 입력하고 전송할때에만 리렌더링을 주고싶어서 inputuseRef로 접근해보았다(순수 JavascriptDOM에 접근하듯이)

// 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 arraynow를 담아서 변할때마다 내부 함수를 실행시킨다
useEffect내부에서는 setIntervalnow를 변동시킨다

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

profile
😂그냥 직진하는 (예비)개발자

0개의 댓글