[React 시작하기] 05. Hooks

2022. 5. 20. 22:12공부/React

Hooks

리액트 버전 16.8부터 추가된 새로운 요소.

클래스 컴포넌트에서만 사용할 수 있었던 state와 lifecycle을 함수 컴포넌트에서도 사용할 수 있게 해주는 기능.

컴포넌트의 state와 관련된 로직을 재사용할 수 있다는 점에서 굉장히 큰 의의를 가진다.

Hook의 개요 – React (reactjs.org)

 

Hook의 개요 – React

A JavaScript library for building user interfaces

ko.reactjs.org

Basic Hooks

Basic Hooks에는 useState, useEffect, useContext가 있다.

등장 배경

  • 컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어렵다.
    • 컨테이너 방식(props를 이용한 로직 재사용) 말고, 상태와 관련된 로직을 의미.
  • 복잡한 컴포넌트들은 사람이 이해하기 어렵다.
  • class는 사람과 기계를 혼동시킨다.
    • 컴파일 단계에서 코드를 최적화하기 어려운 단점이 있었다.
  • this.state는 로직에서 레퍼런스를 공유하기 때문에 문제가 발생할 수 있다.
    • class 컴포넌트는 컴포넌트 생명주기(ex. render) 사이에 레퍼런스를 공유했었다. (함수 컴포넌트는 안그럼)
    • 이 부분은 장단점이 존재하여 뭐가 더 낫다고 볼 수는 없다.

useState()

state를 대체한다.

const [state 명, state 변경 함수] = useState(초기값)

예시 1.

먼저 Class 컴포넌트를 통해 state 예제를 만들었다.

 

위와 같이 Click me를 클릭하면 state의 count를 1씩 증가하는 로직이 있다.

이를 useState() 함수를 이용하여 함수 컴포넌트로 구현하면 다음과 같다.

여기서 useState() 는 매개변수로 넣어준 값을 초기 값으로 하는 배열을 반환한다.

배열의 0번째 인자에는 값이 저장되고, 2번째 인자는 이 값을 변경할 수 있는 함수가 저장된다.

따라서 const [count, setCount] = React.useState(0); 과 같이 사용하여 state 값과 변경 함수를 사용할 수 있다.

 

setCount가 실행되면 Example2 함수가 count+1이 들어간 채로 다시 실행된다.

예시 2.

예시 1과 같은 경우는 useState가 변수 count 단 하나를 가리키게 된다.

만약 여러개의 state가 필요한 경우 useState의 매개변수로 객체 데이터를 넘겨주면 된다.

위처럼 객체 데이터를 넘겨주게 되면, 변수의 이름을 포괄적인 의미인 state로 사용할 수 있다.

setState의 경우 사용법이 조금 달라지는데, 배열의 첫번째 인자가 객체 데이터이니 매개변수로 객체데이터를 넣어주어야 한다.

 

만약 기존 변수에 의존하지 않는 방식으로 값을 넣어주고 싶다면 콜백함수를 이용하는 방법도 있다.

나중에 setState만 사용하는 게 아니라 useEffect와 함께 사용하게 되면 의존성을 파악하는게 중요하다.

useEffect

라이프 사이클 훅을 대체한다. 하지만 대체가 가능한 것이지 똑같다고 보기는 어렵다.

  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

예시 1.

일반적으로 클래스 컴포넌트에서는 다음과 같은 라이프사이클이 동작한다.

componentDidMount는 처음 마운트 될 때 작동하며

componentDidUpdate는 컴포넌트가 업데이트 될 때 작동한다.

 

함수 컴포넌트에서는 이와같은 기능을 useEffect가 대신한다.

useEffect는 componentDidMount와 componentDidUpdate의 기능을 동시에 수행하는 것을 볼 수 있다.

useEffect 두번째 인자

useEffect의 두번째 인자로는 배열을 넣어줄 수 있다. (Dependency라고 한다)

이 배열을 넣어주게 되면, 초기 마운트 될 때와 배열 내부에 있는 변수가 변할 때에만 함수가 호출되게 된다.

여러개의 useEffect 사용

useEffect는 Dependency가 있어서 여러번 사용할 수 있다.

여러개의 useEffect가 있는 경우 순차적으로 실행되며, dependency에 존재하는 변수의 변화에만 반응해서 작동한다.

componentWillUnmount로 사용하기

useEffect의 return값으로 함수를 반환하고, 그 부분에 componentWillUnmount에 해당하는 로직을 작성하면 된다.

useEffect를 만약 componentDidUpdate 용도로 사용하게 된다면 return 부분의 의미가 살짝 달라진다.

위의 예시를 보면, 다음 useEffect가 실행되기 전에 return 부분의 로직이 실행되는 것을 볼 수 있다.

Custom Hooks

useState나 useEffect를 이용하여 자신만의 훅을 만들수도 있다.

훅은 다른 훅의 내부 또는 함수 컴포넌트의 내부에서 호출이 가능하다는 점을 이용한다.

훅의 이름은 use훅이름으로 지으며, js 파일로 작성한다.

1. 특정 기능을 수행하는 훅 만들어보기

