728x90
반응형

리액트 하면 가장 먼저 떠오르는 것은 무엇일까?

나는

Hook

이라고 생각한다.

 

1. Hook의 탄생 배경

함수형 컴포넌트에서도

상태(state)를 관리하고, 생명주기 함수를 사용할 수 있도록 하기 위함.

 

과거 리액트를 사용하면서,

state를 사용하거나 생명 주기 함수를 사용하기 위해서는 클래스 컴포넌트를 사용해야 했다.

하지만, 이는 컴포넌트의 복잡도를 높이고 코드의 재사용성을 떨어뜨리는 요인이 되었다.

 

class ClassComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  incrementCount = () => {
    this.setState((prevState) => ({
      count: prevState.count + 1,
    }));
  };

  render() {
    return (
      <div>
        <h1>Count: {this.state.count}</h1>
        <button onClick={this.incrementCount}>Increment</button>
      </div>
    );
  }

클래스 컴포넌트에서는

constructor 메서드를 사용하여 초기 state를 설정하고,

this를 바인딩하기 위해 화살표함수 혹은 bind 메서드를 호출해야 한다.

또한, 하나의 컴포넌트가 여러 개의 라이프사이클 메서드를 가지고 있을 수 있다. (코드 관리가 힘들어짐)

마지막으로, 상속을 사용하기 때문에 컴포넌트의 코드 재사용성이 떨어지고,

코드 중복이 발생할 가능성이 커 유지보수가 힘들어진다.

 

2. Hook의 종류

리액트에서 많이 사용되는 hook만 뽑아보았다.

 

2-1. useState

이 친구는 state를 관리하는 데 사용되는 리액트에서 가장 기본적인 hook 중 하나이다.

이 컴포넌트를 이용해 동적으로 변하는 값을 관리하고,

상태를 따라 UI를 업데이트할 수 있다.

 

import React, { useState } from 'react';

const Count = () => {
  const [count, setCount] = useState(0);

  return (
    <>
      <p>count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        add+
      </button>
    </>
  );
}

 

- useState를 import 한다.

- count는 현재의 상태값, setCount는 상태를 업데이트하는 함수이다.

- useState(0) 는 초기값을 나타낸다.

 

2-2. useEffect

이 친구는 쉽게 말하면 컴포넌트가 렌더링 될 때 특정 작업을 수행할 수 있도록 해주는 hook이다.

컴포넌트 내부에서 선언되어야 한다.

 

import React, { useState } from 'react';

const Count = () => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    // 특정 작업 수행
  });

  return (
    <>
      <p>count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        add+
      </button>
    </>
  );
}

 

-  useEffect는 두 개의 인자를 받는다.

-- 특정 작업을 수행할 함수

-- 의존성 배열: 특정 작업을 수행할 함수가 실행될 조건을 설정하는 역할

 

[의존성 배열이 빈 배열일 경우]

useEffect(() => {
  // 컴포넌트가 마운트될 때만 실행되는 작업
}, []);

 

[의존성 배열에 특정 변수가 들어있는 경우]

useEffect(() => {
  // count가 변경될 때마다 실행되는 작업
}, [count]);

 

[반환값으로 함수를 반환하는 경우]

useEffect(() => {
  // 컴포넌트가 마운트될 때 실행되는 작업
  return () => {
    // 컴포넌트가 언마운트될 때 실행되는 cleanup 함수
  };
}, []);

+) useEffect 사용해서 API 호출하기 예시

더보기
import { useState, useEffect } from 'react';

