In React, both React.memo
and useCallback
are related to optimizing performance, but they serve different purposes and are used in different contexts.
React.memo:
React.memo
is used to prevent a functional component from re-rendering unless its props have changed.React.memo
when you want to ensure that the component only re-renders when its props change. This is particularly useful when rendering expensive components that rely on props that often remain the same.const MyComponent = React.memo(function MyComponent(props) {
// component implementation
});
useCallback:
useCallback
to memoize callback functions in your component. This is helpful when passing callbacks as props to highly optimized child components, or when a callback is used in dependency arrays for other hooks like useEffect
or useMemo
.const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
Key Difference:
React.memo
is about preventing re-renders of the component itself based on props changes.useCallback
is about memoizing a callback function so it doesn’t cause re-renders of the components that use it as a dependency or prop due to reference changes.Both tools help in optimizing performance but target different aspects of the rendering behavior.
const [number, setNumber] = useState(0);
// prevNumber refers to the current number state's value
const onIncrease = useCallback(
() => setNumber(prevNumber => prevNumber +1),
[],
);
useState의 가장 기본적인 사용법은 전에 배웠 듯 state 값을 직접 넣는 것이다.
setNumber(number+1)
다만 이렇게 하면 최신 상태의 number를 참조하는 함수들이 number가 바뀔 때마다 함수를 새로 생성하게된다.
state 값을 직접 넣지 않고 함수형으로 쓰면 이 현상을 방지할 수 있다.
setTodos 는 useState 에서 지정한 함수이다.
// BEFORE
setTodos(todos.concat(todo));
setTodos(todos.filter((todo) => todo.id !== id));
setTodos(
todos.map((todo) => todo.id === id ? { ...todo, checked: !todo.checked } : todo));
// BEFORE
setTodos(todos => todos.concat(todo));
setTodos(todos => todos.filter((todo) => todo.id !== id));
setTodos(todos =>
todos.map((todo) => todo.id === id ? { ...todo, checked: !todo.checked } : todo));
setTodos
사용할 때 그 안에 todos =>
만 붙혀주면 된다
import React, { useReducer, useRef, useCallback } from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
function createBulkTodos() {
const array = [];
for (let i = 1; i <= 2500; i++) {
array.push({
id: i,
text: `할 일 ${i}`,
checked: false,
});
}
return array;
}
function todoReducer(todos, action) {
switch (action.type) {
case 'INSERT': // 새로 추가
// { type: 'INSERT', todo: { id: 1, text: 'todo', checked: false } }
return todos.concat(action.todo);
case 'REMOVE': // 제거
// { type: 'REMOVE', id: 1 }
return todos.filter(todo => todo.id !== action.id);
case 'TOGGLE': // 토글
// { type: 'REMOVE', id: 1 }
return todos.map(todo =>
todo.id === action.id ? { ...todo, checked: !todo.checked } : todo,
);
default:
return todos;
}
}
const App = () => {
const [todos, dispatch] = useReducer(todoReducer, undefined, createBulkTodos);
// 고유 값으로 사용 될 id
// ref 를 사용하여 변수 담기
const nextId = useRef(2501);
const onInsert = useCallback(text => {
const todo = {
id: nextId.current,
text,
checked: false,
};
dispatch({ type: 'INSERT', todo });
nextId.current += 1; // nextId 1 씩 더하기
}, []);
const onRemove = useCallback(id => {
dispatch({ type: 'REMOVE', id });
}, []);
const onToggle = useCallback(id => {
dispatch({ type: 'TOGGLE', id });
}, []);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
</TodoTemplate>
);
};
export default App;
const [todos, dispatch] = useReducer(todoReducer, undefined, createBulkTodos);
useReducer
를 사용할 때는 두 번째 파라미터에 초기 상태를 넣어준다. undefined
를 넣고 세 번째 파라미터에 초기상태를 만들어주는 함수인 createBulkTodos
를 써줌으로써 컴포넌트가 처음 렌더링 될 때만 createBulkTodos
가 호출된다RECALL useReducer
const array = [1,2,3,4,5]
const nextArrayBad = array //same address
const nextArrayGood = [...array] //different address
근데 여기서 주의할 점은 ...
스프레드 연산자를 쓰면 객체나 배열 내부의 값이 shallow copy로 복사된다. 즉 가장 바깥 쪽에 있는 값만 복사된다.
따라서 object/array 안에 nested 된 object/array까지 불번성 유지하는게 어렵다.
immer library 사용!
💡 RECALL
4 Things that trigger UPDATE
props changed
state changed
parent component re-rendered
force render by this.forceUpdate
React.memo(TodoList)
위에 코드는 프로젝트 성능에 전혀 영향을 주지 않는다! 왜냐 TodoList 컴포넌트의 부모컴포넌트인 App 컴포넌트가 리렌더링되는 유일한 이유가 todo 배열이 업데이트될 때이기 때문. 즉 지금 TodoList 컴포넌트는 불필요한 리렌더링이 발생하지 않는다.
하지만 App 컴포넌트에 다른 state가 추가되어 해당 값들이 업데이트될 때는 TodoList 컴포넌트가 불필요한 리렌더링을 할 수도 있음. 한마디로 미리 대비한거임.
list 컴포넌트에서 스크롤되기 전에 보이지 않는 컴포넌트는 렌더링하지 않고 크기만 차지하게끔 하는 라이브러리
FlatList
of ReactNative
본 후기는 [한글과컴퓨터x한국생산성본부x스나이퍼팩토리] 한컴 AI 아카데미 (B-log) 리뷰로 작성 되었습니다.
#한컴AI아카데미 #AI개발자 #AI개발자교육 #한글과컴퓨터 #한국생산성본부 #스나이퍼팩토리 #부트캠프 #AI전문가양성 #개발자교육 #개발자취업