props
가 변경될 때state
가 변경될 때어떠한 값에 변화가 있을때만 새로운 값을 할당해주고 아닐경우 memoized
된 값 (이전 값) 을 불러오는 Hooks
성능 최적화하는데 쓰인다
const average = useMemo(()=>{
return userList.reduce((acc,cur)=>{ return acc + cur.score /userList.length },0 )
},[userList]);
함수형 컴포넌트에서는 shouldComponentUpdate를 사용 할 수 없는데, 리액트 공식 문서에서는 그 대안으로 React.memo를 제시하고 있다.
단, 공식문서를 인용하면 아래와 같은 주의점이 있으니 잘 알아보고 사용하는 것이 좋겠다.
React.memo는 props 변화에만 영향을 줍니다. React.memo로 감싸진 함수 컴포넌트 구현에 useState, useReducer 또는 useContext 훅을 사용한다면, 여전히 state나 context가 변할 때 다시 렌더링됩니다.
useMemo와 비슷하다, useMemo는 리턴되는 값을 memoize했다면 useCallback은 함수 선언을 memoize 하는데 사용한다.
// 생성자 함수
<Component prop={new Obj("x")} />
// 객체 리터럴
<Component prop={{property: "x"}} />
이런 경우 새로 생성된 객체가 props로 들어가므로 컴포넌트가 리렌더링 될 때마다 새로운 객체가 생성되어 자식 컴포넌트로 전달된다.
props로 전달한 객체가 동일한 값이어도 새로 생성된 객체는 이전 객체와 다른 참조 주소를 가진 객체이기 때문에 자식 컴포넌트는 메모이제이션이 되지않는다.
const getResult = useCallback((score) => {
if (score <= 70) {
return { grade: "D" };
} else if (score <= 80) {
return { grade: "C" };
} else if (score <= 90) {
return { grade: "B" };
} else {
return { grade: "A" };
}
}, []);
return(
<div>
{users.map((user) => {
return (
<Item key={user.id} user={user} result={getResult(user.score)} />
);
})}
</div>
)
export default memo(UserList);
// Item.jsx
function Item({ user, result }) {
console.log("Item component render");
return (
<div className="item">
<div>이름: {user.name}</div>
<div>나이: {user.age}</div>
<div>점수: {user.score}</div>
<div>등급: {result.grade}</div>
</div>
);
}
export default Item;
// UserList.jsx
function UserList() {
{...}
return(
<div>
{users.map((user) => {
return (
<Item key={user.id} user={user} />
);
})}
</div>
)
export default memo(UserList);
// Item.jsx
function Item({ user }) {
console.log("Item component render");
const getResult = useCallback((score) => {
if (score <= 70) {
return { grade: "D" };
}
if (score <= 80) {
return { grade: "C" };
}
if (score <= 90) {
return { grade: "B" };
} else {
return { grade: "A" };
}
}, []);
const { grade } = getResult(user.score);
return (
<div className="item">
<div>이름: {user.name}</div>
<div>나이: {user.age}</div>
<div>점수: {user.score}</div>
<div>등급: {grade}</div>
</div>
);
}
export default memo(Item);
key값으로는 unique한 값이 들어와야하는데 index의 경우에는 중간에 다른 요소가 삽입되거나 할 경우
모든 index가 바뀌게 되고 그로 인해 key값이 변경되어 동일한 DOM Element를 보여주게 되어 예상치 못한 문제가 발생하게 된다
또한, 데이터가 key와 매치가 안되어 서로 꼬이는 부작용도 발생한다.
// 예시) 삭제 함수
const onRemove = useCallback(
id => {
setTodos(todos.filter(todo => todo.id !== id));
},
[todos],
);
// 예시) 함수형 업데이트 후
const onRemove = useCallback(id => {
setTodos(todos => todos.filter(todo => todo.id !== id));
}, []);
// 예시) 최적화 전(X)
//UserList.jsx
function UserList() {
{...}
return (
<div>
<input
type="text"
value={text}
placeholder="아무 내용이나 입력하세요."
onChange={(event) => setText(event.target.value)}
/>
{...}
</div>
);
}
export default UserList;
// 예시) 최적화 후(O)
//UserList.jsx
function UserList() {
{...}
return (
<div>
<input
ref={searchRef}
type="text"
placeholder="아무 내용이나 입력하세요."
onKeyUp={() => {
let searchQuery = searchRef.current.value.toLowerCase();
setTimeout(() => {
if (searchQuery === searchRef.current.value.toLowerCase()) {
setText(searchQuery);
}
}, 400);
}}
/>
{...}
</div>
);
}
export default UserList;
리액트를 좀 더 dep하게 파볼수록 알아야하고 아는게 더 많이 늘어가겠지만.
리액트는 단방향 하향식 데이터 흐름을 가지고 있어서, 부모 컴포넌트에서 자식 컴포넌트 방향으로 데이터(props,state)가 흘러간다.
이 데이터들의 변화는 컴포넌트를 리렌더링 시키는데, state는 그것이 선언된 컴포넌트 내에서 사용되고,
props는 부모 컴포넌트로부터 받은 데이터이다.
이 기본 구조를 숙지하면 최적화를 어떻게 해야할지 쉽게 알 수 있다.