<리액트를 다루는 기술> 11장 컴포넌트 성능 최적화를 공부하면서 정리한 내용입니다.📚
중간에 모르는 것들은 따로 서치를 해서 공부했습니다.
App.js
function createBulkTodos() {
const array = [];
for (let i = 1; i <= 2500; i++) {
array.push({
id: i,
text: `할일 ${i}`,
checked: false,
});
}
return array;
}
리렌더링
됐기 때문입니다.🍧 컴포넌트가 리렌더링 되는 경우
1. 자신이 전달받은 props가 변경될 때
2. 자신의 state가 바뀔 때
3. 부모 컴포넌트가 리렌더링될 때
4. ForceUpdate 함수가 실행될 때
🍧 react.memo(함수)
- 넘어온 props가 변경되지 않으면 리렌더링 x
- 여기서는 todo, onRemove, onToggle이 바뀌지 않으면 리렌더링 되지 않음.
ToDoListItem.js
.
.
.
export default React.memo(ToDoListItem);
onToggle
, onRemove
함수는 배열 상태를 바꾸는(업데이트 하는) 과정에서 최신 상태의 todos
를 참조합니다.todos
배열이 업데이트 되면 onRemove
, onToggle
함수도 새롭게 바뀝니다.setXXX
에 현재 값을 파라미터로 넣어서 현재 값을 업데이트 하는 형식으로 바꿔줍니다.const [number, setNumber] = useState(0);
const onIncrease = useCallback(
()=> setNumber(prevNumber => prevNumber + 1),
[]
)
// 현재의 number = prevNumber
todos =>
를 넣어서 현재의 값을 알려줍니다.function App() {
const [todos, setTodos] = useState(createBulkTodos);
const [selectedTodo, setSelectedTodo] = useState(null);
const [insertToggle, setInsertToggle] = useState(false);
const nextId = useRef(4);
const onInsertToggle = useCallback(() => {
if (selectedTodo) {
setSelectedTodo((selectedTodo) => null);
}
setInsertToggle((prev) => !prev);
}, [selectedTodo]);
const onChangeSelectedTodo = (todo) => {
setSelectedTodo((selectedTodo) => todo);
};
const onInsert = useCallback((text) => {
const todo = {
id: nextId.current,
text,
checked: false,
};
setTodos((todos) => todos.concat(todo)); //concat(): 인자로 주어진 배열이나 값들을 기존 배열에 합쳐서 새 배열 반환
nextId.current++; //nextId 1씩 더하기
}, []);
const onRemove = useCallback((id) => {
setTodos((todos) => todos.filter((todo) => todo.id !== id));
}, []);
const onUpdate = useCallback(
(id, text) => {
onInsertToggle();
setTodos((todos) =>
todos.map((todo) => (todo.id === id ? { ...todo, text } : todo)),
);
},
[onInsertToggle],
);
const onToggle = useCallback((id) => {
setTodos((todos) =>
todos.map((todo) =>
todo.id === id ? { ...todo, checked: !todo.checked } : todo,
),
);
}, []);
🍧불변성: 기존의 값을 직접 수정하지 않으면서 새로운 값을 만들어 내는 것
- 불변성이 지켜지지 않으면 객체 내부의 값이 새로워져도 바뀐 것을 감지하지 못합니다.
이게 무슨 소린가 싶죠? 이를 이해할려면 얕은 복사가 무엇인지 알아야합니다.
const onToggle = useCallback((id) => {
setTodos((todos) =>
todos.map((todo) =>
todo.id === id ? { ...todo, checked: !todo.checked } : todo,
),
);
}, []);
{...todo}
새로운 객체를 만들어서checked: !todo.checked
필요한 부분을 교체해줬습니다. 스프레드 문법(... 문법)
를 사용하여 객체나 배열 내부의 값을 복사하는 게 얕은 복사입니다.🍧얕은 복사: 바깥쪽에 있는 값만 복사되고 내부의 값
ex)객체의 안에 있는 객체의 값
은 주소값만 가져온다.
(복사값과 참조값이 같이 있을 수 있음. 완전한 복사본이 아님)
예시)
const todos = [{id: 1, checked: true}, {id: 2, checked: true}];
const nextTodos = [...todos]; //얕은 복사
이 코드의 메모리 상태(?)를 대강 그림으로 그려봤습니다.
nextTodos[0].checked = false;
console.log(todos[0] === nextTodos[0])
todos[0] === nextTodos[0]
가 true
임을 알았습니다.nextTodos[0] = {
...nextTodos[0], //기존 값들은 유지
checked: false // 바꿔줄 값만 새로 할당
} // 새 객체를 할당
console.log(todos[0] === nextTodos[0]); //false
리스트 관련 컴포넌트를 작성할 때는 리스트 아이템과 리스트를 React.memo로 최적화해주는 게 좋습니다.
🍧 react-virtualized : 스크롤 되기 전에 보이지 않는 컴포넌트는 렌더링하지 않고 크기만 차지하게 해줌.
npm install react-virtualized --save
근데 제 쪽에선 의존성 에러가 나더라고요ㅠㅠ 다른 분 포스트를 보고 아래 명령어로 설치했습니다.
npm install react-virtualized --legacy-peer-deps
import
import {List} from 'react-virtualized'
function TodoList({ todos, onRemove, onToggle, onChangeSelectedTodo, onInsertToggle }) {
const rowRender = useCallback(
({index,key,style}) => {
const todo = todos[index];
return(
<ToDoListItem
todo={todo}
key={key}
onToggle={onToggle}
onRemove={onRemove}
onInsertToggle={onInsertToggle}
onChangeSelectedTodo={onChangeSelectedTodo}
style={style}
/>
)
},
[ todos, onRemove, onToggle, onChangeSelectedTodo, onInsertToggle ]
)
return (
<List
className='TodoList'
width={512} // 전체너비
height={513}// 전체 높이
rowCount={todos.length}//항목갯수
rowHeight={57} // 항목 높이(픽셀)
rowRenderer={rowRender} //항목을 렌더링할 때 쓰는 함수
list={todos}//배열
style={{outline:'none'}} //List에 기본 적용되는 outline 스타일 제거
/>
);
}
rowRenderer:
react-virtualized의 List 컴포넌트에서 각 TodoItem를 렌더링할 때 사용
List 컴포넌트의 props로 설정해야함!
인자로 index, key, style 값을 객체 타입으로 받아와서 사용.
List 컴포넌트
해당 리스트의 전체크기 & 각 항목의 높이 * 각 항목을 렌더링할 때 사용해야하는 함수(rowRenderer)을 props로 넣어줘야 함!!
function ToDoListItem({
todo,
onRemove,
onToggle,
onChangeSelectedTodo,
onInsertToggle,
style
}) {
const { id, text, checked } = todo;
return (
<div className="TodoListItem-virtualized" style={style}>
<li className="TodoListItem">
<div
className={cn('checkbox', { checked: checked })}
onClick={() => onToggle(id)}
>
{checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div
className="edit"
onClick={() => {
onChangeSelectedTodo(todo);
onInsertToggle();
}}
>
<MdModeEditOutline />
</div>
<div className="remove" onClick={() => onRemove(id)}>
<MdRemoveCircleOutline />
</div>
</li>
</div>
);
}
<div className="TodoListItem-virtualized" style={style}>
로 감쌉니다.성능 측정을 해볼까요~~??
render duration이 21.6ms까지 줄어들었습니다!
리액트 공부 진도를 빨리 빼야할텐데..ㅋㅋㅋ
틀린 부분 있으면 지적 부탁드립니다😄 감사합니다~
<리액트를 다루는 기술> - 김민준(벨로퍼트), 길벗
React-virtualized 설치방법
{즉문즉설:자바스크립트} Object의 깊은 복사 vs 얕은 복사 - 시니어코딩 유튜브