CRUD 기획 & 나만의 ToDo 애플리케이션 만들기

강성일·2023년 7월 6일
0
post-thumbnail

🔥 Bare Minimum Requirement



To-Do 앱 기획하기

Figma를 사용해 To-Do 애플리케이션의 프로토타입을 만들어보세요.

  • To-Do 앱에 맞는 CRUD 기능을 기획하세요.
    • 만약 간단한 메모장을 만든다면 꼭 필요한 CRUD 기능은 다음과 같을 것입니다.
      • Create : 메모 작성하기
      • Read : 메모 불러오기
      • Update : 메모 수정하기
      • Delete : 메모 삭제하기
    • 추가적인 기능을 기획해보고 싶다면, 두 세트의 CRUD 기능까지만 기획해 주세요. 이 이상 기획할 경우 프로젝트를 완성하기 버거울 수 있습니다.
  • 와이어프레임을 그리며 화면을 디자인해 보세요.
  • 와이어프레임을 프로토타입으로 고도화시켜서 Figma를 완성해 보세요.

React 클라이언트 만들기

  • Create-React-App을 활용해서 To-Do 앱을 작성할 프론트엔드 개발 환경을 구축하세요.
    • React-Router-Dom, Styled-Components, Redux 등 React 애플리케이션 개발에 필요한 라이브러리들을 설치해 주세요.

컴포넌트 만들기

  • React 개발 환경을 구축했다면, 애플리케이션에서 사용될 컴포넌트를 직접 구현해 보세요.
    • Component Driven Development를 직접 체험해보기 위해서 컴포넌트부터 구현해 봅니다.
    • 가능하다면 Section3에서 학습한 Styled-Components를 적극적으로 활용해 주세요.


💬 회고


목차

1. 초기 세팅

2. 과정을 기록하기

3. Error note



⚙️ 초기 세팅


솔로 프로젝트로 진행했었던, Create-Chrome-App 코드를 React 코드로 리팩토링했다.

이번 프로젝트도 To-Do 앱 구현이 목표라서 리팩토링을 결정했고,
나의 리팩토링한 프로젝트 목표는 기존의 기능을 모두 리액트로 구현했을 때, 보존하는 것이었다.

기존 Create-Chrome-App의 주요 기능은 다음과 같았다.

기존 Create-Chrome-App의 Html 코드도 첨부하겠다.



코드샌드박스 왼 쪽에 있는 바를 오른쪽으로 슬라이드하면, 전체 코드를 볼 수 있다 !!



📝 과정을 기록하기


먼저, 오늘 과제를 받았을 때, 들었던 생각은 '코드를 먼저 구현하자' 였다.

한 번 구현했었고, 웹 사이트 배포도 끝난 상태라서 주어진 모델이라 봐도 무방했다.

따라서 피그마로 구현하는 것은 간단해 보이지만, 막상해보면 쉽지 않다는 것을 알고 있었고,
주어진 시간이 제한되어 있어서 피그마를 다룬다면 코드 구현을 다 못할 것 같았기 때문이다.

결국 수업시간 8시간정도 소요해서, 코드 리팩토링 구현은 완료했다.



구현 순서는 다음과 같이 진행했다.

  1. 필요한 package 파악 & 설치 진행

  2. 컴포넌트 분할 구상하기

  3. React 코드로 리팩토링 & 컴포넌트 분할

  4. Build & Deploy


코드는 코드샌드박스로 모두 첨부하였으니, 각 순서마다 어떻게 진행하였는지
상세한 풀이 과정과 특이사항, 배포, 에러노트 순서대로 회고를 작성하면 되겠다 :)



필요한 package 파악 & 설치 진행

프로젝트 구상을 떠올리면, 가장 먼저 떠오르는 것이 package와 커맨드이다.
Creact React App 이라던가, npm install styled-components 이라던가.. 등이 있다.

'내가 가지고 있는 것에서 최대한을 다 하자' 라는 좌우명이 있는 나이기에..
최대한 이때까지 알고 있는 것과 배운 것을 총동원해서 사용하기로 마음 먹었다.

일단 따로 css 파일을 만들 필요 없게 만들어주는 styled-components가 생각이 났고,
기본적인 npm, 나중에 배포를 위한 gh-pages 가 생각이 나서 package 들을 모두 설치해줬다.



컴포넌트 분할 구상하기

컴포넌트는 당연히 기능마다 각각 하나씩 나눠져야 한다고 생각했다.

