React State와 리렌더링의 이해에서 리렌더링이될때 state들은 스냅샷의 형태로 관리 되어 일반적인 방법으로는 state값을 여러번 연산할 수 없다는 걸 알고 있다. 이번 장에서도 이미 알고 있는 내용이지만 한번 정리하는 시간을 가져보았다. # Batching
리액트는 state 업데이트를 하기 전에 이벤트 핸들러의 모든 코드가 실행될 때까지 기다린다. 이를 batching이라고 하는데, 여러 개의 state 업데이트를 state queue에 순차적으로 저장한 뒤 일괄 처리하고 나서 리렌더링이 발생하는 것이다. 리액트는 이벤트 헨들러 안에 setState를 여러번 호출해도 각각 즉시 리렌더링을 하지 않기 떄문에 batching이라는 단어가 알맞게 느껴졌다.
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1); // 마지막 setter가 호출되기 전까진 리렌더링 하지 않음
이는 UI가 미완성된 상태로 사용자에게 보여지는 것을 방지하고 잦은 리렌더링을 줄여 더 빠르게 프로그램을 처리해 줄 수 있게 한다.
# 객체, 배열 State 업데이트
state를 변경할 땐 setter 함수를 호출하고 state 참조 변경이 감지가 되면 리렌더링이 유발된다. 이때 리액트는 이전 state 스냅샷과 새 state 참조를 하는데, 얕은 비교 (shallow equality)를 사용한다. 얕은 비교를 하기 떄문에 원시 데이터들은 두 state 값을 바로 비교할 수 있지만 객체나 배열 같은 참조(메모리 주소) 데이터들은 얕은 비교로 내부 값이 변경됨을 감지하지 못한다. 그렇기에 우리는 state의 불변성을 유지해야 한다.
왜 리액트는 state를 비교할 때 얕은 비교를 사용할까? 가장 큰 이유는 비용 문제라고 생각한다. 깊은 비교를 하려면 객채나 배열을 일일이 순회해서 비교를 해여하는데 이때는 O(n)의 시간 복잡도를 가질 것이다. 배열이나 객체의 깊이가 깊어질수록 비용은 크게 증가해 성능 저하를 유발할 수 있다. 띠리서 리액트팀은 불변성을 지킨다는 전제하에 얕은 비교 방식으로 설계하지 않았을까? 생각해본다.
객체 업데이트
객체 state를 업데이트할 땐 직접 변경하지 않고 항상 새로운 객체를 생성하여 state가 복사본을 사용하도록 한다. 객체의 프로퍼티를 변경해야 할 경우 … (전개 연산자)를 사용해 기존 state를 복사한 뒤 변경하고 싶은 프로퍼티를 수정하여 새로운 객체를 만든 후 setter 함수에 전달한다. 리액트 공식 문서에서는 이러한 과정을 단순화하기 위해 Immer와 같은 라이브러리 사용을 추천하고 있다.
객체 state를 깊게 설계하는 것을 권장되지 않는다. 하지만 불가피하게 사용해여한다면 Immer와 같은 라이브리러를 활용해 복잡하고 반복적인 코드를 줄여주는 방법을 고려할 수도 있을 것 같다.
배열 업데이트
배열 state도 객체와 똑같이 업테이트하면 된다. … (전개 연산자)를 사용해 복사본을 생성하고 이를 setter에 전달하여 불변성을 유지하면서 업데이트한다. 배열을 직접 변경하는 push, splice, sort 같은 메서드는 원본 배열을 수정하기 때문에 사용을 지양한다. 대산 filter, map, concat 등 새로운 배열을 반환하는 메서드를 사용해 복사본을 만든 뒤 setter에 전달하면 된다. 이렇게 하면 리액트가 참조 변경을 감지해 올바르게 리렌더링을 수핼할 수 있다.
최신 JECMAScript(ES2023)부터 배열 전용 Immutable 메서드들이 추가되었다. toSorted, toReversed, toSpliced, with 등 이제는 리액트에서 더 안전하고 직관적이게 배열 state를 업데이트할 수 있게 되었다.
# 마치며
이번 장을 통해 리액트의 batching 동작 원리와 얕은 비교 기반의 state 업데이트 방식을 다시 한번 확인 할 수 있었고, 특히 리액트가 왜 얕은 비교를 선택했는지 이유가 성능 비용과 밀접할 것이라는 점을 이해하게 되었다. 이미 알고 있는 것들을 정리하면서 단순히 개념을 다시 읽어보는 것에 그치지 않고 리액트의 설계 의도를 고민해보는 기회가 되었다. 앞으로도 당연한 것을 당연하게 생각하지 않고 고민해보는 개발자가 되고 싶다.