2025년, 코딩은 선택이 아닌 필수!

2025년 모든 학교에서 코딩이 시작 됩니다. 먼저 준비하는 사람만이 기술을 선도해 갑니다~

웹프로그래밍/react

6.훅(Hook)

파아란기쁨1 2022. 6. 15. 11:59
반응형

훅(Hook)이란?

State 를 이용하여 렌더링에 필요한 데이터를 관리하게 되는 것을 살펴 보았다.

클래스 컴포넌트에서는 생성자에서 state를 정의 하고 setState 함수를 통해 state 를 업데이트 하게 된다.

하지만 기존 함수 컴포넌트는 이러한 state를 정의해서 사용하거나 컴포넌트 생명주기에 맞춰 실행 되도록 할 수 없기 때문에 나온것이 바로 훅(Hook) 이다.

Hook 이란 갈고리 라는 뜻을 의미하는데 보통 프로그래밍에서 '원래 존재하는 어떤 기능에 갈고리를 거는 것 처럼 끼어 들어가 같이 수행하는 것' 을 의미한다.

마찬가지로 리액트에서도 'state와 생명주기 기능에 갈고리를 걸어 원하는 시점에 정해진 함수를 실행 되도록 만든 것'이다.

 

이러한 훅의 이름은 모두 use 로 시작한다.

다음으로 훅들에 대해 하나씩 알아 보자.

 

useState

useState는 state를 사용하기 위한 훅이다.

다음의 예를 통해 확인해 보자.

import React,{useState} from 'react';
function Counter(props){
	var count = 0;
	return (
		<div>
			<p> 총 {count} 번 클릭</p>
			<button onClick={()=>count++}>클릭</button>
		</div>
	)
}
ReactDOM.render(
	<Counter />,
	document.getElementById('root')
)

이렇게 했을 때 위와 같은 화면에서 클릭 버튼을 클릭하게 되면 count 는 증가 하지만 state 가 변경 되지 않아 재랜더링 되지 않는다.

이럴때 useState()를 사용하게 된다.

useState는 다음과 같이 사용한다.

const[변수명,set함수명]=useState(초깃값);

따라서 다음과 같이 수정을 해 보자.

import React,{useState} from 'react';


function Counter(props){
	const [count,setCount]=useState(0);
	return (
		<div>
			<p> 총 {count} 번 클릭</p>
			<button onClick={()=>setCount(count+1)}>클릭</button>
		</div>
	)
}
ReactDOM.render(
	<Counter />,
	document.getElementById('root')
)

구름 IDE 에서 실행 할때 Type Error 가 뜬다.

react 버젼이 낮아서 에러가 뜰 수 있다.

react를 다음 명령어로 업데이트 하자.

터미널에서 다음을 입력해서 설치 해 준다.

npm add react@next react-dom@next

 

 

 

그렇게 하면 다음과 같이 정상적으로 수행 되는 것을 확인 할 수 있다.

클릭 할 때마다 바로 갱신이 된다.

 

useEffect

사이드이펙트를 수행하기 위한 훅이다.

여기서 사이드이펙트는 사전적인 의미로는 부작용 이라는 뜻을 가지고 있다.

즉 개발자가 의도치 않은 코드가 실행되면 버그가 발생하는데 이때 사이드이펙트가 발생했다고 한다.

useEffect 는 리액트의 함수 컴포넌트에서 사이드이펙트를 실행 할 수 있도록 해 주는 훅으로 사용법은 다음과 같다.

useEffect(이펙트함수,의존성배열);

의존성 배열안의 변수 중에서 하나라도 값이 변경 되었을 때 이펙트 함수가 실행 된다.

이펙트 함수는 컴포넌트가 렌더링 된 후와 업데이트로 인한 재렌더링 이후에 실행 되는데 마운트와 언마운트시 한번만 수행되게 하고 싶으면 의존성 배열에 빈배열([]) 을 넣으면 된다.

 

예제코드를 살펴 보자.

function Counter(props){
	const [count,setCount]=useState(0);
	
	useEffect(()=>{
		document.title=`총 ${count}번 클릭했습니다.`
	}
	);
	
	return (
		<div>
			<p> 총 {count} 번 클릭</p>
			<button onClick={()=>setCount(count+1)}>클릭</button>
		</div>
	)
}
ReactDOM.render(
	<Counter />,
	document.getElementById('root')
)

