본 내용은 아래 강의에 대한 복습내용입니다.
https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8
memo는 useMemo와는 조금 다른 함수다.
useMemo가 값을 메모이제이션 했다면 memo는 컴포넌트 전체를 메모이제이션한다.
아래와 같은 화면은
오늘은부터 날짜가 표기되는 부분은 Header 컴포넌트다.
입력값을 받아 추가하는 부분은 Editor컴포넌트.
그 아래로는 List컴포넌트다.
이 때 체크를 누르면 앱 컴포넌트의 state인 todos에 변화가 생기고 이에 앱컴포넌트가 리렌더링된다.
이로 인해 전혀 영향을 받지않아도 될 헤더 컴포넌트도 불필요하게 리렌더링 된다.
헤더컴포넌트는 앱컴포넌트로부터 props를 받지 않기 때문에 리렌더링이 될 필요가 없는 것이다.
이때 전에 memo 함수를 아래와 같이 사용하면, Header컴포넌트는 리렌더링이 스킵된다.
import "./Header.css";
import {memo} from "react"
const Header = () =>{
return <div className="Header">
<h3>오늘은 😊</h3>
<h1>{new Date().toDateString()}</h1>
</div>
};
const memoizedHeader = useMemo(Header)
export default memoizedHeader;
// 두줄을 합쳐 그냥 export defalut memo(Header) 라 해도 된
그러면 이제 리스트 컴포넌트안의 TodoItem 컴포넌트를 봐보자
todos 배열의 하나하나 객체를 props로 받는다.
그렇다면 체크한 객체를 제외하고는 다른 객체를 감싸는 TodoItem컴포넌트는 리렌더링이 될 필요가 없다.
import { useState, useMemo } from "react";
import "./List.css";
import TodoItem from "./TodoItem";
const List = ({todos, onUpdate, onDelete}) =>{
const [search,setSerch] = useState("");
const onChangeSearch = (e) =>{
setSerch(e.target.value);
}
const getFilterdData = () =>{
if(search===""){
return todos;
}
return todos.filter((todo)=>
todo.content.toLowerCase().includes(search.toLowerCase())
);
}
const filteredTodos =getFilterdData();
//첫번째 콜백함수의 결과값을 그대로 반환하고, []의존성배열에 담긴 값이 바뀔 때만 리렌더링
const{totalCount, doneCount, notDoneCount} = useMemo(()=>{
const totalCount = todos.length;
const doneCount = todos.filter((todo)=>todo.isDone).length
const notDoneCount = totalCount - doneCount
return{
totalCount,
doneCount,
notDoneCount
}
},[todos]) //배열에 담긴 값이 없으면 제일 처음에만 수행 그러면 todos의 변화가 있어도 값이 안바뀌어지는 문제발생
return (
<div className="List">
<h4>Todo List ❤️</h4>
<div>total :{totalCount}</div>
<div>done : {doneCount}</div>
<div>notDone : {notDoneCount}</div>
<input
value={search}
onChange={onChangeSearch}
placeholder="검색어를 입력하세요" />
<div className="todos_wrapper">
{filteredTodos.map((todo)=>{
return <TodoItem
key={todo.id}
{...todo}
onUpdate={onUpdate}
onDelete ={onDelete}
/>
})}
</div>
</div>
);
};
export default List;
그래서 마찬가지로 memo를 쓴다.
import "./TodoItem.css"
import {memo} from "react"
const TodoItem = ({
id,
isDone,
content,
date,
onUpdate,
onDelete}) =>{
const onChangeCheckBox = () =>{
onUpdate(id)
}
const onClickDeleteButton = () =>{
onDelete(id)
}
return <div className="TodoItem">
<input
onChange={onChangeCheckBox}
readOnly
checked={isDone}
type="checkbox" />
<div className="content">{content}</div>
<div className="date">{new Date(date).toLocaleDateString()}</div>
<button onClick={onClickDeleteButton}>삭제</button>
</div>
}
export default memo(TodoItem)
그런데!
아래 첫번째 항목에만 체크를 6번 눌렀는데 동일하게 아래 항목도 리렌더링이 된다. memo를 썼음에도 불구하고...
그이유는 props로 받는 객체가 달라지진 않았지만 바로 onUpdate, onDelete 함수가 달라졌기 때문이다.
함수는 객체처럼 참조값을 가진다.
그래서 체크를 함으로써 앱컴포넌트가 리렌더링되고, 앱컴포넌트에 정의되어있는 함수들이 리렌더링되면서 다시 생기어 참조값이 변했기 때문에 props에 변화가 있다고 생각하고 리렌더링 시킨것이다.
그래서 사용하는 것이 useCallback이다.
useCallback은 함수를 메모이제이션하는 훅함수이다.
리턴값은 첫번째 인자값인 콜백함수 그 자체를 리턴한다.
그래서 이렇게 앱컴포넌트에 정의되어있던 함수를
const onCreate = (content) =>{
dispatch({
type : "CREATE",
data : {
id : idRef.current++,
isDone : false,
content: content,
date : new Date().getTime(),
}
})
}
const onUpdate = (targetId) =>{
dispatch({
type : "UPDATE",
targetId : targetId
})
}
const onDelete = (targetId) => {
dispatch({
type : "DELETE",
targetId : targetId
})
}
아래처럼 useCallback을 사용하여 바꿀 수 있다.
그러면 앱컴포넌트가 리렌더링되더라도 아래함수들은 변화하지않기 때문에
최종적으로 todoItem컴포넌트도 영향을 받지않게 된다.
const onCreate = useCallback((content)=>{
dispatch({
type : "CREATE",
data : {
id : idRef.current++,
isDone : false,
content: content,
date : new Date().getTime(),
}
})
},[])
const onUpdate = useCallback((targetId)=>{
dispatch({
type : "UPDATE",
targetId : targetId
})
},[])
const onDelete = useCallback((targetId)=>{
dispatch({
type : "DELETE",
targetId : targetId
})
},[])