[react] React Hooks (리액트 훅스)

chaeyeon·2023년 1월 18일
0
post-thumbnail
post-custom-banner

Hooks란?

리액트 16.8 이전 버전에서는 function 컴포넌트에서는 상태(state)를 관리할 수가 없었다. 16.8 버전에서 Hooks 라는 기능이 도입되면서 function 컴포넌트에서도 상태를 관리할 수 있게 되었다. function 컴포넌트의 Hooks를 사용하면 class 컴포넌트에서만 사용할 수 있었던 라이프사이클을 사용할 수 있으며 React의 여러 기능을 함수형 프로그래밍으로 사용할 수 있게 해준다.

useState 컴포넌트의 상태(state)를 관리 할 수 있다.
useEffect 의존성 배열에 적힌 값이 변경될 때마다, 특정기능이 작동하도록 할 수 있다.
useRef 특정 DOM을 선택하거나 변수를 관리할 수 있다.
useMemo 의존성 배열에 적힌 값이 변할 때만 값을 다시 정의할 수 있다.
useCallback 의존성 배열에 적힌 값이 변할 때만 함수를 다시 정의할 수 있다.
useContext 여러개의 컴포넌트에서 사용할 수 있는 값을(변수, 함수 등) 만들 수 있다
useReducer 상태(state) 업데이트 로직을 reducer 함수에 따로 분리 할 수 있다.
useImperativeHandle
(+ forwardedRef)
useRef로 만든 래퍼런스를 상위 컴포넌트로 전달할 수 있다.
(useImperativeHandle와 forwardedRef를 활용하면 부모 컴포넌트가 자식 컴포넌트의 함수를 호출하거나 값을 가져올 수 있다.)
useLayoutEffect useEffect와 비슷하지만, 모든 DOM 변경 후 브라우저가 화면을 그리기(render)전에 실행되는 기능을 정할 수 있다는 차이점이 있다.
useDebugValue 리액트 개발자도구에서 사용자 Hook 레이블을 표시하는 데에 사용할 수 있다. (사용자 정의 Hook의 디버깅을 도와준다.)

useState

리액트는 state나 props의 값이 변경되면 이를 감지하고 리렌더링을 한다. state는 화면을 바꿔주기위해 사용되는 트리거역할을 하는 값이며 useState는 state의 값을 변경해주는 Hook이다.

const [state, setState] = useState(기본값);

// 수량 변경 예시
import React, { useState } from 'react';

const Sample = () => {
  const [number, setNumber] = useState(0);

  const increase = () => setNumber((prev) => prev + 1);
  const decrease = () => setNumber((prev) => prev - 1);

  return (
    <div>
      <h1>{number}</h1>
      <button type="button" onClick={increase}>
        +1
      </button>
      <button type="button" onClick={decrease}>
        -1
      </button>
    </div>
  );
};

export default Sample;

useEffect

useEffect는 컴포넌트가 마운트 됐을 때 (처음 나타났을 때), 언마운트 됐을 때 (사라질 때), 그리고 업데이트 될 때 (특정 props가 바뀔 때) 특정 작업을 처리할 수 있다.

useEffect는 class 컴포넌트의 라이프 사이클 메서드인 componentDidMount(), componentWillUnmount(), componentDidUpdate()가 합쳐진 것으로 생각하면 된다.

  • clean-up을 이용하지 않는 Effect: useEffect(() => {}, [])
  • clean-up을 이용하는 Effect: useEffect(() => { 특정작업 return () => {} }, [])

clean-up이란?
파라미터로 넣은 함수의 return 함수이다. 컴포넌트가 Unmount 될 때만 cleanup 함수를 실행시키고 싶다면 deps에 빈 배열을, 특정 값이 업데이트되기 직전에 cleanup 함수를 실행시키고 싶다면 deps에 해당 값을 넣어주면 된다. (class 라이프 사이클의 componentWillUnmount와 비슷)

import React, { useState, useEffect } from 'react';

const Sample = (props) => {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    const handleStatusChange = (status) => setIsOnline(status.isOnline);
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);

    // effect 이후에 어떻게 정리(clean-up)할 것인지 표시
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  }, []);

  if (isOnline === null) return 'Loading...';

  return isOnline ? 'Online' : 'Offline';
};
export default Sample;

useRef

일반적으로 JS에서 DOM을 선택해야 하는 상황에선 getElementById, querySelector와 같은 DOM selector 함수를 사용해서 특정 DOM을 선택한다. 리액트에서도 DOM을 선택해야 하는 상황이 발생할 수 있으며 useRef를 사용한다.
Ref 객체의 .current 값으로 원하는 DOM을 선택한다.

