이제 이 접근법은
이 ref를
이 dialog로 전달하는 것이었는데, 한 가지 작은 결함이 있습니다
왜냐하면 문제는 바로
TimerChallenge 컴포넌트가
ResultModal 컴포넌트를 사용한다는 것인데
그러면 결국 이 dialog 참조를 알아야 한다는 것입니다
여기 dialog 요소에 붙어 있는 것 말입니다
물론 이 작은 데모 애플리케이션에서는
작업하는 개발자는 우리밖에 없습니다
그건 문제될 게 없습니다, 우리가 썼고
우리는 이게 무슨 뜻인지 알고 있습니다
하지만 명심하셔야 합니다
우리는 더 큰 리액트 프로젝트를 작업할 것이고
그리고 거기서 컴포넌트들을 작성할 때
다른 개발자들과 분업하여서
다른 개발자들이 작성한
컴포넌트들을 여러분이 작업해야 할 수도 있고
그 반대의 경우도 있습니다
그러면 여러분은 매번 그 컴포넌트를 자세히
이해해서 내부적으로 어떻게 작동하는 것인지 알아야 합니다
그래야 올바르게 사용할 수 있고, 예를 들어
이 ref에서 correct 메소드를 호출하는 것은
문제가 될 수 있습니다
만약 다른 개발자가
dialog를 div로 바꾼다면, 여러분은 혼란스러울 것 입니다
왜냐하면 showModal을 호출하는 건 더 이상 작동하지 않을 것이기 때문입니다
그러므로 추천하는 것은
이 ResultModal 컴포넌트를
자신의 함수를 노출하도록 구축하여
그 컴포넌트 외부에서 ref의 도움으로 호출될 수 있도록 하는 것입니다
여기서는 예를 들어, 독립적으로 동작합니다
즉 이 JSX 코드가 어떻게 바뀌든 영향을 받지 않습니다
그래서 ResultModal 컴포넌트에 작업하는 사람은
그가 원하는대로 바꿀 수 있습니다
여러분에게 노출된 함수가 하나라도 있다면 말입니다
그걸 이 컴포넌트에서 호출합니다
이 ResultModal 컴포넌트에서 말입니다
그건 showModal을 내부 dialog 요소에서 호출하는 것 대신입니다
그리고 이게 가능한 이유는
컴포넌트에서 또 다른 특별한 훅을 사용했기 때문입니다
그것의 도움으로 호출될 함수를 노출시킵니다
그건 useImperativeHandle 훅입니다
이 훅을
이 컴포넌트 함수에서 호출하여
속성과 메소드들을 정의할 수 있으며
이 컴포넌트에서 바깥으로 접근할 수 있어야 합니다
그리고 제가 말씀드리고 싶은 건
이 훅을 자주 사용하지는 않을 것입니다
왜냐하면 대부분의 상황에서 여러분은
속성을 사용하기를 선호해야 합니다
하지만 이런 사례는 이 훅이 굉장히 유용합니다
이 컴포넌트를 더 견고하고 재사용 가능하게 만들어 줍니다
이제 useImperativeHandle은 두 개의 인자를 필요로 합니다
첫 번째는 이 ref여야 합니다
forwardRef로부터 받은 것입니다
그래서 useImperativeHandle은 실질적으로
forwardRef와 함께 작업해야 하는 것입니다
그리고 두 번째 값은 함수인데
객체를 반환해야 하며
속성과 메소드들을 모아놓습니다
그 속성과 메소드들은 이 컴포넌트나 다른 컴포넌트에 노출되어야 하는 것들입니다
예를 들어 여기, open 메소드를 노출해보겠습니다
이 이름은 완전히 여러분 마음입니다
이 메소드는 곧
이 컴포넌트 바깥에서 호출될 것입니다
그럼 여기 추가해놨으면
useRef 훅도 여기 import해야 합니다
이 ResultModal 컴포넌트 파일 안에 가져오고 useRef를
이 안에서 실행합니다, 왜냐하면 이제
dialog에 접근하는 또 다른 ref가 필요할 것이기 때문입니다
왜냐하면 이제 이 dialog 요소를 분리해야 합니다
이 ResultModal 컴포넌트 내에서 사용되며
다른 외부 컴포넌트로부터 분리됩니다
그래서 TimerChallenge 컴포넌트에서, 예를 들어
새로운 ref를 추가해보겠습니다
이름은 이렇게 dialog입니다
이제 이 dialog 참조가
ref 참조의 값으로 dialog에 전달됩니다
그리고 이제 이 open 메소드가
즉 이 함수가 반환한 이 객체가
useImperativeHandle의 두번째 인자로 전달되는 것입니다
그리고 이 open 메소드가 이 dialog 참조를 사용하고 싶은 곳이며
current 속성이 showModal을 호출하여
이 open 메소드가 호출됐을 때
이 dialog의 showModal 메소드가 호출됩니다
그렇게 설정했으면
다시 TimerChallenge로 돌아가 봅시다
여기에 이 dialog 참조가
여전히 ResultModal과 연결되어 있습니다
하지만 시스템 내부에서는
이제 이 객체를 참고할 것입니다
그게 useImperativeHandle이 하는 일이며
그로 인해
여기서 dialog.current.open를 쓸 수 있습니다
왜냐하면 이 open 메소드는
여기서 호출하고 있는데, 여기서 정의한 open 메소드이기 때문입니다
방금 설명했듯이
이 객체가 결국엔
이 dialog 참조에 저장되며 이 TimerChallenge 컴포넌트 내에 있습니다
왜냐하면 dialog를
ref의 값으로 ResultModal로 전달하기 때문입니다
forwardRef와 useImperativeHandle의 도움으로
이 객체와의 연결이 수립되었습니다
그러니까 결국엔 이 open 메소드입니다, 여기서 호출되는 것입니다
그러면 이걸 저장해보겠습니다
그리고 이 도전을 시작하고 멈추지 않으면
전과 같은 결과를 얻습니다
하지만 이제 TimerChallenge를
이 ResultModal 컴포넌트의 dialog 요소와 분리했습니다
그리고 여길 바꿀 수 있는데
그래도 잘 작동합니다
왜냐하면 여기 로직도 조정할 것이기 때문입니다
이 open 메소드 말입니다
그리고 이건 물론 다시 바꿀 것입니다
하지만 이제 ResultModal 컴포넌트에 작업하는 사람이
얼마든지 원하는대로 바꿔도
이 컴포넌트가 유지되고
open이라는 메소드를 계속 노출시킨다면 말입니다
왜냐하면 이 open 메소드가 우리가 여기에 호출하는 것이기 때문입니다
다시 한 번 말씀드리지만 이 간단한 데모 앱에서는
그런 게 상관 없습니다
하지만 규모가 더 큰 프로젝트들에서는
도움이 될만한 패턴이자 기능입니다
그러면 다시 이 dialog를 원상복구합니다
그리고 여러분은 이제 이 기능을 컴포넌트 내에서 어떻게
호출할 함수들에게 노출 시키는지 배웠습니다