React는 선언적인 방법으로 UI를 조작합니다. 개별적인 UI를 직접 조작하는 대신 컴포넌트가 있을 수 있는 다양한 상태를 기술하고 사용자의 행동에 따라 state를 변경합니다.
1. 컴포넌트의 다양한 시각적 state 확인하기
먼저 사용자에게 볼 수 있는 UI의 모든 상태를 시각화해야 합니다. 그리고 다양한 상태에 대한 목업을 만듭니다. (이런 목업들을 보통 storybook이라 부릅니다.)
- empty: form 비어있음 (제출 버튼이 비활성화)
- typing: form이 입력 완료 (제출버튼이 활성화)
- submitting: form이 비활성화되고 로딩 중
- success: 성공
- error: 실패
2. 무엇이 state 변화를 트리거하는지 알아내기
사람의 입력(버튼 클릭, input 입력 등)과 컴퓨터의 입력(네트워크에서 응답 도착 등)에 대한 응답으로 상태 변경을 트리거할 수 있습니다. 두 경우 모두 state 변수를 설정해야 UI를 업데이트할 수 있습니다.
- empty → typing : 텍스트가 비어있는지 여부 (사람의 입력)
- submitting: 제출 버튼을 클릭 여부 (사람의 입력)
- success: 네트워크 응답이 성공적으로 오는지 여부 (컴퓨터의 입력)
- error: 네트워크 요청이 실패하는지 여부 (컴퓨터의 입력)
3. useState로 표현하기
컴포넌트의 시각적 상태를 useState로 표현해야 합니다.
const [isEmpty, setIsEmpty] = useState(true);
const [isTyping, setIsTyping] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);
state에서 필수적인 것만 남기고 삭제해야 합니다.
- state가 모순이 있나요? 예를 들어 isTyping과 isSubmitting은 둘 다 true일 수 없습니다. 'typing', 'submitting', 'success'를 하나의 status로 합칠 수 있습니다.
- 다른 state 변수에 이미 같은 정보가 있나요? isEmpty는 입력값(answer)의 길이가 0인지 확인해도 알 수 있습니다.
- 다른 state 변수를 뒤집으면 동일한 정보를 얻을 수 있나요? success, error가 그렇습니다.
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success'
4. 이벤트 핸들러를 연결하여 state 설정하기
input 이벤트 핸들러에 setAnswer을 연결하고, 제출 버튼 click 이벤트에 setStatus 연결 등 이벤트 핸들러를 연결하여 state 설정합니다.
state 구조화 원칙
컴포넌트에 state를 작성할 때엔 사용할 state 변수의 수와 데이터 형태를 선택해야 합니다.
- 연관된 state를 그룹화합니다. (2개 이상의 state 변수를 항상 동시에 업데이트한다면, 단일 state 변수로 통합하는 것이 좋습니다.)
- state의 모순을 피합니다.
- 불필요한 state를 피합니다. (기존 state 변수에서 일부 정보 계산할 수 있다면, 컴포넌트의 state에 해당 정보를 넣지 않아야 합니다.)
- state 중복을 피합니다.
- 깊게 중첩된 state를 피합니다.
** 초기값만이 미러링 하고 싶은 경우가 아니라면 props를 state에 그대로 미러링 하는 것은 좋지 않습니다. 그 이유는 부모 컴포넌트가 나중에 다른 값을 전달하더라도 state 변수가 업데이트되지 않습니다. (관례에 따라 props이름을 initial, default로 시작하여 새로운 값이 무시됨을 명확히 하는 것이 좋습니다.)
function Counter({initialNumber}){
const [number, setNumber] = useState(initialNumber);
}
컴포넌트 간의 state 공유하기
두 컴포넌트의 state가 항상 함께 변경되기를 원할 때가 있습니다. 그럴 때 두 컴포넌트에서 state를 제거하고 가장 가까운 공통 부모로 이동한 다음 props를 통해 전달합니다. 이를 state 끌어올리기라고 합니다.
** 로컬 state를 가진 컴포넌트를 비제어 컴포넌트라고 부르고, 자체 로컬 state가 아닌 props에 의해 구동되는 컴포넌트를 제어 컴포넌트라고 합니다. 하지만 보통은 로컬 state와 props가 혼합되어 있습니다. 두 용어는 컴포넌트를 어떻게 설계하고 어떤 기능 제공하는지 이야기할 때 유용한 용어입니다.
** 각 고유한 state들에 대해 해당 state를 소유하는 컴포넌트를 선택합니다. 이 원칙을 단일 진리 원천(SSOT)입니다. 이는 모든 state가 한 곳에 있다는 뜻이 아니라, 각 state마다 해당 정보를 소유하는 특정 컴포넌트가 있다는 뜻입니다. 컴포넌트 간에 공유하는 state를 중복하는 대신 공통으로 공유하는 부모로 끌어올려서 필요한 자식에게 전달합니다.
state 보존하고 초기화하기
각 컴포넌트는 독립된 state를 가집니다. React는 UI 트리에서의 위치를 통해 각 state가 어떤 컴포넌트에 속하는지 추적합니다. 리렌더링마다 언제 state를 보존하고 또 초기화할지 컨트롤할 수 있습니다.
state가 컴포넌트 내부에 존재한다고 생각할 수 있지만 state는 실제로 React 안에 있습니다. React는 UI 트리에서 각 state를 올바른 컴포넌트와 연결합니다. React는 트리의 동일한 컴포넌트를 동일한 위치에 렌더링 하는 동안 상태를 유지합니다. 컴포넌트를 제거하면 React가 컴포넌트를 제거할 때 그 state도 같이 제거합니다. (같은 자리의 같은 컴포넌트에서 props만 다르더라도 React 관점에서는 같은 컴포넌트이기 때문에 state는 보존이 됩니다. 또한 다른 JSX를 return 하는 컴포넌트라도 같은 위치에 렌더링 되기 때문에 state를 보존이 됩니다.) 같은 위치의 다른 컴포넌트로 교체하는 경우는 컴포넌트 서브 트리의 state를 초기화합니다.
리렌더링 할 때 state를 유지하고 싶다면, 트리 구조가 같아야 합니다. 만약 동일한 위치에서 state를 초기화하려면 컴포넌트를 다른 위치에 렌더링 하거나 각 컴포넌트에 key로 명시적인 식별자를 제공합니다.
** React가 컴포넌트를 구별할 수 있도록 key를 사용할 수도 있습니다.
{isA && <Counter key="A"/>}
{isB && <Counter key="B"/>}
{isA ? (
<Counter key="A"/>
) : (
<Counter key="B"/>
)}
** 제거된 컴포넌트의 state를 보존하기 위해서는 state는 유지한 후 CSS로 안 보이게 하거나 state를 상위로 올려 부모 컴포넌트가 가지고 있게 하거나 React state 이외의 다른 저장소를 이용할 수 있습니다.
** React18 공식 문서 보고 정리한 내용입니다.
https://ko-react-exy5xcwjj-fbopensource.vercel.app/learn/reacting-to-input-with-state
State를 사용해 Input 다루기 – React
The library for web and native user interfaces
ko.react.dev
https://ko-react-exy5xcwjj-fbopensource.vercel.app/learn/choosing-the-state-structure
State 구조 선택하기 – React
The library for web and native user interfaces
ko.react.dev
https://ko-react-exy5xcwjj-fbopensource.vercel.app/learn/sharing-state-between-components
컴포넌트 간 State 공유하기 – React
The library for web and native user interfaces
ko.react.dev
https://ko-react-exy5xcwjj-fbopensource.vercel.app/learn/preserving-and-resetting-state
State를 보존하고 초기화하기 – React
The library for web and native user interfaces
ko.react.dev
'Dev > React.js' 카테고리의 다른 글
[React18] 9. context (0) | 2023.10.02 |
---|---|
[React18] 8. reducer (useReducer) (0) | 2023.10.01 |
[React18] 6. state 업데이트 (0) | 2023.09.29 |
[React18] 5. useState와 React의 렌더링 (0) | 2023.09.28 |
[React18] 4. 이벤트 핸들러 (0) | 2023.09.27 |
댓글