컴포넌트는 종종 상호작용의 결과로 스크린에 보여줄 것을 바꾸어야해요. 폼에 입력을 하면 입력창이 업데이트가 되어야하고, 사진 캐러셀에서 "다음"을 누르면 보여지는 이미지가 달라져야하며, "구매하기"를 누르면 장바구니에 상품이 담겨야해요. 컴포넌트는 현재 입력 값, 현재 이미지, 장바구니와 같은 것들을 "기억할" 필요가 있어요. 리액트에서는 이렇게 컴포넌트에 지정된 메모리를 상태라고 해요.
이 페이지에서는
-useState
훅을 사용하여 어떻게 상태변수를 추가하는지
- 어떤 쌍의 값을useState
훅에서 반환하는지
- 한 개 이상의 상태변수를 어떻게 추가하는지
- 상태는 왜 지역적으로 호출되는지
를 알아볼 거예요.
When a regular variable isn’t enough | 정규 변수로는 충분하지 않을 때
이 컴포넌트는 조각상 사진을 렌더링해요. "다음" 버튼을 누르면 index
가 1
에서 2
로 바뀌면서 다음 조각상을 보여줘요. 그러나 이것은 작동하지 않아요. (한 번 시도해보세요!)
handleClick
이벤트 핸들러는 지역 변수인 index
를 업데이트해요. 그러나 두 가지가 현재 보이는 값에서 업데이트 되는 것을 막아요.
- 지역 변수는 렌더링 동안에는 지속되지 않아요. 리액트가 이 컴포넌트를 두 번째로 렌더링할 때, 스트래치에서 렌더링해요. 지역 변수에 어떤 변화가 생길 것이라고는 고려하지 않아요.
- 지역 변수를 바꾸는 것은 렌더링을 발생시키지 않아요. 리액트는 새 데이터로 다시 컴포넌트를 렌더링해야한다고 생각하지 않아요.
새로운 데이터로 컴포넌트를 업데이트하려면 두 가지가 발생해야해요.
- 렌더링 중 데이터를 유지하세요.
- 리액트가 새 데이터로 컴포넌트를 렌더링(리렌더링)하도록 유발하세요.
useState
훅은 이 두 가지를 제공해요.
- 렌더링 사이에 데이터를 유지하기 위한 상태 변수
- 변수를 업데이트하고 리액트가 컴포넌트를 다시 렌더링하도록 유발하는 상태 세터(setter) 함수
Adding a state variable | 상태 변수 추가하기
상태 변수를 추가하려면 파일의 맨 위에서 리액트에서 useState
를 불러오는 구문을 작성하세요.
import { useState } from 'react';
그리고나서
let intdex = 0;
이 줄을
const [index, setIndex] = useState(0);
로 대체하세요.
index
는 상태 변수이고 setIndex
는 세터(setter) 함수예요.
[와 ] 문법은 배열 구조분해 구문이며 배열에서 값을 읽어올 수 있도록 만들어줘요. useState에서 반환된 배열은 항상 정확하게 2개의 아이템을 갖고 있어요.
handleClick
안에서 어떻게 이들이 같이 작동하는지를 보여줄게요.
function handleClick() {
setIndex(index + 1);
}
이제 "Next" 버튼을 눌러서 현재 조각상을 바꿔보세요.
Meet your first Hook | 첫 번째 훅을 만나보세요.
리액트에서 useState
를 비롯하여 "use
"로 시작하는 다른 함수들을 훅이라고 해요.
훅은 리액트가 렌더링하는 동안만 사용이 가능한 특별한 함수에요. (다음페이지에서 더욱 자세하게 알아볼게요.) 훅은 다른 리액트 특성에 '후킹'할 수 있게 만들어줘요.
상태는 이 특성들 중 하나일 뿐이지만 우리는 다음에 다른 훅을 더 만나볼 거예요.
함정use
로 시작하는 함수인 훅은 최상위 컴포넌트나 직접 만든 훅안에서 호출될 수 있어요. 조건문, 반복문 또는 중첩함수에서 훅을 사용할 수 없어요. 훅은 함수지만 컴포넌트가 필요에 맞춘 조건 없는 선언이라고 생각하면 좋을 거예요. 파일의 맨 위에서 모듈을 "불러오는" 방법과 비슷한 방법으로 리액트 특성을 최상위 컴포넌트에서 "사용"하세요.
Anatomy of useState
| useState
해부하기
useState
를 호출하면 리액트에게 이 컴포넌트가 무언가를 기억해야한다고 말해주는 것과 같아요.
const [index, setIndex] = useState(0);
이 경우에, 리액트는 index
를 기억해요.
노트
이 쌍에 이름을 짓는 컨벤션은const [something, setSomething]
과 같아요. 원하는 이름으로 지을 수도 있지만 컨벤션을 지키면 프로젝트를 이해하기가 더 쉬워져요.
useState
가 받는 유일한 인자는 상태 변수의 초기값이에요. 이 예시에서 index
의 초기 값은 useState(0)
에서 0
으로 정해졌어요.
컴포넌트를 렌더링할 때마다 useState
는 두 가지 값을 가진 배열을 여러분에게 줄 거예요.
- 여러분이 저장한 값을 가진 상태 변수(
index
) - 상태 변수를 업데이트하고 리액트가 컴포넌트를 다시 렌더링하도록 만드는 상태 세터 함수(
setIndex
)
그럼 이제 실제로 어떤 일이 일어나는지 알아볼게요.
const [index, setIndex] = useState(0);
- 컴포넌트는 처음에 렌더링돼요.
0
을useState
에index
의 초기값으로 전달하기 때문에[0, setIndex]
가 반환돼요. 리액트는 마지막 상태값으로0
을 기억해요. - 상태를 업데이트해요. 사용자가 버튼을 클릭하면
setIndex(index + 1)
을 호출해요.index
는0
이기 때문에setIndex(1)
가 될 거예요. 이는 리액트에서index
는1
임을 기억하고 다른 렌더링을 유발하도록 해요. - 컴포넌트는 두번쨰 렌더링을 해요. 리액트는 여전히
useState(0)
을 보고 있지만index
를1
로 설정했다는 것을 리액트가 기억하고 있기 때문에[1, setIndex]
을 반환해요. - 이런 식으로 반복돼요!
Giving a component multiple state variables | 컴포넌트에 여러개의 상태값 주기
한 컴포넌트에서 원하는 만큼 다양한 타입의 여러 상태 변수를 가질 수 있어요. 이 컴포넌트는 상태 변수를 2개 가지고 있고 숫자 타입의 index
와 불리안 타입의 showMore
은 "Show details"를 클릭하면 토글돼요.
위 예시의 index
와 showMore
처럼 상태 간 연관이 없다면 여러 상태 변수를 가지고 있어도 좋아요. 하지만 만약 두 상태 변수를 함께 바꾸는 일이 많다면 이 둘을 하나로 합치는 것이 더 나을 거예요. 예를 들어서 만약 많은 입력창을 가진 폼이 있다면 각 입력창 별로 상태 변수를 갖는 것 모다 객체 타입으로 하나의 상태 변수만 만드는 것이 더욱 편리할 거예요. 더 많은 팁을 얻고 싶다면 상태 구조 선택하기를 읽어보세요.
리액트는 어떤 상태를 반환할 것인지 어떻게 아나요?
더보기
useState
호출이 어떤 상태 변수를 참조하는지를 받지 않는다는 점을 알아차렸을 거예요.useState
에 전달되는 식별자가 없는데 리액트는 어떤 상태 변수를 반환해야하는지를 어떻게 알까요? 함수를 파싱하는 것과 같은 마법에 의존하는 걸까요? 아니요.
대신, 간결한 문법을 유지하기 위해서 훅은 같은 컴포넌트의 렌더링이 일어날 때마다 안정된 호출 순서에 의존해요. 실제로 이것은 잘 작동해요. 만약 위의 규칙("훅은 최상위에서만 호출되어야한다.")을 따른다면 훅은 항상 같은 순서로 호출 돼요. 더해서, 린터 플러그인은 대부분의 실수를 잡아내요.
내부적으로 리액트는 모든 컴포넌트의 상태쌍의 배열을 가지고 있어요. 또한 현재 쌍의 인덱스를 유지하고 렌더링 전에
0
으로 지정해요.useState
를 호출할 때마다 리액트는 다음 상태쌍을 주고 인텍스를 증가시켜요. 이 메커니즘에 대해서는 리액트 훅: 마법이 아니라 배열이에요.에서 읽을 수 있어요.
이 예시는 리액트를 사용하지 않아요. 하지만 내부적으로 어떻게
useState
가 작동하는지에 대한 아이디어를 줘요.
리액트를 사용하는데 이것까지 이해할 필요는 없지만 도움이 되는 모델일 수는 있어요.
State is isolated and private | 상태는 독립적이고 사적이에요.
상태는 화면에 보이는 컴포넌트 인스턴스에 지역적이에요. 즉, 만약 같은 컴포넌트를 2번 렌더링한다면 각 복사복은 완벽하게 독립된 상태에요! 이들 중 하나를 바꿔도 다른 것들에 영향을 미치진 않아요.
이 예시에서 이전에 보았던 Gallery
컴포넌트는 로직 변화 없이 두 번 렌더링돼요. 각 갤러리의 버튼을 눌러보세요. 그리고 그들의 상태가 독립적인지 알아보세요.
이것은 상태 변수가 모듈의 맨 위에서 선언하는 다른 일반적인 변수들과는 다른 점이에요. 상태는 특정한 함수 또는 코드 상의 위치에 구애받지 않지만 화면의 특정 위치에 "지역"적이에요. 여기서는 두개의 <Gallery />
컴포넌트를 렌더링했고 이 상태는 별개로 저장돼요.
또한 Page
컴포넌트가 Gallery
상태 또는 상태를 가지긴 했는지에 대해 어떻게 모르는지 생각해보세요. props와 달리 상태는 선언한 컴포넌트에 사적이에요. 부모 컴포넌트는 이것을 바꿀 수 없어요. 그래서 다른 컴포넌트에 영향을 주지 않고 어떤 컴포넌트에 상태를 추가하거나 그것을 제거할 수 있어요.
만약 두 갤럴리가 그들의 상태를 동기화하고 싶다면 어떨까요? 리액트에서 그렇게 하는 방법은 자식 컴포넌트에서 상태를 제거하고 공유하는 부모 중 가장 가까운 부모 컴포넌트에 상태를 추가하는 거예요. 다음 몇 페이지들은 단일 컴포넌트에서 상태를 조직하는데 초점을 두겠지만 우리는 컴포넌트 간 상태 공유하기에서 이 주제로 돌아올게요.
Recap | 요약
- 컴포넌트가 렌더링 중에 정보를 "기억"해야한다면 상태를 사용하세요.
- 상태 변수는
useState
훅을 호출하여 선언돼요. - 훅은
use
로 시작하는 특별한 함수에요. 훅은 상태와 같은 리액트의 특성에 후킹되도록 만들어요. - 훅은 조건 없이 무조건 호출되는 불러오기를 상기시켜줘요. .
useState
를 포함한 훅을 호출하는 것은 최상위 컴포넌트나 다른 훅에서만 유효해요. useState
훅은 한 쌍의 값을 반환해요. 현재 상태와 이 상태를 업데이트하는 함수가 반환되는 값이에요.- 하나 이상의 상태 변수를 가질 수도 있어요. 내부적으로 리액트는 그들의 순서에 따라 연결해요.
- 상태는 컴포넌트에 사적이에요. 만약 두 곳에서 상태를 렌더링한다면 각각의 복사본은 개별적인 상태를 가지고 있어요.
Challenges | 도전 과제
1. 갤러리 완성하기
마지막 조각상에서 "Next"를 누르면 코드는 충돌해요. 충돌을 방지하도록 로직을 수정하세요. 추가적인 로직을 이벤트 핸들러에 추가하거나 해당 행동이 불가능할 때 버튼을 사용할 수 없게 만들어서 이를 구현할 수 있어요.
충돌을 해결한 후에는 이전 조각상을 보여주는 "Previous" 버튼을 추가하세요. 첫 번째 조각상과 충돌하면 안돼요.
두 이벤트 핸들러에 방어조건을 넣고 필요할 때 버튼을 비활성화 시키면 돼요.
이제 hasPrev
와 hasNext
가 반환되는 JSX와 이벤트 핸들러 내부, 이 두군데 모두에서 어떻게 사용되는지를 잘 살펴보세요. 이 간편한 패턴은 이벤트 핸들러 함수가 렌더링 동안 선언된 그 어떤 변수도 모두 클로저로 사용하기 때문에 잘 작동해요.
2. 고정된 폼 입력을 수정하기
입력창에 입력을 하면 아무것도 보이지 않아요. 입력 값이 빈 문자열로 "고정"된 거예요. 첫번째 <input>
의 value
는 항상 firstName
변수랑 일치하고 두번째 <input>
의 value
는 항상 lastName
변수랑 일치해요. 이것은 맞아요. 두 입력창은 onChange
이벤트 핸들러를 갖고 있고 이는 사용자의 마지막 입력(e.target.value
)에 따라 변수를 업데이트해줘요. 그러나 변수는 그들의 값을 리렌더링 사이에 "기억"하는 것 같지 않아요. 일반 변수 대신 상태 변수를 활용하여 이 문제를 해결해보세요.
먼저 useState
를 리액트에서 가져오세요. 그리고 나서 firstName
과 lastName
을 useState
를 호출하여 선언된 상태변수로 대체하세요. 마지막으로 모든 firstName = ...
을 setFirstName(...)
으로 변경하고 lastName
에도 같은 작업을 하세요. handleReset
또한 업데이트해서 리셋 버튼도 작동하도록 만드는 것을 잊지 마세요!
3. 충돌 고치기
사용자가 피드백을 남길 수 있는 작은 폼이 있어요. 피드백이 제출되면 감사 인사말이 화면에 보여야해요. 하지만 "예상보다 적은 훅이 렌더링 되었어요"라는 에러 메시지가 떠요. 실수를 찾아서 해결할 수 있나요?
훅이 호출되는 곳에 제약이 있나요? 이 컴포넌트가 어떤 규칙을 어긴건 아닌가요? 린터가 확인하는 것을 막는 어떤 코멘트가 있는지 확인하세요. 이 부분이 보통은 버그가 숨겨진 곳이에요!
훅은 최상위 컴포넌트에서만 호출될 수 있어요. 여기서 처음에 나온 isSent
는 이 규칙을 따라서 정의되었지만 message
를 정의하는 부분은 조건문 안에 중첩되어 있어요.
이 이슈를 해결하기 위해 조건문 바깥으로 옮기세요.
기억하세요. 훅은 조건이 없이 호출되어야하고 항상 같은 순서를 가져야해요.
불필요한 else
분기를 없애서 중첩을 줄일 수 있어요. 그러나 여전히 훅을 호출하는 것은 첫 번째 return
전에 발생해야해요.
두 번째 useState
호출을 if
조건문 뒤로 옮겨본 후 어떻게 다시 코드가 깨지는지 확인하세요.
린터가 리액트에 맞춰져 있다면 이와 같이 실수가 생겼을 때 린트 에러를 확인하세요. 로컬에서 잘못된 코드를 실행할 때 에러를 찾을 수 없다면 프로젝트에 맞게 린터를 세팅할 필요가 있어요.
4. 불필요한 상태 제거하기
버튼을 눌렀을 때, 이 예시는 사용자의 이름을 물어보고 인사하는 alert를 보여줘야해요. 이름을 저장하기 위해 상태를 사용했지만 모종의 이유로 항상 "Hello, !"만 보여요.
이 코드를 고치려면 불필요한 상태 변수를 없애세요. (나중애 왜 이것이 작동하지 않는지에 대해 더 이야기할 거예요.)
왜 이 상태 변수가 불필요한지 이야기할 수 있나요?
필요한 함수 안에서 선언된 일반적인 name
변수를 활용하여 코드를 고쳤어요.
상태 변수는 컴포넌트가 리렌더링 동안 정보를 가지고 있어야할 때 필요해요. 단일 이벤트 핸들러 안에서는 일반적인 변수는 잘 작동할 거예요. 상태 변수를 일반적인 변수가 잘 작동할 때 사용하지 마세요.
'리액트 공식문서 | React Docs > Learn > Learn React' 카테고리의 다른 글
[Adding Interactivity] State as a Snapshot | 상태는 스냅샷이다 (1) | 2024.02.19 |
---|---|
[Adding Interactivity] Render and Commit | 렌더링과 커밋 (2) | 2024.02.18 |
[Adding Interactivity] Responding to Events | 이벤트에 반응하기 (0) | 2024.02.15 |
[Adding Interactivity] Adding Interactivity Overview | 상호작용 추가하기 개요 (0) | 2024.02.15 |
[Describing the UI] Understanding Your UI as a Tree | 트리로 UI 이해하기 (1) | 2024.02.15 |