dom의 사이즈가 변경될 때마다 해당 창의 가로 너비를 도출하는 훅을 만들어보자.

hook이 실행되면 innerWidth값을 읽어온다.

아직까지는 처음 실행될 때만 값을 읽어오고 있으므로, 화면이 변경되는 이벤트인 'resize'에 따라 값이 수정되게 만든다.

이벤트리스너는 한번만 추가되면 계속 작동하므로, dom이 렌더링 될 때마다 addEventListener가 작동할 필요가 없다.

또한 useWindowW 훅이 제거되면 이벤트 리스너 또한 dom에 있을 필요가 없다.

따라서 useEffect에 dependency를 추가해주고 return 값으로 removeEventListener를 추가해주자.

addEventListener와 removeEventListener 모두 같은 함수를 가리켜야 하므로 콜백 함수를 변수에 저장해서 사용한다.

2. HoC를 대신하는 훅 만들어보기

HOC는 고차 컴포넌트로, 어떤 컴포넌트를 가공하여 새로운 컴포넌트를 리턴한다.

예를 들어, 마운트 여부를 변수로 가진 컴포넌트를 제작하고 싶을 때 HoC는 다음과 같은 로직을 가진다.

이를 커스텀 훅으로 제작하게 되면 다음과 같이 변경된다.

결과

Additional Hooks

기존의 useState나 useEffect처럼 React 라이브러리에서 추가적으로 제공하는 hooks.

useReducer

useState의 확장판. Redux와 깊은 관련이 있으며 다음의 두 경우에 사용한다. 

  • 다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우
  • 다음 state가 이전 state에 의존적인 경우
const [state, dispatch] = useReducer(reducer, { count: 0 });

useReducer는 기본적으로 reducer 함수와 초기값을 매개변수로 받아 state와 dispatch함수를 배열로 반환한다.

 

dispatch 함수는 어떤 객체를 매개변수로 받아 action 객체로 만들어주는 함수이다.

이 때 action 객체는 필수적으로 'type' 이라는 프로퍼티를 가진다.

즉, dispatch 함수의 매개변수로 넘겨주는 객체에는 반드시 'type' 속성이 들어있어야 한다.

reducer 함수는 state와 action객체를 매개변수로 받아 state를 반환하는 함수이다.

useMemo, useCallback

여러 로직이 들어있는 컴포넌트가 다음처럼 준비돼 있다고 가정하자.

여기서 input의 값이 변하면 state가 변하면서 함수 전체가 재실행된다.

그렇게 되면 sum() 함수는 people이 변하지 않았는데도 다시 실행되어 불필요하게 리소스를 사용한다.

이럴 때 useMemo()를 사용하면 된다.

이렇게 하면 sum함수는 people 값에만 변경되게 된다.

 

현재 click 함수는 현재의 state값을 콘솔에 출력하므로, input 태그에 적혀진 값이 console에 출력된다.

만약 초기값으로만 계속 사용하고 싶다면 useCallback을 이용하자.

Dependency를 설정하면, 어떤 변화에도 새로고침 되지 않으므로 state의 초기값을 계속 출력할 수 있다.

 

useMemo 함수와 useCallback 함수의 차이점을 아직은 잘 모르겠지만, 나중에 다시 공부할 예정이다.

 

useRef

비제어 컴포넌트를 사용할 때 우리는 ref 속성을 이용한다고 배웠다.

제어 컴포넌트는 실시간으로 변화를 감지하는 데 유용했고 비제어는 필요할 때만 변화를 감지했었다.

비제어 컴포넌트의 ref는 createRef 또는 useRef로 만들 수 있는데 이 두가지의 차이점은 다음과 같다.

console.log가 return보다 위에 있으므로 mount 되기 전에 ref의 current를 표현하게 되는데

처음에는 둘 다 null과 undefined였으나

한번 mount가 되고 나니 useRef 쪽은 input 태그를 계속해서 가지고 있는 것을 볼 수 있다.

 

즉, createRef는 함수가 실행될 때마다 새롭게 ref를 만들지만

useRef는 한번 실행될 경우 ref를 계속 가지고 있다는 특징이 있다.

 

React Router Hooks

React Router: Declarative Routing for React.js

 

Declarative routing for React apps at any scale | React Router

Version 6 of React Router is here! React Router v6 takes the best features from v3, v5, and its sister project, Reach Router, in our smallest and most powerful package yet.

reactrouter.com

hook으로 router 기능을 구현할 수도 있다.

useHistory

기존에 withRouter라는 HOC를 이용한 방법은 위와 같다.

props에 들어갈 객체를 withRouter가 만들어서 넣어주면 props.history를 통해 push메소드를 사용할 수 있었다.

useHistory를 이용하면 굳이 HOC를 사용하지 않아도 구현 가능하다.

 

useParams

다이나믹 라우팅에 사용했던 id를 사용하려면 props를 사용해야했다.

/profile/:id 의 경우 위와같이 구현했었다.

이를 hook을 이용한 방법으로 바꿔보자면 다음과 같이 바꿀 수 있다.

나머지 두개 훅은 잘 사용하지 않는다.