이제 컴포넌트 함수와
DOM이 어떻게 업데이트되는지는 충분히 다룬 것 같습니다
당연히 이것들을 이해하는 건
매우 중요합니다
하지만 이제부터는 상태(state)를 더 자세히 다뤄볼까 합니다
한 가지 중요한 건
이전에도 언급했었지만
컴포넌트 함수에 등록된 상태는
해당 컴포넌트의 범위 내에 속해있습니다
굉장히 중요합니다
그리고 컴포넌트를 재사용할 때마다 재생성됩니다
예를 들어 앱 컴포넌트에서
두번째 counter를
기존 counter 컴포넌트에 기반하면 생성되고
첫 카운트를 0으로 지정한다고 치면
두 카운터 모두
이 코드에 기반하여 있지만
카운터는 모두 각자의 counter 상태를 부여 받습니다
즉 이 상태는 공유되지 않습니다
그레서 그걸 저장하고 새로고침 후
이 첫 번째 카운터를 바꾸면
두 번째 카운터 값에 영향을 주지 않고, 반대로도 마찬가지 입니다
그리고 이건 굉장히 중요한 개념입니다
그게 컴포넌트들의 재사용을 가능케 하기 때문입니다
상태는 컴포넌트 안에 속해 있습니다
그리고 만약 똑같은 컴포넌트 함수를 사용해
그 함수를 기반으로 여러 컴포넌트 인스턴스(instance)를 만들면
모든 인스턴스는 각자의 상태를 갖습니다
이건 굉장히 중요하므로 꼭 기억하셔야 합니다
하지만, 그게 전부가 아닙니다
상태는 리액트가 위치에 따라 추적합니다
이게 무슨 뜻인지 보여드리려면
Counter 컴포넌트를 살짝 바꿔보겠습니다
업데이트된 버전의 컴포넌트도 첨부해두었습니다
그리고 이제 카운터를 상태로 추적하지 않고
대신 여기서 바뀐 것들을 추적하도록 했습니다
그러니까 이제 제 컴포넌트의
counter의 배열입니다
바뀌는 건 더하기 혹은 빼기 1이 전부입니다
그 다음 counter 값이
모든 변경 사항들을 더해서 계산합니다
그럼 결과적으로 카운터는 이전과 똑같이 동작합니다
하지만 이제 다른 컴포넌트를 불러올 수 있는데요
제가 미리 준비해놨습니다
초기 프로젝트의 일부분입니다
그건 바로 CounterHistory 컴포넌트입니다
이 컴포넌트의 목적은
버튼마다 다른 출력값을 가지는 것입니다
즉 이 카운터에 만든 모든
변경사항들입니다
HistoryItem 컴포넌트에 도움을 받겠습니다
CounterHistory 컴포넌트 함수와
똑같은 파일에 정의되어 있습니다
이 컴포넌트 함수는 오직
이 함수를 통해서만 사용되고 엄격하게 이 함수에 속해 있습니다
이제 이 CounterHistory 컴포넌트를
이 Counter 컴포넌트 안에서 사용할 수 있습니다
이 단락 밑에 CounterHistory를 추가하기만 하면 됩니다
그리고 물론 import도 위에 추가해야 하죠
그 다음엔 CounterHistory에게
history 속성을 전달해야 하고
history가 CounterChanges 속성이 되도록 설정해줍니다
이게 변동 사항의 배열입니다
그러고 나서 저장을 하고
새로고침 후
카운터를 증가시키면, 이 목록이 출력됩니다
이제 감소시켜보겠습니다
다른 결과가 이 목록에 출력되고 있습니다
계속 변화를 주어도 동일합니다
이제 왜 Counter가 현재 값을 갖고 있는지 알 수 있습니다
그 이유는 그 기록이 보이거나
아니면 바뀐 기록이 이 밑에 기록되기 때문입니다
제가 왜 이걸 추가했을까요?
왜냐하면 CounterHistory 컴포넌트 파일의
HistoryItem 컴포넌트에서 상태를 관리할 수 있습니다
특정 항목이 선택되거나
강조되는 것을
조절하는 상태입니다
그러니까 예를 들어, 이 항목을 클릭하면
이게 강조됩니다
이걸 여러 항목에 할 수 있고
강조를 지우는 것도 가능합니다
하지만 뭔가 재밌는 것을 발견하게 됩니다
이 항목을 눌러보겠습니다
그리고 '증가하기'나 '감소하기' 버튼을 다시 눌러봅니다
그러면 아직도 2번째 항목이
강조되어 있습니다
물론 이건 잘못 강조된 것입니다
왜냐하면 제가 강조하고 싶은 건
이 항목이기 때문입니다
저건 제가 좀 전에 선택한 항목입니다
다시 보여드리겠습니다
다시 해제하고 선택한 다음, 버튼을 누르면
또 잘못된 아이템이
-1 대신 +1이 선택되고 있습니다
특정 컴포넌트 인스턴스에 속한
상태인데 이게 어떻게 가능한 걸까요?
어떻게 컴포넌트 인스턴스를 건너뛰고 이동할 수 있는 걸까요?
이건 전에도 언급했듯이, 위치로 추적하기 때문입니다
그러니까, 컴포넌트 인스턴스
또는 정확히는 컴포넌트의 유형(type)이 중요합니다.
이 경우에는, 이건 historyItem이지만
컴포넌트 인스턴스의 위치외도 상관이 있습니다
상태는 이 컴포넌트 타입에만 속한 게 아니라
이 컴포넌트가 사용되는 위치에도 속해 있습니다
카운터의 경우에는 이게 문제가 되지 않았습니다
두 개의 카운터가 있지만
이 둘은 절대 위치가 바뀌지 않습니다
그러니 상태는
카운터 유형과 위치에 속해 있습니다
그리고 바뀐 게 없기 때문에, 다 잘 작동됩니다
하지만 CounterHistory의 경우 그렇지 않습니다
새로운 항목이 위에 삽입되면
다른 항목은 밑으로 내려갑니다
새로운 항목이 추가될 때마다 매번 그렇습니다
그러므로 HistoryItem 텀포넌트 인스턴스의
위치가 바뀝니다
그렇기 때문에 리액트도 그렇게 동작하고
상태는 위치와
컴포넌트 유형에 속해 있습니다
제가 이 ‘증가하기‘를 클릭하면
상태는 이 컴포넌트에서 이 컴포넌트로 넘어갑니다
선택됐었던 건 이 컴포넌트였다는 걸 이전에 보셨습니다
지금은 이 컴포넌트입니다
그리고 그건 문제가 될 수 있습니다
왜냐하면 대개의 경우 상태가 이런 식으로
건너뛰는 걸 원치 않기 때문입니다
그렇기 때문에 리액트는 이를 피할 수 있는
매커니즘인 키(key)를 제공합니다
이건 목록에서만 발생하는 문제고
같은 타입의 형제 컴포넌트가
있을 때 생기는 문제이기 때문에
컴포넌트의 수나 위치가 바뀔 수 있습니다
하지만 그냥 예를 든 것이고, 지금 같은 경우는 아닙니다
그러나 여기서는 동적으로 생성된 목록이 있기 때문에
이 경우에 해당합니다
그래서 리액트가 강제적으로
모든 목록 항목에 키를 추가하도록 하는 겁니다
왜냐하면 그 키는 리액트에서
상태를 구체적인 컴포넌트 인스턴스에 매핑할 때
고려되는 다른 요소이기 때문입니다
하지만 확실히 여기서는 동작하지 않죠습니다
아이템을 선택할 때 발생하는 문제 때문에
상태가 건너뜁니다
그 문제가 발생하는 이유는 키를 사용하기 때문인데
인덱스(index)에 설정했기 때문입니다
따라서 여기서 출력되는 카운트 값의 인덱스와
그 인덱스는 최종적으로 특정 카운트 값에
엄격하게 매핑되는 값이 아닙니다
대신, 인덱스는 항상 똑같이 유지됩니다
이 아이템의 인덱스는 항상 0일 것이고
이 아이템의 인덱스는 항상 1일 것입니다
하지만 이 목록에 새로운 값을 추가하면
당연하게도 키들은 바뀌지 않습니다
하지만 그 키에 속한 값들은 바뀌었습니다
그건 인덱스를 키 값으로 쓸 때의 문제입니다
그렇기 때문에 그런 방법을 써서는 안 되고
대신, 꼭 특정 값과 연결된
키 값을 써야하는 이유입니다
예를 들어, 이 앱에서는
로직을 바꿀 수도 있고 Counter 컴포넌트로 갈 수도 있습니다
그리고 항상 setCounter를 사용해
카운터를 바꿀 땐
그냥 이런 식으로 숫자를 저장하지 않습니다
대신, 객체에 저장을 하고
그래야 바뀌어야 하는 속성 값이
값, 즉 value로 이름이 지어질 수 있습니다
그리고 ID 속성을 추가해서
무작위 숫자를 생성하고
100을 곱해 봅시다
대게의 경우, 데이터베이스 같은 곳에서
오는 데이터를 다루게 될 겁니다
아무튼 고유의 ID들이 있습니다
하지만 이 데모에서는 무작위 숫자를 생성해서
ID로 쓰는 건 괜찮습니다
이론적으로 같은 숫자가 두 번 생성될 수 있지만
그 위험은
예를 들어 1000을 곱해주면 줄일 수 있습니다
그 과정을 이 밑에서 반복할 수 있고
그 변동 사항을 HandleIncrement의
value 속성에 저장 후 그 다음 ID를 추가합니다
이런 식으로 무작위 숫자여야 합니다
여기 저장한 초기 값도 똑같이 해줍니다
초기 상태 값은 initialCount입니다
그리고 무작위 ID도 추가해줍니다
이 reduce 함수에서 변경이 이루어진 상태에서
CounterChange 값을 불러오면 됩니다
이 숫자에 값을 추가하기 위해서입니다
전체 객체에 추가하는 게 아닙니다
이러한 수정을 왜 하는 걸까요?
왜냐하면 이제 change 객체에 속하는
ID가 있을 것이기 때문입니다
그리고 이제 그 ID를 CounterHistory에서 사용할 수 있습니다
이 히스토리는 이제 객체의 배열입니다
모든 count에 값과 ID가 있고
그렇기 때문에 더이상 이 인덱스를 쓸 필요가 없습니다
이렇게 바뀐 것들을 가지고
새로고침을 하고 ‘증가하기’를 클릭한 다음
이걸 누르고, 또 ’감소하기‘를 누르면
이제 상태가 그 아이템과 함께 이동하는 걸 볼 수 있습니다
이제 컴포넌트 인스턴스들을 건너뛰지 않고
구상 인스턴스에 묶여 있습니다
그게 여기서 이 키가 중요한 이유입니다
그게 비슷한 컴포넌트를 포함한 동적인 목록이 있을 때
리액트가 컴포넌트를 확실히 식별해내도록 하기 때문입니다
이런 경우 주의해야 할 건, 예를 들어
상태는 이 컴포넌트 인스턴스와 이동합니다