그래서 이 타이머를 어떻게
이 함수 내에서 멈출 수 있을까요?
자바스크립트에는 Timeout 함수가 있어서
타이머를 멈추는 데에 사용할 수 있지만
그 타이머에 포인터가 필요합니다
예를 들어 그 타이머의 id를 input으로 말입니다
그럼 그 포인터가 setTimeout을 통해 반환됩니다
그래서 그 타이머를 상수에 저장하고
그리고 변수를 여기
이 함수 바깥에 추가해서
이제 두 함수 모두 사용할 수 있고
timer=setTimeout ( ) 해줍니다
그리고 그걸 여기서 값으로 사용합니다
이제 타이머가 실행될 때 handleStop을
이 버튼과 연결하기만 하면 됩니다
그러므로 항상 handleStart을
onClick으로 전달하고 싶지 않고 timerStarted가 true인지 확인하고 싶습니다
만약 true라면, handleStop을 가리켜
이 함수가 실행되도록 합니다
만약 이 버튼이 클릭되고
타이머가 시작된다는 전제 하에 말입니다 그 반대의 경우는 handleStart입니다
하지만 이걸로 handleStop은
타이머가 실행될 때 실행되어야 하고, 이 타이머는 지워야 합니다
이게 제대로 작동한다면, 타이머가 끝나기 전에 잘 멈추었을 때
'졌습니다'가 보이면 안 됩니다
그래서 제가 이걸 시작하고 멈춰보면
여전히 '졌습니다'가 보입니다, 제가 즉각 '중지'를 눌렀음에도 불구하고 말입니다
다시 한 번 시작하고, 바로 멈춰도 여전히 '졌습니다'가 뜹니다
아마도 타이머가 제대로 멈춰지지 않는 것으로 보입니다
그리고 문제는 우리가 변수를 쓴다는 점입니다
아시다시피, 상태가 바뀌면
이 컴포넌트 함수는 재실행됩니다
그리고 그 뜻은 물론
이 변수가 재생성된다는 것입니다
그러니 handleStop에서 사용된 타이머는
handleStart에서 사용되는 타이머와 다른 타이머입니다
왜냐하면 그 사이에, 컴포넌트 함수가 재실행되기 때문입니다
이 handleStart의 상태를 업데이트를 했기 때문에 말입니다
그런 이유로 이 변수는 사용할 수 없습니다
이제 이 변수는
컴포넌트 함수 바깥에서 정의할 때만 사용할 수 있습니다
그러면 재생성되지 않기 때문입니다
그리고 결과적으로, 이걸 재로딩해서
시작하고 멈추면 이제 더 이상 '졌습니다'라는 문구가 보이지 않습니다
물론, 이 부분은 업데이트되고 있지 않습니다
이건 나중에 다시 살펴보겠습니다
하지만 '졌습니다' 문구는 보이지 않지만
또 다른 문제를 마주하게 됩니다
우리가 마주할 문제를 보여드리기 위해
이 5초짜리 도전을 시작해보겠습니다
그리고 바로 1초 도전을 시작해봅니다
그 다음 두 도전 모두 멈춥니다
그러니까, 5초 시작, 1초 시작, 1초 중지, 5초 중지
그리고 결과적으로
여전히 5초 도전에서는 '졌습니다' 문구가 보입니다
일찍 멈추었음에도 불구하고 말입니다
다시 보여드리겠습니다
시작, 시작, 중지, 중지
이 5초 도전을 분명히 클릭했습니다
하지만 여전히, '졌습니다' 문구가 뜨고
원래대로라면 뜨지 않아야 합니다
여기서는 제가 멈추니까 '졌습니다' 문구가 절대 뜨지 않습니다
그럼 이러한 현상은 왜 나타나는 것일까요?
5초 도전은 제가 중지했음에도 불구하고
1초 도전과 함께 시작, 중지되면
졌다는 문구가 나옵니다
이러한 현상이 생기는 이유는
이 timer 변수가 정의된 위치가
TimerChallenge 파일 내 이 함수의
바깥에 있기 때문입니다
이 변수는 모든 컴포넌트 인스턴스들 사이에서
공유되어야 하고 그 인스턴스들은
이 컴포넌트 함수를 기반으로 합니다
그러므로 제가 5초 도전을 시작하면
이 타이머가 5초 도전의
타이머를 저장합니다
하지만 5초 도전을 시작하고 나서
1초 도전도 시작을 하면
여기 있는 타이머가 이 1초 타이머의 포인터로
덮어씌워집니다
그리고 5초 도전의 포인터는
리액트에 의해 버려지는 것입니다
그러므로 이걸 시작하고, 시작하고, 멈추고
멈추면
이 5초 도전에서는 아무것도 멈추지 않는다는 것입니다
왜냐하면 그 5초의 포인터는
제가 1초 도전을 시작하는 순간 버려졌기 때문입니다
이 변수가 이 파일에서 정의되었기 때문입니다
그래서 이 컴포넌트의 모든 인스턴스들과
공유됩니다
그래서 변수가 해결 방법이 될 수 없는 것입니다
그럼 변수가 해결 방법이 아니라면 So if a variable is not the solution,
무엇으로 해결할 수 있을까요?
바로, 참조가 해결 방법입니다
참조는 그저 HTML 요소와
연결하는 것 뿐 아니라
물론 그것도 흔한 사용 방법입니다만
참조를 어떤 정류의 값이든 제어하기 위해 사용할 수도 있습니다
그러므로 이 TimerChallenge 컴포넌트에서
새로운 참조를 생성합니다, 이름은 timer 참조로 하겠습니다
timer 상수가 이 참조를 보관하는 것입니다
이제 이 timer를 사용해서 timer.current를 설정합니다
왜냐하면 언제나 이 current 속성을 목표로 해야 하기 때문입니다
그리고 이 부분을 timer.current에 담아줍니다
그러면 timeout을 clearTimeout으로 지울 수 있는데
그건 timer.current로 할 수 있습니다
왜냐하면 이 current 속성 안에서 이 포인터를
그 timer로 저장할 것이기 때문입니다
이제 이것이
컴포넌트 함수 내에 저장되어 있기 때문에
특정 컴포넌트 인스턴스에만 할당될 것입니다
이 TimerChallenge 컴포넌트의
모든 컴포넌트 인스턴스들은
그들만의 timer 참조를 갖을 것이고
그 컴포넌트 내 다른 인스턴스들의 참조들과
독립적으로 동작할 것입니다
하지만 동시에, 변수가 컴포넌트 함수에 정의되는 것과는 다르게
이 컴포넌트가 재실행될 때
이 참조는 초기화되거나 지워지지 않습니다
대신, 상태 값들처럼
리액트가 그 timer 값들을 저장합니다
그건 시스템 내부에서 이루어지며
그것들이 컴포넌트 함수가 재실행될 때마다
유실되지 않도록 합니다
참조의 또 다른 특별한 기능입니다
상태 값들처럼 이 값은 유실되지 않는다는 것입니다
컴포넌트 함수가 재실행되어도 말입니다
하지만 상태와는 다른 점은, 이 값을 설정하는 것이
이 컴포넌트 함수를 다시 실행하도록 하지 않는 다는 것입니다
그러므로 그걸 가지고, 최종적으로 저장을 해보면
이걸 시작하고 멈췄을 때 '졌습니다'가 나오지 않습니다
그리고 이제 두 개의 타이머를
동시에 시작하고 멈출 수 있습니다
그럼에도 둘 다 '졌습니다'가 나오지 않습니다
그러므로 여기까지가 참조의 또 다른
적용 방법이었으며, 제어해야 하는 값이 있는데
상태로 쓰이기는 힘들 경우
왜냐하면 timer 자체가 UI와 직접적 영향이 없는 경우, 사용하기 좋은 사례였습니다
타이머가 시작되었는지에만
신경을 쓰지만 예를 들어
이 타이머를 멈췄을 때 UI를 업데이트하고 싶지 않다고 해봅시다
여기서 아무것도 변하지 않은 채
그냥 타이머를 지우기만 하고
UI 업데이트는 하고 싶지 않습니다
그래서 이런 식으로
UI에 영향을 주지 않는 값을
적어도 직접적으로는 주지 않는 값을
제어하고 싶을 때
그리고 컴포넌트가 재실행될 때 초기화되지 않는
그런 경우에 참조가 좋은 방법입니다
그리고 물론 더 많은 적용 방법들을
이번 섹션을 통해 보게 될 것입니다