useContext 훅을 사용해서 코드를 간결하게 만들어보기
어제 컨텍스트 API를 처음 사용해보면서 자식 컴포넌트에 상태값을 사용하려고 Consumer 생성자 속에 기존의 반환문을 옮겨서 값을 불러왔었다. 만들고나니 사용법은 맞는 것 같긴한데, 뭔가 좀 지저분해 보이고 다른 방법이 있을 것 같았는데, 오늘 그것을 배웠다.
리액트 라이브러리에 있는 useContext 훅을 사용하는 것인데, 코드를 좀 더 간결하게 만들어준다.
우선 리액트 라이브러리에서 useContext 훅을 불러오고, 기존의 함수 내에서 Authcontext.Consumer를 삭제했다. 그리고 context의 줄임말 ctx라 명명한 상수에 useContext 훅을 사용하여 값으로 상태값이 저장되어 있는 AuthContext를 지정해주었다. 그러고나서 불필요해진 몇몇 코드들을 지워주었더니, 원상태의 코드와 별반 다를 게 없는 깔끔한 상태가 되었다.
이전처럼 Consumer를 사용하는 것도 틀린 방법은 아니지만, 이 방법이 더 깔끔하고 간단한 것 같다.
동적 컨텍스트 만들기
현재 수업을 들으며 연습중인 웹페이지에는 버튼이 몇개 있는데, 모두 로그아웃하는 기능이 있는 버튼이다. 이 버튼은 로그아웃 기능이 연결되어 있어서, 당장은 로그아웃으로만 작동을 하지만, 현 웹페이지가 아닌 좀 더 방대한 프로젝트를 진행하며 여러 버튼들이 생겼을 때는 버튼이 그만큼의 여러 기능을 수행할 수 있어야 한다.
로그아웃 기능만 하라고 지정하여 사용할 수는 있겠지만, 좀 더 동적인 버튼을 만들기 위해 코드를 조금 수정해주었다.
위 코드는 동적 컨텍스트를 만들기 위해 수정한 코드이다. 첫 번째 logoutHandler는 로그아웃 버튼을 누르면 실행되는 함수이며, 이 함수를 onLogout 속성에 지정하여 상태값이 있는 컨텍스트 파일에 저장해주었다. 단순히 괄호와 화살표만 써서 빈 함수를 가진 함수 표현식만 할당했는데, 그 이유는 이 컨텍스트를 사용하는 컴포넌트에서 필요에 따라 해당 값을 재정의할 수 있도록 하기 위해서이다.
위 코드에서도 그 활용을 확인할 수 있는데, 로그아웃 함수가 있는 App 파일에서 AuthContext.Provider의 value 값으로 onLogout이 추가되었고, 이 빈 함수를 logoutHandler 함수로 적용해주었다. 이렇게 하면 onLogout은 빈 함수가 아닌, 컴포넌트에서 정의한 로그아웃 함수가 되는 것이다.
마지막 코드는 로그아웃 함수를 사용하는 자식 컴포넌트의 버튼쪽 코드인데, 기존에 props.onLogout으로 되어있었던 값을 컨텍스트가 있는 ctx로 수정해준 코드이다.
리액트의 컨텍스트 API 사용이 부적합한 경우와 훅의 규칙
지금까지 살펴본 리액트 컨텍스트 API는 상태를 모든 컴포넌트에서 공유하기에 좋은 도구이지만 모든 경우에 적합한 도구는 아니다.
컨텍스트 API를 사용하기에 적합하지 않은 경우로는, 작은 규모의 어플리케이션에서 이 컨텍스트를 사용하는 것은 과할 수 있다. 이 경우에는 상태를 props로 전달하여 관리하는 것이 더 간단하고 직관적일 수 있다.
두 번째로, 여러 단계의 컴포넌트 트리를 가진 어플리케이션이나 다양한 종류의 상태가 있는 어플리케이션에서는 컨텍스트를 사용하면 상태를 전달하는 과정이 오히려 더 복잡해질 수 있다. 이 경우에는 Redux 같은 상태 관리 라이브러리를 사용하는 것이 효율적일 수 있다.
세 번째로, 컴포넌트의 테스트 가능성을 고려해야 한다면 상태를 컴포넌트의 로컬 상태로 유지하고 테스트할 수 있는 다른 방법을 사용하는 것이 좋다.
마지막으로 너무 많은 컴포넌트에 의존하는 경우, 상위 컴포넌트에서 상태를 변경하면 하위 컴포넌트들이 다시 렌더링되면서 성능면에서 안좋은 어플리케이션이 될 수 있다. 따라서 의존성을 최소화하고 필요한 상태만 전달하는 것이 좋다.
또한, 리액트 훅을 사용할 때는 지켜야할 몇 가지 규칙이 있다. 이런 규칙은 리액트 컴포넌트 생명주기와 상태 관리에 대한 일관성과 안정성을 보장하기 위해 필요한 부분이다.
첫 번째로, 훅은 최상위에서만 호출되어야 한다. 최상위가 아닌 조건문, 반복문, 중첩 함수 등의 내부에서 호출하면 오류가 발생할 수 있다.
두 번째로, 훅은 함수 컴포넌트 또는 커스텀 훅 내에서만 호출되어야 한다. 일반적인 자바스크립트 함수나 클래스 컴포넌트(리액트의 이전 버전에서 사용되던 컴포넌트 형태) 내에서는 훅을 호출해서는 안된다.
세 번째로, 리액트 훅은 호출 순서에 의존하기 때문에 컴포넌트 내에서 훅을 호출할 때는 항상 동일 순서로 호출해야 한다. 훅은 호출 순서에 따라 내부 상태를 관리하고 컴포넌트의 렌더링 주기를 제어하게 된다.
네 번째로, 조건문이 최상위 레벨에서만 변경되거나 조건문 내에서 훅 호출 부분이 항상 실행되는 경우에, 조건문 내에서 훅을 호출한다면 호출 패턴을 일관되게 유지해야 한다. 조건문의 각 분기에서 같은 훅을 사용하고, 분기 외부에서는 훅을 호출하지 않아야 한다.
다섯 번째로, 커스텀 훅은 use로 시작되어야 한다. 아직 배우지는 않았지만 커스텀 훅을 작성할 때도 이름을 use로 시작해야 한다. 이렇게 하면 리액트는 훅이 아닌 일반 함수로 인식하여 오류를 방지할 수 있다.
ForwardRef와 useImperativeHandle 훅
이 부분은 아직 제대로 이해하지 못했지만, 정리가 필요할 것 같아 공부한 내용을 바탕으로 적어보려 한다. 우선 리액트의 고급기능에 해당하며, 실무에서는 거의 쓰지 않는다고 한다.
forwardRef는 저번에 배웠던 ref를 자식 컴포넌트로 전달할 때 사용한다. forwardRef를 사용하면 부모 컴포넌트에서 생성한 ref를 자식 컴포넌트로 직접 전달할 수 있으며, 자식 컴포넌트에서 해당 ref를 활용할 수 있게 된다.
forwardRef는 DOM 요소에 직접 접근해야 할 경우와 자식 컴포넌트 내부의 ref에 접근해야 하는 경우에 사용하게 된다. 예를 들어, 특정 요소의 스크롤 위치를 조정하거나, 포커스를 설정하거나, 외부 라이브러리와 연동하는 등의 작업을 할 수 있다. 그리고 자식 컴포넌트 내부의 ref에 접근하는 경우, 자식 컴포넌트가 내부의 특정 요소나 컴포넌트에 대한 ref를 노출하고, 부모 컴포넌트에서 해당 ref를 사용하여 조작할 수 있다.
forwardRef를 사용하려면 React.forwardRef 함수로 생성해주어야 하며, forwardRef 컴포넌트 내부에서 ref를 원하는 자식 요소 또는 컴포넌트에 전달한다. forwardRef 컴포넌트를 부모 컴포넌트에서 사용할 때는 ref를 생성하고 이를 forwardRef 컴포넌트에 전달해야 한다.
이렇게 하면 부모 컴포넌트에서 생성한 ref가 forwardRef 컴포넌트 내부로 전달되고, 해당 컴포넌트에서 ref를 활용할 수 있게된다.
useImperativeHandle 훅은 부모 컴포넌트가 자식 컴포넌트의 메서드나 속성에 직접 접근할 수 있는 훅이다. 주로 ref와 함께 사용되며, 자식 컴포넌트에서 노출시키고 싶은 메서드나 속성을 선택적으로 노출시킬 수 있다.
이 훅을 사용하여 자식 컴포넌트의 메서드나 속성을 노출시키려면 우선 자식 컴포넌트 내부에서 훅을 사용하여 노출시키고 싶은 메서드나 속성을 정의해야 한다. useImperativeHandle (ref, () => ({ method1, method2, prop1 }))처럼 정의할 수 있다.
그 다음으로 자식 컴포넌트가 참조할 ref를 생성한다. const childRef = useRef(); 처럼 생성할 수 있다.
이후, 자식 컴포넌트에 ref를 전달하여 useImperativeHandle 훅이 노출시킨 메서드나 속성을 부모 컴포넌트에서 접근할 수 있도록 해야한다. <ChildComponent ref= {childRef} /> 처럼 ref를 전달할 수 있다.
그리고 부모 컴포넌트에서 자식 컴포넌트의 노출된 메서드나 속성에 접근하여 사용하면 된다. 이때, childRef.current.method1()처럼 ref의 current 속성을 사용하여 접근할 수 있다.
이 훅을 사용할 때는 자식 컴포넌트의 내부 상태를 부모 컴포넌트에서 직접 변경하는 것은 좋지 않다. 그래서 주로 외부 라이브러리와의 통합이나 특정 상황에서만 사용되어야 한다.

'React' 카테고리의 다른 글
음식주문앱 만들면서 복습하기(2) (0) | 2023.05.22 |
---|---|
음식주문앱 만들면서 복습하기(1) (0) | 2023.05.20 |
복잡한 상태 관리, 리액트 컨텍스트 API (0) | 2023.05.18 |
useEffect의 사용, useEffect 훅의 종속성, cleanup 함수 (0) | 2023.05.17 |
프래그먼트 작업, wrapper로 감싸기, Portals, Ref (0) | 2023.05.17 |