리액트로 Form을 다루다 보면 입력 중, 제출 중, 성공, 실패… 다양한 상태들이 얽혀 복잡해진다. 이번에 리액트 공식 문서 State를 사용해 Input 다루기를 읽으며 선언형 UI 설계의 핵심을 다시 정리해 보았다. # 명령형 vs 선언형 UI
처음 리액트를 배울 때 가장 어려웠던 부분이 바로 사고방식의 전환이었다. 기존의 바닐라 자바스크립트에서는 버튼을 클릭하면 이 요소를 숨기고, 저 요소를 보여주고.. 식으로 DOM을 직접 조작하는 명령형 방식에 익숙했기 때문이다.
// 명령형: 어떻게 (how) 할지에 집중
const handleSumbit = () => {
disabled(button);
show(spinner);
hide(errorMessage);
// 각각의 요소를 직접 조작
}
하지만 리액트는 지금 UI가 어떤 상태여야 하는지를 선언하는 방식이다. 리액트 공식문서에서는 명령형 프로그래밍은 택시를 탔을 때 기사님께 목적지를 얘기하지 않고 순간순간 어떤 길로 가는지 가이드하는 방식이라고 비유하며, 선언형 프로그래밍은 택시 기사님께 목적지만 알려드리는 방식으로 가는 길은 택시 기사님이 알아서 판단하고 도착지로 데려다주는 것이라고 비유한다. 비유가 정말 찰떡이라고 생각이 들었다!
// 선언형: 무엇을(what) 보여줄지에 집중
const Form = ({state}) => {
if(state === 'submitting') return <Spinner />;
if(state === 'success') return <SuccessMessage />;
// 현재 상태에 따른 UI 묘사
}
# UI를 선언적인 방식으로 생각하기
공식문서에서 제시하는 4단계 프로세스가 인상적이었다. 이 과정을 따르면 선언형 프로그래밍에 익숙하지 않더라도 복잡한 Form도 체계적으로 정리할수 있을 것이다.
1단계: 시각적 상태 파악하기
우선 form이 가질 수 있는 모든 state를 시각화 하는 것이다. 시각화란 모든 state를 선언해서 나열한다.
•Empty: 아무것도 입력하지 않은 상태
•Typing: 사용자가 입력 중인 상태
•Submitting: 제출 중인 상태 (로딩)
•Success: 성공한 상태
•Error: 에러가 발생한 상태
state를 파악할 땐 디자이너 처럼 생각하기라는 표현이 와닿았다. 로직을 구현하기 전에 각 상태의 UI를 먼저 그려보는 것이다.
2단계: 상태 변화 트리거 파악하기
form이 가질 수 있는 상태를 쭉 나열한 다음 각각의 상태를 변화 시키는 요인들을 분류해 볼 수 있다. 공식 문서에선 Human Input (텍스트 입력, 버튼 클릭)과 Computer Input(네트워크 응답, 타임아웃)으로 나누었다. 그 후 시각화를 해서 state의 변화의 흐름을 파악해 보는 것이다.
시각적으로 확인할 수 있게 그려보면 흐름을 정확하게 파악할 수 있고 구현 전 버그를 찾을 수도 있을 것 이다.
3-4단계: useState로 모델링 & 리팩토링
앞서 나열한 상태들을 useState로 선언해보자(모델링). state는 컴포넌트가 갖는 지역적 저장 공간이며 리액트가 변화를 감지할 수 있는 데이터이다. state가 많아지면 어떻게 될까? 리액트가 관리하는 데이터가 많아지면서 가독성이 떨이지고 신경써야할 데이터가 많기 때문에 개발자가 실수할 가능성도 높아질 것이다. 그래서 state는 적으면 적을 수록 좋다. state가 변할 때 마다 리렌더링도 발생하니 성능 저하의 원인이 될수도 있다!
모델링을 했다면 리팩토링으로 불필요한 state 변수를 제거해보자 Typing, Submitting, success를 하나로 합칠수 있고 empty 상태는 inputinput..length === 0 조건으로 체크할수 있다. 이렇게 우선 상태들을 나열하고 하나씩 합치거나 삭제하는 방식으로 리팩토링을 진행해보는 것이다.
# 왜 이 방식이 더 견고할까?
명령형 방식과 비교해보니 선언형의 장점이 몇가지 생각이 났다.
•새로운 상태 추가가 쉽다: 기존 로직을 건드리지 않고 새 상태를 추가할 수 있다. 명령형 방식은 하나의 상태를 추가하려면 조건을 추가하고 이에 따른 DOM 조작도 해줘야하는 불편함이 있을 것이다.
•버그 가능성이 줄어든다: 각 상태가 명확히 정의되어 있어 예상치 못한 UI 상태가 나타날 위험이 적다. 명령형 방식은 코드가 훨씬 많이 늘어나기 때문
•테스트하기 쉽다: 각 상태별로 독립적인 UI를 확인할 수 있다. 상태만 바꿔가며 테스트를 할 수 있기 때문
# 마치며
리액트를 사용하고 있지만 여전히 명령형 사고에 빠질때가 많은 것 같다. 특히 복잡한 상태관리가 필요한 상황에서는 이걸 어떻게 관리하지.. 부터 생각했던 것 같다. 이번 정리를 통해서 우선 어떤 상태들이 필요한지 나열하고 꼭 필요한 상태들을 먼저 정리한 후, 나머지 상태들을 합치거나 다른 방식으로 대체할 수 있는지 고민해보면 명령적 사고에 빠지지 않고 선언적 프로그래밍을 더욱 견고하게 설계할 수 있을 것이다. 단순히 리액트는 선언형이다 라고 아는 것과 실제로 선언형으로 생각하는 것은 다르다. 리액트가 이런 방식을 권장하는지 고민해보는 시간이 되었다.