따라서 현재 시간, 로그인, 랜덤 명언, 현재 위치 & 날씨 기온 기능
이렇게 총 4개의 컴포넌트로 구성되었다. (배경화면은 컴포넌트에서 빠지게 되었다.)

각각 어떻게 코드를 구성했는지 소개하겠다.

1. Background

const MainSection = styled.div`
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  
  ...

  background: linear-gradient(
      to right,
      rgba(20, 20, 20, 0.1) 10%,
      rgba(20, 20, 20, 0.7) 70%,
      rgba(20, 20, 20, 1)
    ),
    url(https://source.unsplash.com/random/1920x1080);
  background-size: 100% 100%;
`;


배경화면은 리팩토링한 코드가 이상하게 작동되지 않았다.

추측해보니 이미지 경로에서 문제가 생긴 것 같아 기존 내장 메소드를 버리고,
홈 페이지에서 Link로 랜덤 이미지를 가져오도록 스타일 컴포넌트로 넣어주었다.


2. Clock.js

const Clock = () => {
  const [time, setTime] = useState("");

  useEffect(() => {
    const getClock = () => {
      const date = new Date();
      const hours = String(date.getHours()).padStart(2, "0");
      const minutes = String(date.getMinutes()).padStart(2, "0");
      const seconds = String(date.getSeconds()).padStart(2, "0");

      setTime(`${hours}:${minutes}:${seconds}`);
    };

    getClock();
    const interval = setInterval(getClock, 1000);

    return () => {
      clearInterval(interval);
    };
  }, []);


시간 컴포넌트는 메서드만 사용하면 되서 크게 어렵지 않았다.

확실히 Html 코드와 다르게, React에선 useState 를 사용하여 상태를 관리하니까
시간을 매 초마다 가져올 때 시간 부분만 업데이트하면 되므로,

사용하는 사용자 입장에서도 부드러운 user-flow를 제공받을 수 있을 것이고,
브라우저 입장에서도 DOM 조작이 최소화되어 부담이 줄어들게 될 것이다.

성능면으로 이득이고, 사용하는 사람 입장에서도 이득인 일타쌍피 상황이다 ✨


3. Login.js

const Login = ({ onLoginSubmit }) => {
  const [username, setUsername] = useState("");

  const handleSubmit = (event) => {
    event.preventDefault();
    onLoginSubmit(username);
  };

  return (
    <LoginFormSection>
      <form id="login-form" onSubmit={handleSubmit}>
        <div id="loginGuideMsg"></div>
        <LoginInput
          id="login-input"
          type="text"
          maxLength="15"
          placeholder="Please write your name"
          autoFocus
          required
          value={username}
          onChange={(event) => setUsername(event.target.value)}
        />
        <LoginButton type="submit"></LoginButton>
      </form>
    </LoginFormSection>
  );
};


React를 입문할 때, 사용하는 전형적인 예시인 로그인 기능이다.

현재로서는 정말 기본적인 로그인 기능만 다뤘지만,
최근에 배운 OAuth를 이용하여 로그인 인증 시스템을 넣어보고 싶다는 생각을 했다.

이 부분은 시간이 나거나, 추후에 다시 구현할 기회가 생긴다면 꼭 넣을 예정이다.


4. Quotes.js

const quotes = [
  {
    quote:
      "Any fool can write code that a computer can understand. Good programmers write code that humans can understand.",
    author: "Martin Fowler",
  },
  
  ...
  
  {
    quote: "Just Do it. Now.",
    author: "Kang Seong Il",
  },
];

const Quote = () => {
  const [quoteData, setQuoteData] = useState({ quote: "", author: "" });

  useEffect(() => {
    const randomQuote = quotes[Math.floor(Math.random() * quotes.length)];
    setQuoteData(randomQuote);
  }, []);

  return (
    <QuoteSection>
      {quoteData.quote}
      {quoteData.author}
    </QuoteSection>
  );
};


여기에서 Quote 함수 컴포넌트를 지정할 때, randomQuote 변수에서 Math 메서드를 사용했다.

  • Math.floor(Math.random() * quotes.length)을 사용하여 quotes 배열에서 임의의 인용구를 선택한다.
  • Math.random()은 0 이상 1 미만의 난수를 생성하고,
    이를 quotes.length와 곱하여 배열의 인덱스 범위 내에서 난수를 생성한다.
  • 그런 다음 Math.floor() 함수를 사용하여 소수점 이하를 버리고 난수를 정수로 변환한다.

따라서 미리 지정해준 quotes 객체 배열에서 랜덤하게 인용구를 가져올 수 있게 되는 것이다.
(배경화면도 마찬가지로 이미지 배열을 만들고, 똑같은 방식으로 했으나 실패했다.)


5. Weather.js

function Weather() {
  const [weatherData, setWeatherData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    const onGeoOk = (position) => {
      const API_KEY = process.env.REACT_APP_CLIENT_ID;
      const lat = position.coords.latitude;
      const lon = position.coords.longitude;
      const url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${API_KEY}&units=metric`;

      fetch(url)
        .then((response) => response.json())
        .then((data) => {
          setWeatherData(data);
        })
        .catch((error) => {
          setError(error.message);
        });
    };

    const onGeoError = (error) => {
      setError("Can't find you. No weather for you.");
    };

    navigator.geolocation.getCurrentPosition(onGeoOk, onGeoError);
  }, []);

  if (error) {
    return <div>{error}</div>;
  }

  if (!weatherData) {
    return <WeatherSection>Loading...</WeatherSection>;
  }

  return (
    <WeatherSection>
      🌈{" "}
      {`${weatherData.weather[0].main} / ${Math.floor(
        weatherData.main.temp
      )}°C`}
      <br />
      📍 {weatherData.name}
    </WeatherSection>
  );
}


useState 다음으로 약간 React의 꽃이라고 할 수 있는 API 가져오기이다.

최근에 배운 환경 변수를 만들어 키 값을 따로 저장하고 깃헙에 올라가는 부분은 가려주었다.

그 후, openweathermap 사이트에서 Open API를 fetch 로 가져왔다.

혹시 모를 에러처리(위치 추적이 불가능한 상황)까지 지정해준 다음에
return문 안에서 상태 변수인 weatherData 를 받아줬다.

이렇게 나의 To-Do 어플리케이션이 탄생하게 되었다.


6. App.js

const App = () => {
  const [username, setUsername] = useState("");
  const [todos, setTodos] = useState([]);
  const [todoInput, setTodoInput] = useState("");

  useEffect(() => {
    const savedUsername = localStorage.getItem("username");
    if (savedUsername) {
      setUsername(savedUsername);
    }
  }, []);

  const handleLoginSubmit = (newUsername) => {
    localStorage.setItem("username", newUsername);
    setUsername(newUsername);
  };

  const handleTodoSubmit = (event) => {
    event.preventDefault();
    if (todoInput.trim() === "") {
      return;
    }
    const newTodo = {
      id: Date.now(),
      text: todoInput,
    };
    setTodos((prevTodos) => [...prevTodos, newTodo]);
    setTodoInput("");
  };

  const handleTodoDelete = (id) => {
    setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
  };

  return (
    <MainSection>
      <Weather />
      <Clock />
      {!username ? (
        <Login onLoginSubmit={handleLoginSubmit} />
      ) : (
        <>
          <Greeting>Welcome~! {username}</Greeting>
          <ToDoSection>
            <FormSection>
              <form onSubmit={handleTodoSubmit}>
                <input
                  type="text"
                  placeholder="Please Write your tasks"
                  autoFocus
                  required
                  value={todoInput}
                  onChange={(event) => setTodoInput(event.target.value)}
                />
                <button type="submit"></button>
              </form>
            </FormSection>
            <TodoList>
              {todos.map((todo) => (
                <TodoItem key={todo.id}>
                  {todo.text}
                  <button onClick={() => handleTodoDelete(todo.id)}></button>
                </TodoItem>
              ))}
            </TodoList>
          </ToDoSection>
        </>
      )}
      <Quotes />
    </MainSection>
  );
};


App.js는 전체적으로 유저의 로그인 상태에 대해 useState 로 다루고 있다.

로그인이 완료되면, 바로 숨김 처리를 해야하는 살짝 까다로운 상황이라고 인지하였고,
map 으로 바로 뿌려주는 작업을 하므로, 컴포넌트가 아닌 App.js에서 다루게 되었다.

이렇게 나의 To-Do App이 완성하게 되었다.



💡 Error Note.


💡 깃허브 배포 자동화

  1. gh-pages 모듈 설치 (Terminal)
  2. 스크립트에 predeploy, deploy 추가 (package.json)
  3. gh-pages 디펜던시가 추가되어 있는지 확인 (package.json)
  4. 홈페이지 설정 추가 (package.json)
  5. 커밋 후, build, deploy 차례로 진행 (Terminal)
profile
아이디어가 넘치는 프론트엔드를 꿈꿉니다 🔥

0개의 댓글