To-do List: 일정 추가하기 / 검색하기
📌 전체 컴포넌트 구조
🔺 App - 전체
🔺 Header - 제목, 날짜 표시
🔺 Editor - Todo 입력칸
🔺 List - 전체 Todo (TodoItem의 모음)
🔺 TodoItem - 개별 Todo
- 일정 추가, 수정, 삭제가 일어나는 컴포넌트
- props를 바꿔가며 반복적으로 렌더링
📌 일정 추가하기 (+ 렌더링)
- 내용을 입력하고 추가 버튼을 누르면 todos state값을 변경해줘야 한다.
🔺 App.jsx
임시 데이터로 데이터 모델링
- 데이터가 어떤 형태로 있어야 하는지 설정
- 임시 데이터는 계속 렌더링할 필요 없으니 App 컴포넌트 밖에 선언
// 임시 데이터 (기본 데이터) - 계속 렌더링할 필요 없으니 App 컴포넌트 밖에 선언
const mockDate = [
{
id: 0, // index
isDone: false, // 체크박스
content: "React 공부하기", // 글 내용
date: new Date().getTime() // 글 발행 시간
},
{
id: 1,
isDone: false,
content: "자기소개서 작성하기",
date: new Date().getTime()
},
{
id: 2,
isDone: false,
content: "포르폴리오 수정하기",
date: new Date().getTime()
},
];
* App 컴포넌트 내부
Todo-Item을 저장할 state 선언
- 임시 데이터를 todos state초기값으로 설정
const [todos, setTodos] = useState(mockDate);
Todo의 index 역할을 할 useRef 선언
👉 중복 글을 구분하기 위해
- 임시 데이터 id와 겹치지 않도록 설정
const idRef = useRef(3);
(Editor.jsx에서) Todo 입력값을 가져와서(props) 기존의 데이터 객체(임시 데이터)로 만드는 함수 생성
- 새로 부여한 index를 증가시켜서 id에 부여
- props로 받은 Todo 입력값(=content)을 키 content에 부여
state값 업데이트 하기
- spread 연산자(...)로 기존의 todos state 배열의 값들을 풀어헤치고 👉 위에서 받은 Todo 입력값 객체(newTodo)를 넣는다.
const onCreate = (content) => {
const newTodo = { // 이 데이터 객체를 todos state 배열에 추가해줘야 함
id: idRef.current++, // 새로 부여한 index를 증가시켜서 id에 부여
isDone: false,
content: content, // props로 받은 Todo 입력값을 content에 부여
date: new Date().getTime()
}
// state값 업데이트 하기
// spread 연산자(...)로 기존의 todos state 배열의 값들을 풀어헤치고 -> 추가할 newTodo 데이터를 넣는다.
setTodos([newTodo, ...todos])
}
return문
- Editor.jsx에 props로 onCreate() 함수 전달
- List.jsx에 props로 todos state 전달
return (
<div className='App'>
<Header/>
<Editor onCreate={onCreate}/>
{/* Editor.jsx에 props로 onCreate함수 전달 */}
<List todos={todos}/>
{/* List.jsx에 props로 todos state 전달 */}
</div>
)
🔺 Editor.jsx
- Todo 입력칸
App.jsx에서 onCreat 함수 받아오기(구조분해할당)
- onSubmit() 함수에서 실행됨
<input> 입력값을 저장할 state 선언
- 초기값은 빈 배열("")
<input> 입력값을 content state에 저장하기 위한 변경 함수 생성
- content의 state값을 <input> 입력값으로 변경하기 위해 변경 함수 이용
const Editor = ({onCreate}) => {
// <input> 입력값을 저장할 state 선언
const [content, setContent] = useState("");
// <input> 입력값을 content state에 저장하기 위한 변경 함수
const onChangeContent = (e) => {
setContent(e.target.value)
}
입력칸이 공백일 때 추가 버튼을 누르면 입력칸에 포커싱을 주기 위한 ref 선언
const contentRef = useRef();
Enter를 치면 Todo 등록하는 함수
- Enter 실행: e.keyCode === 13
- Enter를 누르면 onSubmit() 함수 실행
Enter를 치면 실행되는 함수
- 입력칸이 공백인데 '추가' 버튼을 누르면 입력칸 포커싱 & 추가 막기
- App.jsx의 onCreate() 함수에 인자값으로 content 전달하여 실행
(위에서 props로 onCreate 함수를 받아왔기 때문에 가능) - Todo 추가 후 content의 state값 초기화 (안하면 입력칸에 문자 남아있음)
// Enter를 치면 Todo 등록하는 함수
const onKeyDown = (e) => {
if(e.keyCode === 13) {
onSubmit();
}
}
// Enter를 치면 실행되는 함수
const onSubmit = () => {
// 입력칸이 공백인데 '추가' 버튼을 누르면 입력칸 포커싱 & 추가 막기
if (content === "") {
contentRef.current.focus();
return;
}
// App.jsx의 onCreate 함수에 인자값으로 content 전달하여 실행
// 위에서 props로 onCreate 함수를 받아왔기 때문에 가능
onCreate(content);
// Todo 추가 후 content state 초기화 (안하면 입력칸에 문자 남아있음)
setContent("");
}
return문
- <input> 입력값의 초기값(value)을 content("")로 설정
- 입력칸 포커싱을 위한 ref 연결(useRef)
- 값을 입력했을 때(onChange), <input> 입력값을 content state에 저장하기 위한 변경 함수 실행
- 사용자가 키를 눌렀을 때(onKeyDown), Enter를 치면 Todo를 등록하는 함수 실행
return (
<div className="Editor">
<input
ref={contentRef} // 입력칸 포커싱을 위한 ref 연결
onKeyDown={onKeyDown} // 사용자가 키를 눌렀을 때 동작
value={content} // <input> 입력값의 초기값을 content("")로 설정해둠
onChange={onChangeContent}
type="text"
placeholder="새로운 Todo..."
/>
<button onClick={onSubmit}>추가</button>
</div>
)
🔺 List.jsx
- 전체 Todo (TodoItem의 모음)
App.jsx에서 todos state 받아오기(구조분해할당)
map( ) 메서드를 통해 todos state 배열에 담긴 데이터를 리스트로 렌더링
- map( ) 메서드의 매개변수로 todo 전달 (todo엔 하나의 TodoItem 객체가 들어있음)
- 매개변수 todo를 spread 연산자(...)로 풀어헤쳐서 TodoItem.jsx에 props로 전달
- 컴포넌트 렌더링 시 모든 아이템 컴포넌트에 반드시 key props를 고유한 값으로 전달해줘야 한다. (key={})
✨ map( )
: 배열의 모든 요소에 콜백 함수 수행 👉 콜백 함수가 리턴한 반환값들을 모아서 새로운 배열로 반환 & 렌더링
- map() 메서드를 JSX에서 사용할 때는 콜백 함수가 HTML 요소를 반환하도록 설정할 수 있음
// App.jsx에서 todos state 받아오기(구조분해할당)
const List = ({todos}) => {
return (
<div className="List">
<h1>Todo List 🌱</h1>
<input type="text" placeholder="검색어를 입력하세요" />
<div className="Todos_wrapper">
<!-- map() 메서드를 통해 todos state 배열에 담긴 데이터를 리스트로 렌더링 -->
{todos.map((todo)=> {
return <TodoItem key={todo.id} {...todo}/>;
<!-- 매개변수 todo의 모든 데이터가 props로 TodoItem.jsx에 전달됨 -->
})}
</div>
</div>
)
}
export default List;
🔺 TodoItem.jsx
- 개별 Todo
List.jsx에서 todo 객체 안의 내용들 받아오기(구조분해할당)
- id, 체크박스(boolean), 내용, 날짜
props로 받아온 todo의 내용물 적용시키기
- 체크 박스 (checked={})
- 내용(content) 적용
- 날짜(date)를 Date 객체로 받아와서 Local Date로 변환(toLocaleDateString())
// List.jsx에서 todo 객체 안의 내용들 받아오기
const TodoItem = ({id, isDone, content, date}) => {
return (
// props로 받아온 todo의 내용물 적용시키기
<div className="TodoItem">
<input checked={isDone} type="checkbox" />
<div className="content">{content}</div>
<div className="date">{new Date(date).toLocaleDateString()}</div>
<button>삭제</button>
</div>
)
}
export default TodoItem;
📌 일정 검색하기
- 검색어 입력, 변경이 되면 리스트 컴포넌트가 리렌더링 되어야 한다. 👉 현재 검색어를 state로 보관
🔺 TodoItem.jsx
검색어를 저장할 state 선언
const [search, setSearch] = useState("");
검색어 입력 시 입력된 검색어로 state값을 변경하는 변경 함수 실행
const onChangeSearch = (e) => {
setSearch(e.target.value)
};
todos state 배열에서 현재 검색 결과의 값들만 필터링하기
- 검색어가 공백이면 전체 리스트(todo) 반환
- 검색어가 공백이 아니면 todo를 매개변수로 받아서(todo를 모두 순회) todo의 내용(content)에 검색어(search)가 포함되어있는 일정만 필터링해서 반환
- 대소문자 구분을 없애기 위해 검색어를 소문자로 변환해서 검색
const getFilteredData = () => {
// 공백이면 전체 리스트 반환
if (search === "") {
return todos;
}
// 공백이 아니면
// todo를 매개변수로 받아서(todo를 모두 순회) todo 내용에 검색어(search)가 포함되어있는 일정만 필터링해서 반환
return todos.filter((todo) =>
todo.content.toLowerCase().includes(search.toLowerCase())
// 대소문자 구분 X: 검색어를 소문자로 변환해서 검색
);
};
검색 필터링 함수를 변수에 담기 + 리렌더링 될때마다 호출
- 컴포넌트 내부에 선언
const filteredTodos = getFilteredData();
return문
- 필터링 된 일정을 렌더링할 수 있도록 map() 메서드를 검색 필터링 함수를 저장한 변수로 바꾼다.
return (
<div className="List">
<h1>Todo List 🌱</h1>
<input
placeholder="검색어를 입력하세요"
value={search}
onChange={onChangeSearch}
/>
<div className="Todos_wrapper">
{filteredTodos.map((todo) => { // 필터링 된 todos를 렌더링
return <TodoItem key={todo.id} {...todo} />;
})}
</div>
</div>
)
출처
한입 크기로 잘라먹는 리액트