import React, { useRef } from 'react';

const Sample = () => {
  const name = useRef();
  const onClick = () => {
    name.current.focus();
  };

  return (
    <div>
      <input name="name" placeholder="이름을 입력해주세요." ref={name} />
      <button onClick={onClick}>input값에 foucs!</button>
    </div>
  );
};

export default Sample;

useRef는 일반적으로 컴포넌트에서 특정 DOM을 선택해야 할때 사용하지만 컴포넌트 안에서 조회 및 수정 할 수 있는 변수를 관리할 때도 사용할 수 있다. useRef로 관리하는 변수는 값이 바뀐다고 해서 컴포넌트가 리렌더링 되지 않는다.

  • setTimeout, setInterval
  • scroll 위치
/* https://microcephalus7.github.io/react/js/377 */

// 1. useState를 사용했을 때
const [number, setNumber] = useState(0);

let timer = setInterval(() => {
  setNumber(number + 1);
  console.log(number); // 0 0 0 0 0 ...
}, 1000);

// 2. useRef를 사용했을 때
const number = useRef(0);

let timer = setInterval(() => {
  number.current = number.current + 1;
  console.log(number); // 0 1 2 3 4 ...
}, 1000);

리액트에서 useState를 사용했을 때 prevState와 nextState를 비교하여 값을 변경한다.

setInterval의 내부 callback의 closure는 prevState를 참조하며 참조로 인하여 prevState는 메모리에 남게되고 가비지 컬렉터의 영향을 받지 않는다.

setStae로 state를 변경하더라도 closure에 의해 state 값은 변경되지 않고 console에는 같은 값이 찍힌다. (0 0 0 ...)

리액트에서 useRef를 사용하면 순수한 JS 객체를 생성한다.
useState처럼 값을 비교하는 과정이 없으며 .current로 값을 변경하면 JS 객체도 그대로 변경된다.
setInterval의 내부 callback의 closure에는 변경되는 .current를 참조한다.
.current로 값을 변경 시 값이 변경되면서 console에는 다른 값이 찍히게 된다. (1 2 3 ...)

setInterval, setTimeout 및 리렌더링에 영향을 주지않는 값들은 useRef를 참조하여 값을 변경한다.


useMemo

useMemo메모이제이션된 값을 return하는 Hook으로 성능최적화에 쓰인다. useMemo는 두번째 인자(deps)로 준 인자 중에 하나라도 변경되면 값을 재계산하며 컴포넌트가 리렌더링 될 때마다 소요되는 불필요한 계산을 피할 수 있다.

만약 deps에 아무것도 전달하지 않는다면, 렌더시


useMemo로 전달된 함수는 렌더링 중에 실행되므로, 렌더링 중에 하지 않는 것을 이 함수 내에서 처리하면 안되고 useEffect 내에서 처리해야 한다.

useMemo(() => {}, []);

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useMemo vs React.memo

React.memo?

  • 컴포넌트를 인자로 받아 새로운 컴포넌트롤 다시 return해주는 Higher-Order Components(HOC)
  • 컴포넌트가 같은 props를 받을 때 같은 결과를 렌더링한다면 React.memo를 사용하여 불필요한 컴포넌트 렌더링을 방지할 수 있다.
  • 같은 props가 들어온다면 리렌더링을 방지하고 마지막 결과를 재사용한다.
  • React.memo는 오직 props가 변경됐는지 아닌지만 체크한다.
  • 만약 React.memo에 감싸진 함수형 컴포넌트가 함수 내부에서 useState나 useContext같은 훅을 사용하고 있다면, state나 context가 변경될 때마다 리렌더링된다.
const MyComponent = (props) => {
  /* props를 사용하여 렌더링 */
};
cosnt function areEqual = (prevProps, nextProps)  =>{
  /*
  nextProps가 prevProps와 동일한 값을 가지면 true를 반환하고, 그렇지 않다면 false를 반환
  */
}
export default React.memo(MyComponent, areEqual);

useMemo와 React.memo의 공통점

React.memouseMemo 모두 props가 변하지 않으면 인자로 넘긴 함수는 재실행되지 않고, 이전의 메모이즈된 결과를 사용한다.

useMemo와 React.memo의 차이점

  1. React.memo는 HOC, useMemo는 hook이다.
  2. React.memo는 HOC이기 때문에 클래스형 컴포넌트, 함수형 컴포넌트 모두 사용 가능하지만, useMemo는 hook이기 때문에 오직 함수형 컴포넌트 안에서만 사용 가능하다.

