// TodoList.jsx
import React, { useEffect, useState } from "react";
import AddTodo from "../AddTodo/AddTodo";
import Todo from "../Todo/Todo";
import styles from "./TodoList.module.css";
export default function TodoList({ filter }) {
const [todos, setTodos] = useState(readTodo);
const handleAdd = (todo) => setTodos([...todos, todo]);
const handleUpdate = (updated) =>
setTodos(todos.map((t) => (t.id === updated.id ? updated : t)));
const handleDelete = (deleted) =>
setTodos(todos.filter((t) => t.id !== deleted.id));
useEffect(() => {
localStorage.setItem("todos", JSON.stringify(todos));
}, [todos]);
const filtered = getFilteredItems(todos, filter);
return (
<section className={styles.container}>
<ul className={styles.list}>
{filtered.map((item) => (
<Todo
key={item.id}
todo={item}
onUpdate={handleUpdate}
onDelete={handleDelete}
/>
))}
</ul>
<AddTodo onAdd={handleAdd} />
</section>
);
}
function getFilteredItems(todos, filter) {
if (filter === "all") {
return todos;
}
return todos.filter((todo) => todo.status === filter);
}
function readTodo() {
console.log('readTodo');
const todos = localStorage.getItem("todos");
return todos ? JSON.parse(todos) : [];
}
상단에 맨 처음 todos의 state를 전달하는 부분에서
처음에는
const [todos, setTodos] = useState(readTodo());
라고 작성했다. 그런데 이렇게 작성하면
새로운 아이템이 추가되거나 업데이트 할 때마다 readTodo 함수가 다시 호출되면서 local storage에 있는 데이터를 다시 읽어와서 JSON을 parsing한다.
useState는 React에서 제공해주는 React Hook이다.
그래서 인자로는 초기값을 전달할 수 있다.
초기값을 0이라고 해도 컴포넌트 상태나 props가 변경되어서 이 함수 자체가 다시 호출이 될 때, 모든 함수의 내용이 다 호출이 된다.
그래서 우리는 이러한 불필요한 리렌더링을 방지하기 위해서 useMemo나 useCallback, useEffect 등을 사용한다.
useState를 쓰면 초기값이 다시 전달이 된다.
그러나, useState는 내부적으로 컴포넌트에 필요한 데이터를 기억하고 있다. 그래서 useState가 아무리 많이 호출되어도 예를 들어 채count가 늘어나는 컴포넌트 만들 때 초기값을 0으로 설정해도 +버튼을 누르면 state값이 업데이트 되어서 값이 증가된다. 증가된 값을 기억할 수 있는 이유는 useState 내부적으로 이 값들을 기억하고 있기 때문이다.
정리해보면, 컴포넌트가 리렌더링 될 때마다 useState도 다시 호출되어서 초기값이 다시 전달이 되는데, useState 내부적으로 저장된 값이 있다면(이미 업데이트 된 값이 있다면), 그 초기값을 무시하고 내부적으로 사용하고 있는 값을 사용하게 된다.
이렇게 함수를 호출해서 무언가 데이터를 읽어오거나, localStorage를 사용하거나 파일을 들고오거나, 네트워크 상에서 읽어오는 등의 일을 한다면 이 함수를 호출할 때마다 다시 읽어온다. 그래서 내부적으로 읽어온 값을 사용하지 않고 내부적으로 갖고 있는 값을 사용할 것이다.
그럼 UI상으로 업데이트 되지는 않겠지만, 여전히 네트워크에서 또는 파일에서 불필요하게 계속 초기값을 읽게 될 것이다.
이를 방지하기 위해서는 함수를 호출하는 경우라면, 특히 함수에서 무거운 일을 하는 경우라면 이렇게 함수를 호출한 값 자체를 전달하는 것이 아니라 이걸 콜백함수로 감싸주어야 한다.
따라서
const [todos, setTodos] = useState(() => readTodo());
이렇게 작성해주어야 한다! 이렇게 작성하면 컴포넌트가 mount될 때 딱 한번만 호출되고 아무리 상태가 바뀌어도 함수가 재호출 되지 않는다!
함수 자체를 계속 호출해서 값을 전달하는 것 대신에 콜백함수를 전달하는 것이다!!
그리고 이 코드는 또
const [todos, setTodos] = useState(readTodo);
이렇게 축약할 수 있는데, 인자와 호출하는 내용이 동일하다면 함수의 이름(참조값)만 전달해 줘도 똑같다!
useState(초기화하는함수) // 함수의 레퍼런스만 전달
useState(()=> 초기화하는함수()) // 위와 동일함, 단, 콜백함수를 만듦 (단점을 꼽자면, 불필요한 함수가 만들어 진다는 단점이 있음)