document.title 은 브라우저에서 제공하는 API를 사용해서 document의 title을 업데이트 한다.

위 코드처럼 의존성 배열 없이 useEffect()를 사용하면 리액트는 DOM이 변경된 이후에 해당 이펙트 함수를 실행하라는 의미로 받아 들여 진다.

여기서는 useEffect() 함수를 이용하여 componentDidMount(),componenetDidUpdate() 와 같은 생명주기 함수의 기능을 수행하게 된다

 

useMemo

useMemo는 Memoized value를 리턴하는 훅이다.

리액트에서 컴포넌트가 리렌더링 될 때 변화가 없는 부분도 리렌더링 된다면 자원이 낭비되고 성능에도 문제가 생기게 됩니다. 이러한 상황에서 useMemo를 사용 시 의존성 배열의 값에 아무런 변화가 없다면 이전에 연산된 값인 memorized value를 리턴하고 불필요한 연산을 없애 성능을 최적화할 수 있습니다.

사용법을 살펴 보면 다음과 같습니다.

const getNumber = (number) => {
  console.log("숫자가 변동되었습니다.");
  return number;
};

const getText = (text) => {
  console.log("글자가 변동되었습니다.");
  return text;
};

const ShowState = ({ number, text }) => {
  const showNumber = getNumber(number);
  const showText = getText(text);

  return (
    <div className="info-wrapper">
      {showNumber} <br />
      {showText}
    </div>
  );
};

const App = () => {
  const [number, setNumber] = useState(0);
  const [text, setText] = useState("");

  const increaseNumber = () => {
    setNumber((prevState) => prevState + 1);
  };

  const decreaseNumber = () => {
    setNumber((prevState) => prevState - 1);
  };

  const onChangeTextHandler = (e) => {
    setText(e.target.value);
  };

  return (
    <div className="App" style={{ margin: "50px" }}>
      <div>
        <button onClick={increaseNumber}>+</button>
        <button onClick={decreaseNumber}>-</button>
        <input
          type="text"
          placeholder="Text..."
          onChange={onChangeTextHandler}
        />
      </div>
      <ShowState number={number} text={text} />
    </div>
  );
};

ReactDOM.render(
	<App />,
	document.getElementById('root')
)

숫자나 글자가 변경되었을 때마다 둘 다 출력 되는 것을 확인 할 수 있다.

이것은 불필요한 자원을 낭비하고 있다는 증거가 된다.

 

useMemo 사용법

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

useMemo를 사용해서 memoizedValue라는 memorization 될 값을 선언하고 의존성 배열에 a와 b값을 넣어 a, b의 변경 여부를 확인하며, 변경 시 computeExpensiveValue를 이용하여 연산된 값을 memoizedValue에 대입합니다

 

다음과 같이 useMemo 를 적용해 보자.

import React,{useState,useEffect,useMemo} from 'react';
import ReactDOM from 'react-dom';
import './index.css';
//import App from './App';
import * as serviceWorker from './serviceWorker';

const getNumber = (number) => {
  console.log("숫자가 변동되었습니다.");
  return number;
};

const getText = (text) => {
  console.log("글자가 변동되었습니다.");
  return text;
};

const ShowState = ({ number, text }) => {
  const showNumber = useMemo(() => getNumber(number), [number]);
  const showText = useMemo(() => getText(text), [text]);

  return (
    <div className="info-wrapper">
      {showNumber} <br />
      {showText}
    </div>
  );
};

const App = () => {
  const [number, setNumber] = useState(0);
  const [text, setText] = useState("");

  const increaseNumber = () => {
    setNumber((prevState) => prevState + 1);
  };

  const decreaseNumber = () => {
    setNumber((prevState) => prevState - 1);
  };

  const onChangeTextHandler = (e) => {
    setText(e.target.value);
  };

  return (
    <div className="App" style={{ margin: "50px" }}>
      <div>
        <button onClick={increaseNumber}>+</button>
        <button onClick={decreaseNumber}>-</button>
        <input
          type="text"
          placeholder="Text..."
          onChange={onChangeTextHandler}
        />
      </div>
      <ShowState number={number} text={text} />
    </div>
  );
};

