React

[리액트] React Hook(useState, useEffect, useRef) 정리하기

ejunyang 2024. 5. 21. 12:22

 

 

리액트 숙련주차에 들어갔다. 입문 주차에서는 무엇인가 새로운 것을 배운다는 느낌에 집중도 잘되었는데 숙련 주차에 들어오니 끊임없는 정보때문에 머리가 헤롱하다. 과부하가 온 것 같다. 지금 배우고 있는 단계가 실무에서 가장 많이 쓰고 있는 것이라고 한다. 이럴때일수록 더 집중하고 이해해보려고 노력해야겠다.🔥

 


 

 

 

Styled-components

css-in-js 란?

자바스크립트 코드로 css 코드를 작성하여 컴포넌트를 꾸미는 방식이다. css를 사용할 때 조건문과 변수 등 다양한 로직을 적용해서 사용할 수 있다는 장점이 있다.

 

 

패키지 설치

yarn add styled-components

 

사용방법

태그의 속성명에 접근하여 스타일 주는 방법은 아래와 같다.

import styled from "styled-components";

styled.button`
  // <button> HTML 엘리먼트에 대한 스타일 정의
`;

 

컴포넌트에 접근하여 스타일 주는 방법은 아래와 같다.

import styled from "styled-components";
import Button from "./Button";

styled(Button)`
  // <Button> React 컴포넌트에 스타일 정의
`;

 

태그 속성에 원하는 스타일을 주고 StyledButton 변수에 저장한다. 해당 변수를 Button 컴포넌트에 사용하면 다른 리액트 컴포넌트에서 같은 스타일이 적용된 <Button>을 사용할 수 있다.

import React from "react";
import styled from "styled-components";

const StyledButton = styled.button`
  padding: 6px 12px;
  border-radius: 8px;
  font-size: 1rem;
  line-height: 1.5;
  border: 1px solid lightgray;
  color: gray;
  background: white;
`;

function Button({ children }) {
  return <StyledButton>{children}</StyledButton>;
}
import Button from "./Button";
<Button>Default Button</Button>;

 

 

 

 

 

 

 

useState()

 

기본 문법

컴포넌트의 state를 관리할 수 있는 기본적은 hook 이다. state를 변수로 사용하고, setState를 이용해서 state 값을 수정할 수 있다.

const [state, setState] = useState(initialState);

 

 

함수형 업데이트

카운트로 예시를 들어보자. 기존에 우리가 카운트 값을 증가시켰던 방법은 원시적 데이터로 값을 올려주었다. ( ) 안에 수정할 값이 아니라 함수를 넣어보자. 

import { useState } from "react";

export default function useStatePage() {
  const [count, setCount] = useState(0);
  
  const onPlusHandler = () =>{
  	setCount(count + 1);
  }

  return (
    <div>
      <span> 카운트 {count}시 </span>
      <button onClick={()=>onPlusHandler}>+</button>
    </div>
  );
}

 

( ) 안에 현재 number의 값을 가져와서 그 값에 +1을 더하여 반환하는 함수를 만들었다. 일반적인 업데이트 방식와 함수 업데이트 방식은 어떤 점이 다른지 아래에서 확인해보자.

setCount((currentNumber)=>{ return currentNumber + 1 });

 

 

일반 업데이트 방식은 버튼을 클릭했을 때 첫번째 줄 ~ 세번째 줄의 있는 setCount가 각각 실행되는 것이 아니라, 배치(batch)로 처리한다. 즉 우리가 onClick을 했을 때 setCount 라는 명령을 세번 내리지만, 리액트는 그 명령을 하나로 모아 최종적으로 한번만 실행을 시킨다는 뜻이다. 그래서 setCount을 3번 명령하던, 100번 명령하던 1번만 실행한다.

<button onClick={() => {
  	setCount(count + 1); // 첫번째 줄 
  	setCount(count + 1); // 두번쨰 줄
  	setCount(count + 1); // 세번째 줄
}}
>

 

반면에 함수형 업데이트 방식은 3번을 동시에 명령을 내리면, 그 명령을 모아 순차적으로 각각 1번씩 실행시킨다. 현재값 0에 1더하고, 그 다음 1에 1을 더하고, 2에 1을 더해서 3이라는 결과가 나오는 것을 확인할 수 있다.

<button onClick={() => {
          setCount((previousState) => previousState + 1);
          setCount((previousState) => previousState + 1);
          setCount((previousState) => previousState + 1);
        }}
>

 

 

 

예시

자식 컴포넌트에 버튼을 눌렀을 때 부모 컴포넌트에 카운트가 증가하도록 하는 코드를 구현해보자.

import { useState } from 'react';

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

	return (
		<div>
			<h1>여기는 부모컴포넌트입니다.</h1>
			<span>현재 카운트 : {count}</span>
			<Child setCount={setCount}/> 
		</div>
	)
}

export default App;

 

Child 컴포넌트에 setCount만 props로 넘겨주면 된다. 함수형으로 값을 업데이트해줄 것이기때문에 count 까지 넘겨주어 setCount(count + 1) 을 해주는 것은 불필요한 props 전달이기때문이다. 

<Child setCount={setCount}/>
const Child = (props) => {
	const handleAddCound = () => {
		setCount((prev) => prev + 1);
	}

	return <button oncClick={handleAddCound}>Count 1 증가</button>
}

export default Child;

 

 

 

 

 

useEffect()

 

기본 문법

