Hook

JaeungE·2022년 1월 27일
0

React 찍어먹기

목록 보기
7/8
post-thumbnail

지금까지는 클래스 컴포넌트를 이용해서 애플리케이션을 구현해보았다.

클래스 컴포넌트의 문제는 JavaScriptthis 키워드에 대한 정확한 이해를 필요로 하고, 메서드에 this 바인딩을 하기 위한 코드들이 가독성에 좋지 않은 영향을 끼쳤다.

이 외에도 클래스 컴포넌트는 다양한 문제를 가지고 있어서 함수형 컴포넌트의 사용을 권장하는데, 함수형 컴포넌트는 state생명 주기 메서드를 비롯한 여러 기능들을 사용하지 못 한다는 단점이 존재했다.

하지만 React 16.8v 에서 도입된 Hook의 등장으로 함수형 컴포넌트가 가지고 있던 문제점이 대부분 해결되었다.

Hook을 이용해서 사용하는 state, context, ref와 같은 기능들은 기존에 클래스 컴포넌트에서 알고있던 개념에서 크게 벗어나지 않지만, 조금씩 다른 내용이 있으므로 자세한 내용은 아래에서 살펴보자.




useState()

useState()는 함수형 컴포넌트에서 state를 사용할 수 있게 해주는 Hook이다. 문법은 아래와 같다.

useState(InitialState) => [ state, setState() ]

InitialState는 처음 렌더링(마운트) 할 때 필요한 매개변수로, state의 초기값을 지정한다.

useState() 호출시 상태값을 가지는 statestate의 값을 변경시키는 setState() 함수를 담고있는 배열을 반환하며, 주로 구조 분해 할당을 이용해서 사용한다.

또한, 리렌더링(업데이트)시 InitialState 인자는 무시되며, state의 값은 사라지지 않고 갱신된다.

간단한 count 예제를 통해 Hook 사용법을 보도록 하자.



State Hook을 호출해서 받은 state 변수와 함수를 배열 구조 분해를 이용해서 다른 이름으로 재정의 한다.

이벤트 핸들러의 setCount() 함수의 매개변수로 callback 함수를 이용해 호출하고 있는데, 클래스 컴포넌트에서 사용하던 것과 동일하게 최신의 state를 참고하는 방법이다.

이처럼 State Hook을 이용한 것과 클래스 컴포넌트의 state 객체를 사용하는 것은 큰 차이가 없어보이는데, 아래 예제를 보면 차이점을 이해할 수 있다.

기존 state와의 차이



카드의 심볼과 번호를 출력해주는 간단한 예제다.

cardsymbolvalue 값을 가지는 객체이며, 버튼의 이벤트 핸들러를 호출하면 card 객체의 symbol값을 spades로 변경한다.

이제 결과를 확인해보자.



?????????????????????

card 객체가 가지고 있던 value 값이 사라져 버렸다.

이런 결과가 나타나게 된 이유는 State Hookstate 변경 함수는 값을 병합하는게 아닌, 새로운 값으로 덮어쓰기 때문이다.

클래스 컴포넌트의 setState() 함수처럼 값을 병합하고 싶다면, 아래처럼 확산 연산자(Spread operator)를 이용해서 기존 값을 포함해 갱신하도록 하자.



value 값이 유지되는 것을 확인할 수 있다.




useEffect()

useEffect()는 함수 컴포넌트에서 부수 효과(Side effect)의 구현을 위한 Hook이다.

클래스 컴포넌트에서 사용하던 생명 주기 메서드를 대체하며, 문법은 아래와 같다.

useEffect(didUpdate)

인자로 전달되는 didUpdate는 함수이며, useEffect()는 인자로 전달받은 함수를 매 렌더링마다 실행한다.

또한 useEffect()는 비동기적으로 브라우저에 렌더링이 끝나고 난 뒤에 실행된다.

브라우저에 렌더링 되기 이전에 동기적으로 부수 효과를 실행하고 싶다면, useEffect()와 실행 순서에만 차이가 있는 useLayoutEffect() Hook을 이용하자.

그럼 간단한 타이머 예제를 통해 사용 방법을 알아보자!



의도한 동작은 1초 마다 count가 1씩 증가하고, count 값을 콘솔에 출력하는 코드다.

정상적으로 동작하는지 확인해보자.



이상해 보이지만 언뜻 보면 제대로 동작하는 것 처럼 보인다.

콘솔에도 로그가 잘 찍히고 있는지 확인해보자.



.................... 모르는 사이에 대참사가 일어나고 있었다.

이런 사태가 벌어지게 된 이유는 업데이트마다 실행되는 useEffect()의 특성상, 1초 뒤에 count에 변경이 일어날 때 마다 setInterval 함수가 계속해서 호출되기 때문이다.

그럼 업데이트마다 이전에 등록한 타이머를 제거하면 될 것 같은데, 이럴 때 정리(clean-up) 함수를 이용하면 된다.


정리(clean-up) 함수

정리 함수는 업데이트시 다음 effect가 실행되기 전, 그리고 마운트가 해제될 때 실행된다.

사용하는 방법은 useEffect() 함수의 인자로 전달된 함수가 새로운 함수를 반환하기만 하면 된다.

위의 예제에서 타이머를 정리하는 정리 함수를 추가해보자.



useEffect() 내부에서 등록했던 타이머를 해제하는 함수를 반환하고 있다.

이제 컴포넌트 업데이트가 이루어지면 다음 타이머를 등록하기 전, 그리고 컴포넌트의 마운트가 해제되기 전에 정리 함수가 실행될 것이다.

다시 한 번 확인해보자.