ReactDOM.render(
	<App />,
	document.getElementById('root')
)

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();

숫자가 변경되면 숫자만 변경되는 이벤트가 발생 되는 것을 확인 할 수 있습니다.

 

 useCallback

useCallback는 useMemo()와 유사한 역할을 수행한다. 다만 값이 아닌 함수를 반환하는 부분이 다르다.

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

사용법

const memoizedCallback = useCallback(function, deps);

침실,주방,욕실의 불을 끄고 켜는 프로그램을 확인해 봅니다.

import React,{useState,useEffect,useMemo} from 'react';
import ReactDOM from 'react-dom';
import './index.css';
//import App from './App';
import * as serviceWorker from './serviceWorker';

function Light({ room, on, toggle }) {
  console.log({ room, on });
  return (
    <div>
      <button onClick={toggle}>
        {room}
        {on ? "💡" : "⬛"}
      </button>
    </div>
  );
}

function SmartHome() {
  const [masterOn, setMasterOn] = useState(false);
  const [kitchenOn, setKitchenOn] = useState(false);
  const [bathOn, setBathOn] = useState(false);

  const toggleMaster = () => {
    setMasterOn(!masterOn);
  };
  const toggleKitchen = () => {
    setKitchenOn(!kitchenOn);
  };
  const toggleBath = () => {
    setBathOn(!bathOn);
  };
  return (
    <div>
      <Light room="침실" on={masterOn} toggle={toggleMaster}></Light>
      <Light room="주방" on={kitchenOn} toggle={toggleKitchen}></Light>
      <Light room="욕실" on={bathOn} toggle={toggleBath}></Light>
    </div>
  );
}

const App = () => {
  return (
    <div style={{ position: "absolute", top: "50%", left: "50%" }}>
      <SmartHome />
    </div>
  );
};

ReactDOM.render(
	<App />,
	document.getElementById('root')
)

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();

참고 : (https://cocoon1787.tistory.com/798?category=973781)

 

침실만 켜도 세개의 이벤트가 발생합니다.

function Light({ room, on, toggle }) {
  console.log({ room, on });
  return (
    <div>
      <button onClick={toggle}>
        {room}
        {on ? "💡" : "⬛"}
      </button>
    </div>
  );
}
Light = React.memo(Light);

function SmartHome() {
  const [masterOn, setMasterOn] = useState(false);
  const [kitchenOn, setKitchenOn] = useState(false);
  const [bathOn, setBathOn] = useState(false);

  const toggleMaster = useCallback(() => {
    setMasterOn(!masterOn);
  }, [masterOn]);
  const toggleKitchen = useCallback(() => {
    setKitchenOn(!kitchenOn);
  }, [kitchenOn]);
  const toggleBath = useCallback(() => {
    setBathOn(!bathOn);
  }, [bathOn]);

  return (
    <div>
      <Light room="침실" on={masterOn} toggle={toggleMaster}></Light>
      <Light room="주방" on={kitchenOn} toggle={toggleKitchen}></Light>
      <Light room="욕실" on={bathOn} toggle={toggleBath}></Light>
    </div>
  );
}

const App = () => {
  return (
    <div style={{ position: "absolute", top: "50%", left: "50%" }}>
      <SmartHome />
    </div>
  );
};

ReactDOM.render(
	<App />,
	document.getElementById('root')
)

이때 Light 는 React.memo 타입으로 감싸 주어야 props 값이 변경되니 않는 한 다시 호출되지 않습니다.

 

실행해 보면 주방을 켰을 때 주방의 이벤트만 발생하는 것을 알 수 있습니다.

 

훅의 규칙

훅은 단순한 자바스크립트 함수이지만 다음 두가지 지켜야 할 규칙이 있다.

첫번째규칙 : 훅은 무조건 최상위 레벨에서만 호출해야 한다.(따라서 반복문,중첩된 함수안에서 호출하면 안된다.)

두번째규칙 : 리액트 함수 컴포넌트에서만 훅을 호출해야 한다.(일반적인 자바스크립트 함수에서 훅을 호출하면 안된다.)

 

 

 

 

 

참고

소플의 처음 만난 리액트

반응형