본문 바로가기

공부/react

React 숙련 2 (react hooks)

반응형

useState 📖

가장 기본적인 hook이며, 함수 컴포넌트에서 가변적인 상태를 가집니다.

 

사용 방법

구조분해할당으로 상태값 `state`와 상태를 변경하는 함수 `setState`를 사용할 수 있습니다.

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

 

함수형 업데이트

기존 업데이트 방식과 `함수형 업데이트 방식`이 있습니다.

// 기존에 우리가 사용하던 방식
setState(number + 1);

// 함수형 업데이트 
setState(() => {});

 

함수형 업데이트 방식을 이용하면 setState `() 안에`함수를 넣을 수 있고 인자로 현재의 state를 가져올 수 있습니다.

 

두 방식의 차이점

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

onClick={() => {
  setState(state + 1);
  setState(state + 1);
  setState(state + 1);
  // 결과 : 1
}}

onClick={() => {
  setState((prev) => prev + 1);
  setState((prev) => prev + 1);
  setState((prev) => prev + 1);
  // 결과 : 3
}}

 

두 결과값이 다르게 나오는 걸 확인할 수 있습니다.

 

왜 다르게 동작할까

 

일반 업데이트 방식은 setState가 각각 실행되는 것이 아니라 배치(batch)로 처리합니다. 리액트는 명령을 하나로 모아 최종적으로 한 번만 실행을 시킵니다. 그래서 1번만 실행됩니다.

 

함수형 업데이트 방식은 3번을 동시에 명령을 내리면 명령을 모아 순차적으로 1번씩 실행시킵니다. 

 

ex)

0->1

1->2

2->3

이라는 결과가 나오게 됩니다.

 

useEffect 📖

리액트 컴포넌트가 렌더링 된 이후 특정 작업을 수행하도록 설정할 수 있는 hook입니다.

 

사용 방법

컴포넌트가 화면에 렌더링 된 이후 useEffect 첫 번째 인자인 함수에 console.log()가 실행됩니다.

useEffect(() => {
  // 이 부분이 실행된다.
  console.log("hello useEffect");
});

 

 

그러나 아래 코드는 input 태그에 onChange가 발생하면 화면이 리렌더링 되면서 `hello useEffect`를 계속 출력하게 됩니다.

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;

 

이런 문제를 useEffect의 두 번째 인자인 의존성 배열로 해결할 수 있습니다.

 

의존성 배열

의존성 배열에 있는 값이 바뀔 때만 useEffect만 실행시킬 수 있습니다.

useEffect(()=>{
	// 실행하고 싶은 함수
}, [count])

 

`빈 배열 일 경우` : 최소 렌더링 이후 한 번만 실행되고, 그 이후로는 실행되지 않습니다.

`배열에 값이 있는 경우` : 의존성 배열에 있는 값이 변하게 되면 useEffect 실행

 

clean up

컴포넌트가 unmount(=사라질 때) 될 때 동작하는 함수이고 return 부분이 실행이 됩니다.

useEffect(()=>{
    return () => {
      console.log('hi');
    }
}, [])

 

라이프 사이클

`클래스형 컴포넌트`를 사용했을 때 생애주기와 관련된 여러 메서드가 존재했지만

LifeCycle

 

`함수형 컴포넌트`를 사용할 때는 useEffect를 사용해 핸들링합니다.

출처 : Dev Community

 

useRef 📖

useRef는 저장공간 또는 DOM 요소에 접근하기 위해 사용되는 hook입니다. 특정 DOM 요소에 접근이 가능하고, 리렌더링을 하지 않습니다.

 

사용 방법

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

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

  return (
    <div>
      <p>useRef에 대한 이야기에요.</p>
    </div>
  );
}

export default App;

 

ref 객체

 

console.log(ref)를 출력하게 되면 `current`를 가지고 있는 객체를 출력하게 됩니다.

 

변경도 가능합니다.

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

 

변경 후

 

용도

useState와 비슷한 역할을 하지만 state는 변화가 일어나면 리렌더링이 일어나지만 ref에 저장한 값은 렌더링이 발생하지 않습니다.

 

여기서 제어 컴포넌트 & 비제어 컴포넌트 개념이 등장합니다.

 

`제어 컴포넌트` : React에 의해 값이 제어되는 컴포넌트 

`비제어 컴포넌트`: React에 의해 값이 제어되지 않는 컴포넌트 

 

제어 컴포넌트 예시

import React, { useState } from 'react';

function Input() {
  const [state, setState] = useState(null);

  const handleChange = (e) => {
    setState(e.target.value);
  };

  return <input onChange={(e) => handleChange(e)} value={state} />;
}

 

