본문 바로가기

Front-end/React

Hook

정의


hooking - 소프트웨어 구성요소간 발생하는 함수호출, 메시지, 이벤트 등을 중간에 바꾸거나 가로채는 명령, 행위

hook - 이때의 함수호출, 이벤트 또는 메시지를 처리하는 코드를 hook 이라고 함

React에서의 hook

함수 컴포넌트에서 React state와 생명주기 기능을 연동할 수 있게 해주는 함수

  • props, state, context, refs, lifecycle 과 같은 react 개념에 좀더 직관적인 API 를 제공
  • 컴포넌트 사이에서 상태 로직을 재사용 하기 위함
  • 독립적인 테스트가 가능
  • class 컴포넌트 안에서는 동작하지 않음

class 컴포넌트의 단점

  • this 키워드의 작동이해가 되지 않으면 혼란을 줄수있음
  • 코드의 최소화를 힘들게 만듦

Hook 사용 규칙(linter plugin 에서 강제함)

  1. 취상위 에서만 Hook을 호출해야함 → 반복문, 조건문, 중첩된 함수 내에서 hook 실행 금지
  2. 함수 컴포넌트 또는 custom hook 내에서만 호출

State Hook


useState

  • 컴포넌트가 다시 렌더링 되어도 그대로 유지
  • 초기값은 첫 번째 렌더링에만 딱 한번 사용
  • 매번 렌더링시 useState가 사용된 순서대로 실행

예시

state 변수 선언

import React, { useState } from 'react';

function Example() {
  // 새로운 state 변수를 선언하고, 이것을 count라 부르겠습니다.
  const [count, setCount] = useState(0);
  •  
  • 인자로 state의 초기값 전달
  • return
    • state 변수
    • 해당 변수를 갱신할 수 있는 함수

state 갱신 & 가져오기

 <p>You clicked {count} times</p>
 <button onClick={() => setCount(count + 1)}>
    Click me
  </button>
  • 클래스 방식과 달리 this 호출하지 않아도됨
  • this.setState와 달리 state를 갱신 하는것은 병합이 아니라 대체

Effect Hook


effect(side effects) - 컴포넌트 안에서 데이터를 가져오거나 구독, DOM 조작 등을 말함

  1. clean-up 이 필요한 effect
  2. 그렇지 않은 effect
  • 함수 컴포넌트 내에서 이런 side effects를 수행할 수 있도록 해줌
  • class 컴포넌트 life cycle 의 세가지를 하나의 API 로 통합한것
    • componentDidMount
    • componentDidUpdate
    • componentWillUnmount

Clean-up 이 필요없는 Effects

  • React 가 DOM을 업데이트한 뒤(rendering 이후) 추가로 코드를 실행해야하는 경우
  • 첫번째 rendering 과 모든 업데이트에서 수행
  • 실행 이후 신경쓸게 없는 effects
  • class life cycle method
    • componentDidMount
    • componentDidUpdate
  • ex) - network request, DOM 수동조작, logging

예시

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

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Clean-up을 이용하는 Effects

  • 외부 테이터에 구독을 설정해야 하는 경우 메모리 누수가 발생하지 않도록 clean-up 필요
  • class life cycle method
    • componentDidMount → 외부 데이터 구독
    • componentWillUnmount → clean-up

예시

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

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // effect 이후에 어떻게 정리(clean-up)할 것인지 표시합니다.
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
  • clean-up은 마운트 해제되는 시점에 실행

Multiple Effect

예시

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}
  • 각 관심사에 따라 로직을 분리시킬 수 있음

Effect가 업데이트시 마다 실행되는 이유

예시

// { friend: { id: 100 } } state을 사용하여 마운트합니다.
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // 첫번째 effect가 작동합니다.

// { friend: { id: 200 } } state로 업데이트합니다.
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 이전의 effect를 정리(clean-up)합니다.
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // 다음 effect가 작동합니다.

// { friend: { id: 300 } } state로 업데이트합니다.
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 이전의 effect를 정리(clean-up)합니다.
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // 다음 effect가 작동합니다.

// 마운트를 해제합니다.
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 마지막 effect를 정리(clean-up)합니다.
  • clean-up이 Unmount시 한번이 아니라 모든 re-rendering시 실행되는 이유
    • → 컴포넌트가 이미 rendering이 된 상태에서 update가 일어났을때
    • → 기존 값(외부 데이터)에 해당하는 effect는 clean-up을 시키고 update된 effect를 작동시킴
  • 클래스 컴포넌트에서는 보통 update 로직을 빼먹었을시 발생할 수 있는 버그를 예방함

useContext


  • context 객체를 받아 현재 값을 반환
  • useContext를 호출한 컴포넌트는 context 값이 변경되면 항상 re-rendering
    • re-redering 으로 인한 최적화가 필요할 경우 메모이제이션(useMemo 등..)을 이용하여 최적화
  • context API 사용시 아래와 동일한 의미
    • static contextType = MyContext
    • <MyContext.Consumer>
  • provider는 사용해야함
const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

// context 생성 및 초기값 세팅
const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    // provider component 사용 및 value 로 새로운 값 전달
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

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

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

useCallback


  • 메모이제이션된 콜백을 반환
  • 의존성이 변경 되었을 때에만 새로운 콜백으로 변경됨
  • 불필요한 렌더링을 방지하기 위함
  • useCallback(fn, deps) == useMemo(() => fn, deps)

예시

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useMemo


  • 메모이제이션된 을 반환
  • 의존성이 변경 되었을 때에만 다시 계산함
  • rendering 중에 실행됨
  • useEffect에서 하는 일과 구분 필요
  • 이것을 사용하지 않고도 동작할 수 있도록 작성하고 최적화 할때 사용하는것을 권장

예시

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

useRef


  • 전달된 인자로 초기화된 변경 가능한 ref객체 반환

예시

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` attribute는 mount된 input element를 가리키고있음
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

useLayoutEffect


  • useEffect와 컨셉은 동일
  • 모든 DOM 변경이후 동기적으로 발생
  • DOM 에서 레이아웃을 읽고 동기적으로 리렌더링 하는 경우 사용
  • 화면 갱신 차단의 방지가 가능할때 표준 useEffect를 먼저 사용

useDebugValue


  • 개발자도구에서 custom hook 레이블을 표시할때 사용
  • custom hook 에서도 공유된 라이브러리에서만 사용 권장

예시

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // 개발자도구에서 표시될 내용 -> "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

 

참고자료

https://ko.reactjs.org/docs/hooks-intro.html

'Front-end > React' 카테고리의 다른 글

Styled-component  (0) 2022.12.05
React Query  (0) 2022.11.28
Redux  (0) 2022.11.23
Next.js  (0) 2022.11.23
React core  (0) 2022.11.23