-
Todo App 최적화FrontEnd/React 2023. 11. 23. 22:00반응형
List 컴포넌트 분리하기
- components 이하 List.js 파일을 생성합니다.
- 함수형 컴포넌트를 생성합니다.
- Lists.js 파일에서 UI 부분과 함수를 List 컴포넌트로 이동합니다.
- Lists 컴포넌트에서 List 컴포넌트를 import하고 props를 내려줍니다.
- List 컴포넌트에서 props를 받아옵니다.
List.js
import React from "react"; const List = ({ id, title, completed, todoData, setTodoData, provided, snapshot, }) => { const handleClick = (id) => { let newTodoData = todoData.filter((data) => data.id !== id); setTodoData(newTodoData); }; const handleCompleChange = (id) => { let newTodoData = todoData.map((data) => { if (data.id === id) { data.completed = !data.completed; } return data; }); setTodoData(newTodoData); }; return ( <div key={id} {...provided.draggableProps} ref={provided.innerRef} {...provided.dragHandleProps} className={`${ snapshot.isDragging ? "bg-gray-400" : "bg-gray-100" } flex items-center justify-between w-full px-4 py-1 my-2 text-gray-600 border rounded`} > <div className="items-center"> <input type="checkbox" onChange={() => handleCompleChange(id)} defaultChecked={false} />{" "} <span className={completed ? "line-through" : undefined}>{title}</span> </div> <div className="items-center"> <button onClick={() => handleClick(id)}>x</button> </div> </div> ); }; export default List;
Lists.js
import React from "react"; import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd"; import List from "./List"; export default function Lists({ todoData, setTodoData }) { ... 생략 ... <Draggable key={data.id} draggableId={data.id.toString()} index={index} > {(provided, snapshot) => ( <List key={data.id} id={data.id} title={data.title} completed={data.completed} todoData={todoData} setTodoData={setTodoData} provided={provided} snapshot={snapshot} /> )} </Draggable> ... 생략 ... }
React.memo
현재 Todo App은 App, Lists, List, Form 컴포넌트로 구성되어 있습니다. 이렇게 컴포넌트를 분리한 이유는 재사용성을 위한 것이기도 하지만, 각 컴포넌트의 렌더링을 최적화하기 위해서이기도 합니다. 현재 Todo App은 Form에서 글을 타이핑 할 때 Form 컴포넌트와 그 State를 가지고 있는 App 컴포넌트만 렌더링 되어야 하는데, Lists 컴포넌트와 List 컴포넌트까지 렌더링 됩니다(콘솔 로그 or 리액트 확장 프로그램을 추가하여 확인이 가능합니다).
해당 문제는 React.memo를 적용하여 해결이 가능합니다. React.memo 적용은 간단하게 원하는 컴포넌트를 React.memo로 감싸면 됩니다.
List.js
const List = React.memo(({ id, title, ... 생략 ... }) => { ... 생략 ... });
Lists.js
const Lists = React.memo(({ todoData, setTodoData }) => { const handleEnd = (result) => { ... 생략 ... }; return ( ... 생략 ... ); });
React.useCallback
컴포넌트가 렌더링 될 때 그 안에 있는 함수도 다시 만들게 됩니다. 똑같은 함수를 컴포넌트가 리 렌더링 된다고 해서 계속 다시 만드는 것은 좋은 현상이 아닙니다. 그리고 만약 함수가 자식 컴포넌트에 props를 내려 준다면 자식 컴포넌트 또한 리 렌더링 하게 됩니다. 해당 문제는 React.useCallback을 적용하여 해결할 수 있습니다.
useCallback 적용은 useCallback 안에 콜백함수와 의존성 배열을 순서대로 넣어주면 됩니다. 함수 내에서 state, props가 있다면 의존성 배열에 추가해주면 됩니다. 다음은 useCallback으로 인해 todoData가 변하지 않는다면 새로 함수를 생성하지 않는 예제 코드입니다. 새로 생성되지 않기에 메모리에 새로 할당되지 않고 동일 참조 값을 사용하게 됩니다. 의존성 배열에 아무것도 없다면 컴포넌트가 최초 렌더링 시에만 함수가 생성되며 그 이후에는 동일한 참조 값을 사용하는 함수가 됩니다.
const handleClick = useCallback((id) => { // callback }, [todoData])
useMemo
Memoization이란 비용이 많이 드는 함수 호출의 결과를 저장하고 동일한 입력이 다시 발생할 때 캐시된 결과를 반환하여 컴퓨터 프로그램의 속도를 높이는 데 주로 사용되는 최적화 기술입니다.
function Component({ a, b }) { const result = compute(a, b) return <div>{result}</div> } // useMemo 적용 function Component({ a, b }) { const result = useMemo(() => compute(a, b), [a, b]) return <div>{result}</div> }
localStorage
localStorage에 todoData 값을 담아 페이지를 refresh 해도 todoData가 계속 남아 있을 수 있도록 해줍니다.
Todo App에서는 setTodoData를 이용해서 todoData State를 바꿔줄 때 localStorage에도 같이 바꿔주고, 초기 todoData를 가지고 올 때 localStorage에 저장된 todoData를 활용하면 됩니다.
localStorage 값 저장하기
setTodoData(newTodoData); localStorage.setItem("todoData", JSON.stringify(newTodoData));
localStorage 값 불러오기
const initialTodoData = localStorage.getItem("todoData") ? JSON.parse(localStorage.getItem("todoData")) : []; function App() { ... 생략 ... }
[참고 정보]
반응형'FrontEnd > React' 카테고리의 다른 글
Custom Hooks (useDebounce, useRef) (1) 2023.12.06 React Router DOM (0) 2023.12.01 react-beautiful-dnd (0) 2023.11.22 TailwindCSS (0) 2023.11.16 컴포넌트 분리 (1) 2023.11.16