리액트의 가장 큰 이점은 컴포넌트를 활용해 페이지를 구성하는 것이다. 물론 한 파일안에 모든 항목을 우겨넣어 만들 수 있지만 코드를 수정하거나 업데이트하는과정에서 복잡함이 있다. 그래서 제작하기에 앞서 어떻게 나누면 좋을지 구상을 먼저 해본다.

이 페이지에서 나눌수 있는 컴포넌트는
- Input 컴포넌트
- todolist를 출력하는 board 컴포넌트
- todolist의 항목 한개를 표현하는 item 컴포넌트
3개로 정리할 수 있다. 컴포넌트를 나누는 기준은 따로 있지는 않지만 재사용 횟수가 많거나 동적인 요소가 있을때 사용하면 편하다.

위에서 구상한 대로 만든 파일 구조이다. 3개의 컴포넌트를 Home.js에서 불러와 출력을 하는 방식으로 페이지가 구성되어 있다.
리액트를 활용하는게 목적이였기 때문에 css는 크게 신경쓰지 않고 작업을 했다. 간단하게 XML로 작성한 코드를 설명하겠다.
todoinput
return( <div className='input'> <input name='text' type='text' placeholder='해야할일' onChange={onChange} value={inputValue}></input> <button onClick={addItem}>입력</button> </div> );
input과 입력 버튼으로 구성되어 있다. onChange 함수를 통해 value의 변화를 감지하고 입력 버튼을 누르면 입력된 value를 전달한다.
todoboard
return( <div className="todoBoard"> <h1>Todo List</h1> {todoList.map((item)=> ( <TodoItem item={item} key={item.id} todoList={todoList} setTodoList={setTodoList}/> ))} </div> );
todoboard 컴포넌트는 아이템을 출력만하기 때문에 배열 렌더링 코드만 작성되어 있다. 이 부분은 배열 렌더링에서 다시 설명하겠다.
todoitem
return( <div className="todo-item"> <b>{item.inputValue}</b> <button onClick={()=>removeItem(item.id)}>삭제</button> <button onClick={()=>editItem(item.id)}>수정</button> </div> );
todoitem 컴포넌트는 input에서 입력한 value값을 출력하고 삭제/수정 버튼을 포함하고 있다.
위의 컴포넌트를 출력하는 Home.js는
return( <div className="main"> <TodoInput inputValue={inputValue} setInputValue={setInputValue} todoList={todoList} setTodoList={setTodoList} /> <TodoBoard todoList={todoList} inputValue={inputValue} setInputValue={setInputValue} setTodoList={setTodoList} /> </div> );
간단하게 컴포넌트를 출력만 한다.
컴포넌트로 파일을 나누어 관리하기 때문에 state를 활용해야 한다. todolist는 페이지가 하나이고 컴포넌트도 갯수가 적기때문에 전역변수 라이브러리를 사용하지 않도록 하겠다. state값으로 지정하는 것은 동적으로 변하는 값에 사용해야한다. 이 프로젝트에서 동적인 값은 list와 inputvalue이다.
state를 사용하는 곳은 모든 컴포넌트가 모이는 메인 페이지인 Home.js 이다.
import React from 'react'; import { useState } from 'react'; import TodoInput from '../components/todoinput'; import TodoBoard from '../components/todoboard'; function Home() { const [inputValue,setInputValue] = useState(''); const [todoList,setTodoList] = useState([]); return( <div className="main"> <TodoInput inputValue={inputValue} setInputValue={setInputValue} todoList={todoList} setTodoList={setTodoList} /> <TodoBoard todoList={todoList} inputValue={inputValue} setInputValue={setInputValue} setTodoList={setTodoList} /> </div> ); }; export default Home;
inputValue는 input값에 따라 변하기 때문에 기본으로 빈 문자열으로 지정해주고
todolist는 todoitem이 담기기 때문에 기본으로 빈 배열을 지정한다.
그리고 state값과 setter 함수를 각 컴포넌트로 넘겨주어 동적으로 관리한다.
todolist에서 가장 핵심적인 배열 렌더링을 해보겠다.
만약 동적인 리스트가 아닌 고정된 리스트라면 배열을 그냥 출력하면 된다.
const todoList = [ { id:1, inputValue:'밥먹기' }, { id:2, inputValue:'운동하기' }, { id:3, inputValue:'공부하기' } ]; return( <div> <b>{list[0].inputValue}<b> <b>{list[1].inputValue}<b> <b>{list[2].inputValue}<b> <div> );
todoitem은 반복되니까 컴포넌트로 return을 수정하면
return( <div> <todoItem value={list[0]}/> <todoItem value={list[1]}/> <todoItem value={list[2]}/> </div> );
하지만 이런 방식을 사용하면 고정적인 배열 출력은 가능하지만 동적으로 변하는 리스트를 감지해서 렌더링을 할 수가 없다.
리액트에서 동적인 배열을 출력할 때는 map함수를 사용한다.
map함수는 배열을 순회하며 함수를 적용해 요소를 변환하고 새로운 배열로 반환 하는 역할을 한다.
Map함수
return( <div className="todoBoard"> <h1>Todo List</h1> {todoList.map((item)=> (<TodoItem item={item}/>))} </div> );
todoList 배열의 각 item을 인자로 받아와서 todoItem 컴포넌트에 전달을 해준다. 렌더링을 실행하면

이러한 error가 발생한다. 리액트에서 map함수를 사용할때는 key값을 전달해 배열을 렌더링 해야한다. key값이 없어도 배열의 index값을 기본으로 받아서 렌더링은 가능하지만 효율적인 렌더링이 안된다.
예를들어 배열 중간에 추가되거나 삭제되는 항목이 있다면 모든 배열의 데이터가 수정되는 상황이 발생된다. 물론 배열이 짧다면 문제없지만 수만가지의 데이터를 포함한다면 비효율적인 작업이다.
하지만 key값으로 각 데이터의 고유값(id,number)값을 사용한다면 데이터가 변동될때 배열의 데이터 변화는 없이 id값에 의한 배열 추가가 이루어진다.

이미지 출처: 벨로퍼트 리액트 입문
그래서 todoList 배열에선 id를 지정해 고정된 값을 준다.
return( <div className="todoBoard"> <h1>Todo List</h1> {todoList.map((item)=> ( <TodoItem item={item} key={item.id}/> ))} </div> );
그러면 정상적으로 렌더링이 실시된다.
id값을 지정하는 방법과 배열 수정방법은 다음 포스트에서 다루겠다.
글에 이렇게 두서가 없다는 것이 원통하다. 다음 포스트는 좀더 정리해서 일맥상통하게 작성해야겠다.