To-Do 앱 기획하기
Figma를 사용해 To-Do 애플리케이션의 프로토타입을 만들어보세요.
React 클라이언트 만들기
컴포넌트 만들기
목차
1. 초기 세팅
2. 과정을 기록하기
3. Error note
솔로 프로젝트로 진행했었던, Create-Chrome-App 코드를 React 코드로 리팩토링했다.
이번 프로젝트도 To-Do 앱 구현이 목표라서 리팩토링을 결정했고,
나의 리팩토링한 프로젝트 목표는 기존의 기능을 모두 리액트로 구현했을 때, 보존하는 것이었다.
기존 Create-Chrome-App의 주요 기능은 다음과 같았다.
기존 Create-Chrome-App의 Html 코드도 첨부하겠다.
코드샌드박스 왼 쪽에 있는 바를 오른쪽으로 슬라이드하면, 전체 코드를 볼 수 있다 !!
먼저, 오늘 과제를 받았을 때, 들었던 생각은 '코드를 먼저 구현하자' 였다.
한 번 구현했었고, 웹 사이트 배포도 끝난 상태라서 주어진 모델이라 봐도 무방했다.
따라서 피그마로 구현하는 것은 간단해 보이지만, 막상해보면 쉽지 않다는 것을 알고 있었고,
주어진 시간이 제한되어 있어서 피그마를 다룬다면 코드 구현을 다 못할 것 같았기 때문이다.
결국 수업시간 8시간정도 소요해서, 코드 리팩토링 구현은 완료했다.
구현 순서는 다음과 같이 진행했다.
필요한 package 파악 & 설치 진행
컴포넌트 분할 구상하기
React 코드로 리팩토링 & 컴포넌트 분할
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 메서드를 사용했다.
따라서 미리 지정해준 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이 완성하게 되었다.
💡 깃허브 배포 자동화