리액트 컴포넌트가 렌더링 된 이후마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다. 컴포넌트가 마운트 / 언마운트 / 업데이트 됐을 때 특정 작업을 처리할 수 있다.

useEffect(setup, dependencies?)

 

 

useEffect는 컴포넌트가 렌더링된 이후에 실행된다는 것이 핵심 포인트이다. 아래와 같은 코드는 input 태그를 만들어 state와 연결해준 상태이다. input창에 값을 입력했을 때 state가 변경하므로 리렌더링이 되면서 console.log 값이 계속해서 찍히는 것을 확인할 수 있다. 

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

const App = () => {
  const [value, setValue] = useState("");

  useEffect(() => {
    console.log("hello useEffect");
  });

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(event) => {
          setValue(event.target.value);
		      console.log("value => ", value);
        }}
      />
    </div>
  );
}

export default App;

 

1. 인풋 값 입력 > 2. state 변경 > 3. 리렌더링 > 4. 렌더링이 되었으니 useEffect 다시 실행 > 반복 과 같은 흐름이다.

 

 

 

의존성 배열(defendency array)

 

Hook이 불필요하게 반복해서 실행되는 것을 방지하여 성능을 최적화하기 위해서 사용한다.

// useEffect의 두번째 인자가 의존성 배열이 들어가는 곳
useEffect(()=>{
	// 실행하고 싶은 함수
}, [의존성배열]) //[] 이 배열에 값을 넣으면 그 값이 바뀔 때만 useEffect를 실행할게

✔️ Hook의 재실행 조건 설정

- 의존성 배열에 포함된 값들이 변경될 때만 훅이 재실행되도록 조건을 설정한다. 이를 통해 불필요한 실행을 방지할 수 있다.

 

✔️ 최신 상태 유지

- 의존성 배열을 사용하면 Hook이 항상 최신 상태의 값을 참조할 수 있다. 이를 통해 불필요한 연산을 방지할 수 있다.

 

✔️ 의존성 관리의 명확성

- 의존성 배열을 사용하면 훅이 어떤 값에 의존하고 있는지 명확하게 파악할 수 있다. 이는 코드의 가독성을 높이고, 의도하지 않은 의존성 관계를 방지할 수 있다.


 

 

의존성 배열이 비어있는 경우는, 렌더링된 이후 딱 한번만 실행하겠다는 뜻이다. 렌더링 이후 한번만 실행하고 싶다면 빈 배열을 넣어주면 된다. 

// src/App.js

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

const App = () => {
  const [value, setValue] = useState("");
  useEffect(() => {
    console.log("hello useEffect");
  }, []); // 비어있는 의존성 배열

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(event) => {
          setValue(event.target.value);
        }}
      />
    </div>
  );
}

export default App;

 

 

반대로 의존성 배열이 채워져 있는 경우에는, 이 배열의 값이 변경될 때만 실행하겠다는 뜻이다. 인풋의 값이 바뀔 때마다 실행한다.

// src/App.js

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

const App = () => {
  const [value, setValue] = useState("");
  useEffect(() => {
    console.log("hello useEffect");
  }, [value]); // value를 넣음

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(event) => {
          setValue(event.target.value);
        }}
      />
    </div>
  );
}

export default App;

 

 

 

클린 업(Clean up)

컴포넌트가 언마운트 즉, 사라졌을 때 실행하는 함수이다. 

useEffect(()=>{
	// 화면에 컴포넌트가 나타났을(mount) 때 실행하고자 하는 함수를 넣어주세요.

	return ()=>{
	// 화면에서 컴포넌트가 사라졌을(unmount) 때 실행하고자 하는 함수를 넣어주세요.
	}
}, [])

 

 

 

 

 

 

 

useRef()

 

기본 문법

리렌더링과 상관없이 값을 기억하기 위해 사용된다. 이 특징을 이용해 자바스크립트 DOM API를 직접 사용하지 않고 DOM 요소를 다루기 위한 용도로도 자주 사용된다. 

const 변수명 = useRef(초기값)

 

 

 

useRef()에 초기값을 주고 실행해보면 current : '초기값' 을 확인할 수 있다. 이는 current라는 키값을 지닌 프로퍼티가 생성되고, 값에 어떤 변경을 줄때도 이 current를 이용해서 한다는 점이다.

import "./App.css";
import { useRef } from "react";

function App() {
  const ref = useRef("초기값");
  console.log("ref 1", ref);

  return <div></div>;
}

export default App;

 

아래 코드와 같이 ref.current 의 값을 바꾼 값으로 변경하고 실행해보면 다음과 같다.

import "./App.css";
import { useRef } from "react";

function App() {
  const ref = useRef("초기값");
  console.log("ref 1", ref);

  ref.current = "바꾼 값";
  console.log("ref 1", ref);

  return <div></div>;
}

export default App;

 

이렇게 설정된 ref 값은 컴포넌트가 계속해서 렌더링 되어도 언마운트 전까지 값을 유지하며 currnet 속성은 값을 변경해도 상태를 변경할 때 처럼 리액트 컴포넌트가 리렌더링 되지 않는다는 특징이 있다. 

 

 

 

 

저장공간

state와 같이 어떤 값을 저장하는 공간으로 사용한다. 대신 state는 리렌더링이 필요한 값을 다룰때 사용하고, ref는 변하지 않는 값을 다룰 때 사용하자. 변화는 감지하지만 그 변화가 렌더링을 발생시키지 않을 때! 사용하기!

Hook rendering change
useState() Y 내부 변수 초기화
useRef() N 값 유지