useCallback

useCallback은 useMemo와 비슷한 Hook으로 성능최적화에 사용되는 메모제이션 Hook이다.

useMemo는 특정 결과값을 재사용 할 때 사용하는 반면, useCallback은 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용한다.

useCallback(() => {}, []);

// 함수 안에서 사용하는 상태 혹은 props 가 있다면 꼭, deps 배열안에 포함시켜야 한다.
// props 로 받아온 함수가 있다면, 이 또한 deps에 꼭 포함시켜야 함.
const memoizedCallback = useCallback(() => doSomething(a, b), [a, b]);

useContext

리액트는 state를 상위 컴포넌트에서 관리하고 해당 state를 하위 컴포넌트 props로 내려주는데 하위 컴포넌트가 많아질 수록 props를 통해 하위로 계속 전달해야 하기 때문에 컴포넌트가 복잡해 지고 코딩이 불편해진다.

리액트의 기본 기능인 context API를 사용하면 모든 컴포넌트가 context에 있는 state를 자유롭게 사용할 수 있어 편리해진다. (Redux가 contextAPI 기반으로 만들어졌다고 한다.)

useContext는 props를 아무 컴포넌트에서도 조회할 수 있게 도와주는 Hook이다. context 객체를 받아 해당 context의 현재 값을 반환한다.

Provider로 감싼 하위 컴포넌트에 props를 전달하지 않아도 어디서든 state에 접근을 할 수 있게 된다.

const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
  },
};

const ThemeContext = React.createContext(themes.light);

// Context.Provider와 useContext를 같이 사용
const App = () => {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
};

const Toolbar = (props) => {
  return (
    <div>
      <ThemedButton />
    </div>
  );
};

const ThemedButton = () => {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
};

useReducer

useReduceruseContext로 내려받은 state에 대해서 state관리를 할 수 있게 된다.

useReducer를 사용하면 컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있으며 상태 업데이트 로직을 컴포넌트 바깥에 작성 할 수도 있고, 다른 파일에 작성 후 불러와서 사용 할 수도 있다.

useState의 대체 함수이며 (state, action) => newState의 형태로 reducer를 받고 dispatch 메서드와 짝의 형태로 현재 state를 반환한다.

const [state, dispatch] = useReducer(reducer, 초기값);

reducer란? reducer는 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 함수다.

const initialState = { count: 0 };

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
};

const Counter = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  );
};

Custom Hooks

리액트의 함수형 컴포넌트에서 로직을 쉽게 재사용하기 위해 Custom Hook을 만들 수 있다. 주로 재사용 되는 로직들을 커스텀 훅으로 만들어 사용한다고 한다.

자주사용하는 Custom Hook (1~14번까지는 npm으로 설치 가능)

  1. useTitle: react document의 title을 몇개의 hooks와 함께 바꾸는 것
  2. useInput: input 역할
  3. usePageLeave: 유저가 페이지를 벗어나는 시점을 발견하고 함수를 실행
  4. useClick: 누군가 element를 클릭하는 시점을 발견해 함수 실행
  5. useFadeIn: 어떤 element든 상관없이 애니메이션을 element 안으로 서서히 사라지게 만듦
  6. useFullscreen: 어떤 element든 풀스크린으로 만들거나 일반화면으로 돌아갈 수 있도록 함
  7. useHover: 어떤 것에 마우스를 올렸을 때를 감지
  8. useNetwork: online, offline 확인
  9. useNotification: notification API 사용할 때 유저에게 알림을 보내줌
  10. useScroll: 스크롤을 사용할 때를 감지하여 알려줌
  11. useTabs: 웹사이트에 메뉴 또는 무엇이든간에 tab을 사용하기 매우 쉽게 만들어줌
  12. usePreventLeave: 유저가 변경사항이나 무엇이든간에 저장하지 않고 페이지를 벗어나길 원할 때 확인
  13. useConfirm: 위와 비슷
  14. useAxios, useFetch: HTTP requests client axios을 위한 wrapper 같은 것
  15. useLocalStorage : 로컬스토리지 쉽게 사용할 수 있도록 함
  16. useEventListener
  17. useMediaQuery
  18. useDarkMode : 다크 모드 관련

이 외에도 https://nikgraf.github.io/react-hooks/에서 다양한 custom hook을 확인할 수 있다.


-참고

profile
4년차 퍼블리셔에서 프론트엔드 개발자로 이직 성공한 신입 개발자 입니다-*
post-custom-banner

0개의 댓글