input의 값을 state로 관리하고 사용자가 값을 입력할 때마다 handleChange를 통해 state 값을 업데이트해주는 코드입니다. 값이 바뀔 때마다 화면이 렌더링 됩니다.

 

 

비제어 컴포넌트 예시

function Input() {
  const ref = useRef(null);

  return <input ref={ref} type="text" />;
}

 

사용자만이 input값을 상호작용할 수 있고 필요한 시점에 이벤트 핸들러를 통해 ref에 저장된 엘리먼트의 값을 가져와 활용합니다. 비제어 컴포넌트는 state로 값을 관리하지 않기 때문에 리렌더링이 발생하지 않습니다.

 

일반적으로 제어 컴포넌트를 사용하는 것이 좋습니다. 

 

useContext 📖

prop drilling 문제점

부모 -> 자식 컴포넌트로 데이터를 전달할 때 깊이가 너무 깊어지면 데이터 흐름 파악이 어려워지고 오류 추적이 힘들어집니다.

https://www.copycat.dev/blog/react-context/

 

그래서 contextAPI가 등장했고 전역 데이터 관리를 할 수 있습니다.

 

사용 방법

context만들기

 

`FamilyContext.js` 파일을 만들고 createContext를 return 해줍니다.

import { createContext } from "react";

export const FamilyContext = createContext(null); // null은 초기값

 

`FamilyContext.Provider`로 태그를 만들어 데이터를 전달할 컴포넌트를 감싸줍니다. 그리고 value 속성에는 전달할 데이터 값을 작성해 줍니다. 

import React from "react";
import Father from "./Father";
import { FamilyContext } from "../context/FamilyContext";

function GrandFather() {
  const houseName = "곰돌이";
  const pocketMoney = 10000;

  return (
    <FamilyContext.Provider value={{ houseName, pocketMoney }}>
      <Father />
    </FamilyContext.Provider>
  );
}

export default GrandFather;

 

`FamilyContext.Provider`로 감싼 컴포넌트들은 value로 보낸 데이터를 공유해서 사용할 수 있습니다.

 

 

context를 사용할 해당 컴포넌트에서 import를 해주고 `useContext()`를 사용해 반환받은 값을 사용합니다.

import { FamilyContext } from '위치 경로';

const data = useContext(FamilyContext);

 

주의 사항

useContext를 사용할 때 Provider에서 제공한 value가 달라지면 useContext를 사용하고 있는 모든 컴포넌트가 리렌더링 되기 때문에 렌더링 이슈가 생길 수 있습니다.

 

Custom hooks 📖

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

const App = () => {
	// input의 갯수가 늘어날때마다 state와 handler가 같이 증가한다.
  const [title, setTitle] = useState("");
  const onChangeTitleHandler = (e) => {
    setTitle(e.target.value);
  };

	// input의 갯수가 늘어날때마다 state와 handler가 같이 증가한다.
  const [body, setBody] = useState("");
  const onChangeBodyHandler = (e) => {
    setBody(e.target.value);
  };

  return (
    <div>
      <input
        type="text"
        name="title"
        value={title}
        onChange={onChangeTitleHandler}
      />
      <input
        type="text"
        name="title"
        value={body}
        onChange={onChangeBodyHandler}
      />
    </div>
  );
};

export default App;

 

위 코드는 input 개수가 증가하면 useState, 이벤트 핸들러도 같이 증가해서 중복이 생기게 됩니다. 그래서 중복되는 코드를 커스텀 훅을 통해 관리할 수 있습니다.

 

 

hooks 폴더 생성

 

useState와 핸들러를 따로 빼놓고 커스텀 훅을 구성했습니다.

import React, { useState } from "react";

const useInput = () => {
	// 2. value는 useState로 관리하고, 
  const [value, setValue] = useState("");

	// 3. 핸들러 로직도 구현합니다.
  const handler = (e) => {
    setValue(e.target.value);
  };

	// 1. 이 훅은 [ ] 을 반환하는데, 첫번째는 value, 두번째는 핸들러를 반환합니다.
  return [value, handler];
};

export default useInput;

 

 

리팩토링 후 동일하게 동작하지만 중복코드를 제거해서 코드의 양도 감소했습니다.

import useInput from "./hooks/useInput";

const [title, onChangeTitleHandler] = useInput();
const [body, onChangeBodyHandler] = useInput();

 


출처 🏷️

https://velog.io/@thumb_hyeok/%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-vs-%EB%B9%84%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8

반응형