카운트는 정상적으로 올라가고 있다.

콘솔에도 제대로 출력되고 있는지 확인해보자.



정상적으로 동작하는 것을 볼 수 있다.




useContext()

useContext() 함수는 이전의 컨텍스트와 거의 차이가 없다.

React.createContext() 메서드를 이용해 컨텍스트 객체를 생성해야 하는 것도 동일하며, ProviderConsumer의 사용도 다를것이 없다.

다른 점이 있다면 useContext()가 클래스 컴포넌트의 Class.contextType 프로퍼티를 대체한다는 것 정도밖에 없다...

그래도 간단하게 알아보고 가자. 문법은 아래와 같다.

useContext(Context)

인자로 전달받는 ContextReact.createContext(default)메서드로 생성된 객체이며, useContext() 함수의 용도는 단지 인자로 전달된 context 객체를 구독한다는 것 밖에 없다.

간단한 예제를 통해 알아보자.



context를 이용해 전달한 값으로 하위 트리에 있는 h1 element의 색상을 바꾸는 간단한 예제다.

결과는 아래와 같다.



아래처럼 useContext()를 사용하지 않고 Context.Consumer를 이용해도 같은 동작이 가능하다.





useRef()

useRef() 또한 클래스 컴포넌트에서 사용하던 React.createRef()와 크게 다르지 않다.
똑같이 Ref 객체를 생성하며, current 프로퍼티를 이용해 노드의 참조를 얻어온다.

유일한 차이점이라면 클래스 컴포넌트와 함수 컴포넌트의 리렌더링 방식에 따른 차이밖에 없다.

React.createRef() 함수는 항상 새로운 Ref 객체를 생성하기 때문에 함수형 컴포넌트에서 리렌더링 시 이전 Ref 객체를 참조할 수 없다.

반대로 useRef()는 매번 새로운 객체를 생성하는 것이 아니라, 이전에 사용한 Ref 객체를 반환하기 때문에 리렌더링을 해도 이전 Ref 객체를 참조할 수 있다는 점이 차이점이다.

이 외에는 모두 동일하기 때문에 문법만 보고 넘어가자.

useRef(initialValue)

initialValueRef 객체 생성시 초기 값이다.




Custom Hook

React에서 기본적으로 제공하는 Hook을 이용해 사용자가 직접 새로운 Hook을 만들수도 있다.

Custom Hook 또한 반복되는 로직을 뽑아낸 함수이기 때문에 매개변수와 반환 값을 사용자가 자유롭게 정의할 수 있다.

다만, Hook이라는 것을 알리기 위해 Custom Hook의 이름은 항상 "use"로 시작해야 한다.

그럼 Custom Hook을 직접 만들어보자.



useEffect() 예제에서 사용했던 카운터를 변형해서 별도의 Hook으로 만들었다.

useCounter() 함수는 매개변수로 init을 가지기 때문에 해당 Hook을 사용하는 입장에서는 원하는대로 초깃값을 변경할 수 있다.

프로그램의 흐름은 MyComponent를 호출하면 count5부터 1초 간격으로 1씩 감소하고, 0이 되면 멈출 것이다. 결과를 확인해보자.



정상적으로 실행되는 것을 확인할 수 있다.

이번엔 Custom Hook을 여러 번 호출하면 state 값은 어떻게 되는지 확인해보자.



하나의 컴포넌트에서 Cumstom Hook의 초깃값을 각각 7, 3으로 두 번 호출하는 코드다.

count state가 어떻게 변화하는지 확인해보자.



만약 같은 stateeffect를 공유했다면 초깃값이 모두 3이 되거나, 아니면 countBcount0이 됐을 때 countA도 더 이상 변하지 않아야 하지만 그러지 않았다.

왜냐하면 MyComponent 내부에서 useCounter()를 호출한 것이 실제로는 useState()useEffect()를 호출한 것과 다르지 않기 때문이다.

그렇기 때문에 같은 Custom Hook을 사용해도 서로 state 값을 공유하지 않고 독립된 state를 가질 수 있다.




Hook 사용 규칙

일반 JavaScript 함수에서 호출 금지

당연한 얘기지만 HookReact의 함수형 컴포넌트에서 클래스 컴포넌트의 기능을 사용하기 위해 만들어졌다.

그러므로 Hook함수 컴포넌트 혹은 Custom Hook에서만 호출해야 한다.



함수의 최상위(at the Top Level)에서 호출

Hook은 반드시 함수의 최상위에서 호출되어야 하는데, 이유는 함수 컴포넌트의 업데이트 방식에 있다.

함수형 컴포넌트의 경우 리렌더링(업데이트) 할 때 함수 자체를 다시 호출하기 때문에 Hook은 이전 stateeffect를 참조할 때, state가 어떤 useState()에 매칭되는지 판별하려면 Hook이 호출된 순서에 의존하게 된다.

그렇기 때문에 반복문, 조건문, 중첩된 함수 내에서 Hook을 호출하게 되면 순서를 보장할 수 없어서 항상 함수의 최상위에서 호출해야 한다.





참고 자료
Using the State Hook - React
https://ko.reactjs.org/docs/hooks-state.html


Using the Effect Hook - React
https://ko.reactjs.org/docs/hooks-effect.html


Hook의 규칙 - React
https://ko.reactjs.org/docs/hooks-rules.html


자신만의 Hook 만들기 - React
https://ko.reactjs.org/docs/hooks-custom.html


Hooks API Reference - React
https://ko.reactjs.org/docs/hooks-reference.html


Hook 자주 묻는 질문 - React
https://ko.reactjs.org/docs/hooks-faq.html

0개의 댓글