const CallAPI = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, []);

  if (loading) {
    return <p>Loading...</p>;
  }

  return (
    <>
      {data && (
        <ul>
          {data.map(item => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
      )}
    </>
  );
}

 

2-3. useContext

이 친구는 이름에서 알 수 있다시피 context를 사용하는 hook이다.

리액트에서 상태 관리를 위해 사용되며, 전역 상태를 관리할 수 있다.

 

import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';

const Button = () => {
  const theme = useContext(ThemeContext);

  const buttonStyle = {
    backgroundColor: theme.background,
    color: theme.color,
    padding: '10px 20px',
    border: 'none',
    borderRadius: '5px',
    cursor: 'pointer',
  };

  return (
    <button style={buttonStyle}>
      Click me!
    </button>
  );
}

export default Button;

- useContext를 사용하면 별도로 props를 전달받지 않고도 해당 context 값을 사용할 수 있다.

(코드의 가독성이 향상되고, 복잡한 props drilling이 발생하지 않는다.)

 

** props drilling 이란?

리액트에서 props를 통해 데이터나 콜백 학수를 전달할 때, 여러 레벨의 하위 컴포넌트를 거쳐서 전달하는 것을 의미한다.

 

2-4. useMemo

이 친구는 처음 봤을 때, 뭔가 적을 때 쓰는 hook인줄 알았다.

하지만, 이 hook은 계산이 오래 걸리는 함수의 반환값을 캐싱하여 불필요한 연산을 줄이는 역할을 한다.

 

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

// 오래 걸리는 계산 함수
const ExpensiveCalculation = ({ value1, value2 }) => {
  console.log("Expensive calculation executed!");
  return value1 * value2;
}

const TestComponent = ({ value1, value2 }) => {
  const [count, setCount] = useState(0);

  const result = useMemo(() => {
    return ExpensiveCalculation({ value1, value2 });
  }, [value1, value2]);

  return (
    <div>
      <div>Result: {result}</div>
      <div>Count: {count}</div>
      <button onClick={() => setCount(count + 1)}>add</button>
    </div>
  );
}

- ExpensiveCalculation 함수는 어어어엄청 오래 걸리는 계산식 함수이다.

- useMemo는 함수의 결과 값을 캐싱합니다.

- value1과 value2 값이 변경될 때에만 저 무거운 함수가 실행되고, 그 외의 경우에는 이전에 계산된 result를 사용한다.

 

2-5. useCallback

이 친구는 callback을 해주는 hook이다? 라고 생각했다.

더 자세히 말하자면 함수형 컴포넌트에서 선언된 함수를 memoization(메모이제이션)하여

동일한 인자로 호출되었을 때, 캐싱된 결과를 반환하는 것이다.

 

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

const App = () => {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]); // count가 변경될 때만 handleClick 함수를 새로 생성합니다.

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increase</button>
    </div>
  );
}

- 사실 이 hook은 왜 쓰는 지 궁금했다.

- 성능 최적화를 위해 사용한다고 하니 잘 사용하는 것이 중요한 것 같다.

 

2-6. useRef

이 친구는 DOM 요소를 선택하거나 컴포넌트 내부에서 변수를 선언하고자 할 때 사용된다.

 

import { useRef } from 'react';

const App = () => {
  const inputRef = useRef(null);

  const handleClick = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={handleClick}>Focus</button>
    </div>
  );
}

 

- 쉽게 말하면 ref를 사용하면 저 태그 자체를 inputRef에 저장하는 것이다.

 

 

3. 다른 hooks (내가 아직 안 써본 hook..)

3-1. useReducer

상태를 관리하고 업데이트하는 로직을 컴포넌에서 분리하여 작성할 수 있도록 해주는 hook이다.

useState와 유사하지만, 복잡한 상태 로직을 다룰 때 더 효과적이며, reducer함수를 이용해 상태를 업데이트한다.

 

3-2. useImperativeHandle

ref를 사용하여 자식 컴포넌트의 메서드를 부모 컴포넌트에서 직접 호출할 수 있도록 해주는 hook이다.

 

3-3. useLayoutEffect

useEffect와 유사하지만, useEffect보다 더 일찍 실행되며,

브라우저의 레이아웃과 페인팅 작업이 수행되기 전에 실행되기 때문에

UI 업데이트 전에 동기적으로 상태를 업데이트 할 수 있는 hook이다.

 

3-4. useDebugValue

개발자 도구에서 컴포넌트의 상태 값을 디버깅할 수 있게 해주는 hook이다.

 

4. 커스텀 hook 만들기

리액트에서는 공통으로 사용하는 기능을 묶어서 하나의 hook으로 만들 수 있다.

 

[규칙]

ㄱ. hook 이름은 항상 `use`로 시작해야 합니다.

ㄴ. 기존의 hook을 이용해서 만들거나 다른 커스텀 hook을 이용해서 만들어야 합니다.

ㄷ. 반드시 함수형 컴포넌트 내에서 만들어져야 합니다.

ㄹ. 필요한 경우 state, effect, context, reducer 등을 이용해서 로직을 구현합니다.

 

[useLoading 커스텀 hook]

import { useState, useEffect } from 'react';

const useLoading = (initialValue = false) => {
  const [isLoading, setLoading] = useState(initialValue);

  useEffect(() => {
    const timer = setTimeout(() => {
      setLoading(false);
    }, 3000);

    return () => clearTimeout(timer);
  }, []);

  return [isLoading, setLoading];
}

 

[useLoading 커스텀 hook 사용 ]

import { useLoading } from './useLoading';

const App = () => {
  const [isLoading, setLoading] = useLoading();

  return (
    <div>
      {isLoading ? <p>Loading...</p> : <p>Loaded!</p>}
      <button onClick={() => setLoading(true)}>Load</button>
    </div>
  );
}

 

- 커스텀  hook을 만들면 여러 컴포넌트에서 중복되는 코드를 줄일 수 있어서 좋다.

728x90
반응형

+ Recent posts