이제, 진행 표시줄이 비어 있지만
추가적인 시간 뒤에 다음 질문으로
넘어가 버리는 이 이상한 현상이
어디에서 발생한 걸까요?
자, 이 문제의 원인은
진행 표시줄이 너무 빠르게 고갈되기 때문입니다
10초의 타이머를 가졌지만
stopwatch를 설정하면
5초 뒤면 진행 표시줄이
비어있는 것을 볼 수 있습니다
그리고 이는 여기 interval이
두 번 작동되기 때문에 일어나는 일입니다
그러므로 두 개의 interval이 보이지 않게
실행되는 것이고 이 상태를 업데이트 하게 하는 것입니다
그래서 진행 표시줄은 정한 시간의 반이 지나도 비어버립니다
여기 콘솔을 보시면
두 번 작동되는 것을 볼 수 있습니다
전에 언급한 것처럼 이는 리액트의 strict mode (엄격 모드)가
메인 JSX에 켜져 있기 때문에 일어나는 일입니다
이제 개발 도중에, 이 StrictMode가
모든 컴포넌트 함수를 두번
실행하는 것은
앱 내에서 특정한 에러를 잡아내는데 도움을 주기 위함입니다
왜냐하면 이론적으로
이 앱은 컴포넌트 함수가
화면에 렌더링될 때 실행되는 횟수가 한 번이든 백 번이든
정확하게 똑같이 작동되어야 하기 때문입니다
그리고 그 내용이 여기에는 적용되지 않았습니다
이렇게 코드 내에 있는 버그를 식별해 주는 것입니다
그래서 이 경우에
엄격 모드에 버그가 있다는 사실을 알려줍니다
그럼 무엇이 문제일까요?
문제는 바로 이 Effect 함수가
두 번 실행될 때
두 개의 interval이 작동된다는 것입니다
그렇게 되면 안 됩니다
그리고 이 코드에 필요한 것은 cleanup (클린업) 함수입니다
이미 존재하는 interval을 클린업, 즉 삭제해야합니다
만일 Effect 함수 다시 작동하는 경우의 이야기입니다
그렇게 하면
이 Effect 함수를
10번 불러와도 문제가 되지 않습니다
왜냐하면 예전 interval을 삭제하면
동시에 작동되는 interval은
하나만 될 것이기 때문입니다
그리고 이는 어차피 추가해야 하는 기능입니다
왜냐하면 지금 상황에서 다른 질문으로 넘어가면
예전 타이머가 지속해서 작동되는 문제가 발생하고
초기화되지 않기 때문입니다
이 부분도 곧 작업을 해야 하므로
이것이 클린업 함수가 필요한
또 다른 이유입니다
그러니 QuestionTimer 컴포넌트로 가봅시다
그리고 저 상수 안의 interval에 참조를 보관하고
그리고 클린업 함수를 추가해줍니다
반환값으로서 배운 것과 같습니다
위치는 Effect 함수입니다
그리고 이 클린업 함수는
이 Effect 함수를 다시 작동하기 전이나
컴포넌트가 DOM으로부터 삭제될 때
리액트에 의해 자동으로 실행될 것 입니다
그래서 스크린으로부터 사라지면
이 클린업 함수에서
이제 clearInterval을 호출하고
그리고 그것에 interval 참조를 전달합니다
그리고 타이머에서 같은 작업을 할 필요가 있습니다
그러지 않으면 이 타이머는 계속 작동할 것입니다
예를 들어
만일 이 컴포넌트가 화면에서 삭제된다면
즉 게임 종료 화면에 다다른 경우라면
이 때에도 또한 타이머를 삭제해야 합니다
그러므로 변수이나 상수로 그 타이머에
참조를 보관하고
그리고 나서 그 클린업 함수를 추가해야 합니다
이는 timeout을 그곳에 타이머를 전달함으로써 없애겠습니다
이걸 가지고
클린업 함수가 추가 되었으니
다시 불러와 보면
진행 표시줄이
더 이상 너무 빠르게 사라지는 현상이 없어졌고
의도한 대로 잘 작동합니다
그리고 표시줄이 비어버리면
다음 질문으로 넘어가게 됩니다
그러나 당연하게도, 타이머와 이 진행 표시줄이
다시는 초기화 하지 않는 문제가 발생합니다
이제 왜 다음 질문으로 넘어갈 때 타이머와 진행 표시줄이
초기화 되지 않을까요?
이는 QuestionTimer 컴포넌트가
재생성되지 않았기 때문입니다
이것은 중요한 부분입니다
QuestionTimer 컴포넌트를
여기 Quiz 컴포넌트에서 사용 중입니다
그리고 다음 문제로 넘어갈 때
사용자의 답안을 보관하면서 넘어가고
그리고 activeQuestionIndex가 변경됩니다
왜냐하면 userAnswers의 길이가 변경되기 때문입니다
그러나 이 JSX코드는 업데이트 됩니다
그러나 이 questionTimer 컴포넌트는 재성성되지 않습니다
왜냐하면 변경된 적이 없기 때문입니다
이전엔 DOM의 일부였고
지금도 여전히 DOM의 일부입니다
바뀐 것은 유일하게
표시되는 QUESTIONS 텍스트와
그리고 표시되는 답안 뿐이죠
그러나 이 컴포넌트는 거기에 있었고 여기에 있습니다
그리고 그렇기에 삭제되지도 않고 생성되지도 않습니다
그러므로 그 컴포넌트 안의 이 타이머와 interval은
초기화 되지 않는 거죠
이제 당연히 여기 앱에서
타이머가 초기화 되어야 합니다
질문이 바뀌면 당연히 초기화 되어야 하기 때문입니다
그리고 이를 해결할 아주 간단하지만 강력한 방법이
리액트에 있습니다
바로 이 컴포넌트에 키(key)를 추가하는 것입니다
키 속성은
어느 요소나 컴포넌트에도 추가 될 수 있기 때문입니다
키는 리액트가 찾을 수 있는 빌트인 속성이기 때문입니다
그리고 이미 목록 데이터를 출력할 때
해당 키를 사용해야 한다고 알려드렸었습니다
왜냐하면 해당 키는 리액트가
저 각기 다른 목록 항목들을 식별하는데 돕기 때문입니다
그리고 그것은 리액트가 목록을 더 효율적으로 관리하도록 돕습니다
이 모든 작업은 백엔드에서 일어납니다
그러나 키 속성에는 또한 다른 목적이 있습니다
컴포넌트에서 변경될 때마다
컴포넌트가 목록의 일부가 아니더라도
변경될 때면 언제든
리액트는 오래된 컴포넌트 인스턴스(instance)를 삭제할 것이고
새로운 것을 만들어낼 것이기 때문입니다
그래서 삭제하고 재생성할 것입니다
그리고 그게 여기에 딱 필요한 것입니다
왜냐하면 새로운 질문으로 교체할 때마다
QuestionTimer 컴포넌트를 재생성해야하기 때문입니다
그리고 그러므로 질문이 변경될 때마다
변경될 activeQuestionIndex로
QuestionTimer 키를 설정할 수 있습니다
그리고 그 키 속성이 추가된 상태에서
이러한 간단한 변경으로
새로고침을 해보면
이 타이머가 만료되었을 때
즉 진행 표시줄이 텅 비었을 때
그리고 다음 질문으로 넘어갈 때
다시 초기화되는 것을 볼 수 있습니다
새로운 타이머가 설정되고 새로운 interval이 시작됩니다
그리고 자동으로 모든 질문을 거쳐서
계속 그렇게 작동할 겁니다
키를 이용하는 좋은 예시였습니다
이것은 여러분이 꼭 숙지해야 할
리액트의 좋은 기능이기도 합니다