todolist
timelapse

개발 타임랩스 은근히 재밌다..

리액트로 만든 첫번째 프로젝트, 투두리스트

항상 뭔가를 배우면 그걸로 프로젝트를 하나 만들어봐야(기왕이면 배포까지 해봐야) 직성이 풀리고 그 다음 주제로 넘어가야 직성이 풀린다. 이번에 리액트 컴포넌트를 배우고 (정말 간단한) TodoList를 구현해보았다.

파일 구성과 로직

핵심적인 파일은

1
2
3
App.js
CreateTodo.js
TodoList.js

다음과 같이 3개이다.

투두리스트를 추가, 토글, 삭제하는 함수는 App.js에 작성되어 있다. App.js에서 앱의 전체적인 구조(<CreateTodo> 컴포넌트와 <TodoList> 컴포넌트)를 렌더링한다.

핵심 코드 작성하기

useState(), useRef() 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
const [inputs, setInputs] = useState({
title: "",
});

const [todos, setTodos] = useState([
{
id: 1,
title: "리액트 혼내주기",
isImportant: false,
},
]);

const nextId = useRef(2);

앱에서 사용하는 변수를 useState()를 사용하여 선언한다. 투두리스트는 할 일(title)과 중요 여부(isImportant)로 구성되어 있다.

id는 1씩 증가시켜야 하는데, 비렌더링 객체로 관리해주기 위해 useRef()를 사용한다.

할 일 추가, 중요 여부 토글, 삭제 구현

onCreate()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const onCreate = () => {
if (title.length < 1) {
return;
} else {
const newTodo = {
id: nextId.current,
title,
};
setTodos([...todos, newTodo]);
setInputs({
title: "",
});
nextId.current += 1;
}
};

CreateTodo 객체에서 onCreate() 함수가 호출되면 입력된 title 값을 useState()의 set 메소드로 추가해준다. setTodo에서 spread 방식으로 배열에 요소를 추가해줬는데, concat() 메소드를 써도 상관없다. 할 일이 추가되었다면 setInputs() 메소드로 사용자가 입력한 값을 초기화한다. 그 후 nextId.current를 1 증가시킨다. (뒤에 .current를 붙이는 이유는 일반적인 숫자 객체가 아닌 useRef()로 선언된 객체이기 때문이다)

1시간동안 잡은 오류

idnextId로 정해주기 위해 계속 작성했으나 정상적으로 id가 선언되지 않고 계속 object 객체로 선언되었다. 바로 뒤에 .current를 붙이지 않아 생긴 오류였다. useRef()로 관리되는 객체는 접근시 항상 .current를 붙어야 한다.

onRemove()

1
2
3
const onRemove = (id) => {
setTodos(todos.filter((todo) => todo.id !== id));
};

Todo 객체에서 onRemove() 함수가 호출되면 id를 인자로 받아서 해당 id 값을 가진 요소를 제외한 배열을 필터링해서 setTodo() 메소드로 변수를 다시 지정한다.

onToggle()

1
2
3
4
5
6
7
const onToggle = (id) => {
setTodos(
todos.map((todo) =>
todo.id === id ? { ...todo, isImportant: !todo.isImportant } : todo
)
);
};

중요 여부를 바꿀 수 있는 onToggle() 객체이다. 위에서 작성한 onRemove() 함수와 작동방식은 비슷한데, id를 인자로 받아 해당 id 값을 가진 요소의 isImportant를 true, false로 바꿔준다.

TodoList 컴포넌트 작성

TodoList 컴포넌트로 todos 데이터가 들어오면 각각을 매핑하여 Todo 객체로 만들어 반환한다. 사실 이 부분에서 컴포넌트를 효율적으로 짜야 하는 이유를 체감했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function TodoList({ todos, onRemove, onToggle }) {
return (
<div className="todolist-wrapper">
{todos.map((todo) => (
<Todo
key={todo.id}
todo={todo}
onRemove={onRemove}
onToggle={onToggle}
/>
))}
</div>
);
}

리액트를 깃허브 페이지에 배포하기

리액트 프로젝트를 다 만들었다면 깃허브 페이지에 배포하는 것은 일도 아니다. 우선 프로젝트 코드를 깃허브 레파지토리에 커밋, 푸시한다.

이제 프로젝트에 gh-pages 패키지를 설치한다.

npm을 사용한다면,

1
npm i gh-pages --save-dev

yarn을 사용한다면,

1
yarn add gh-pages --dev

를 입력하면 된다. 이제 package.json 파일을 열어보자. 두가지만 하면 된다.

첫번째로, homepage를 추가한다. 그냥 끄트머리에

1
2
3
4
5
  "devDependencies": {
"gh-pages": "^3.1.0"
}, // 밑에서부터 참고해서 추가하면 된다
"homepage": "http://<깃허브아이디>.github.io/<레파지토리 이름>"
}

위와 같이 한 줄을 추가한다.

두번째로 scripts 항목에 deploypredeploy를 추가한다.

1
2
3
4
5
6
7
8
9
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
//밑에서부터 deploy, predeploy를 추가한다.
"deploy": "gh-pages -d build",
"predeploy": "yarn run build" //yarn <-> npm
},

predeploy에는 yarn, npm을 기호에 맞게 사용하자.

이제 설정은 모두 끝났고, 깃허브 페이지로 배포만 하면 정말 끝이다.

터미널을 열어

1
yarn run deploy # 또는 npm run deploy

를 입력하고 페이지로 접속해보자.

혹시나 깃허브 페이지에 접속하였는데 readme 파일이 뜬다면 깃허브의 레파지토리 설정으로 접속하여 GitHub Pages의 Source를 gh-pages 브랜치로 설정하고 새로고침하면 리액트 프로젝트가 정상적으로 표시된다.

마치며

정말 별 볼일 없는 할 일 리스트지만 하나를 배우면 써먹어보는 습관은 자부심을 느끼게 한다. 맨땅에서 코드를 작성해서 오류도 잡아보고 배포까지 해보면 잊을래야 잊을 수가 없다. 리액트는 정말 매력적이고, 나는 아직 멀었다. 더 노력해야겠다.

모든 코드는 Github에서 확인할 수 있다.