본문 바로가기
개발/react

React Hook 개념 및 활용

by 궁즉변 변즉통 통즉구 2023. 9. 23.
반응형

React Hook은 v16.8부터 React 요소로 새로 추가된 기능으로 함수형 컴포넌트에서도 클래스형 컴포넌트의 기능을 사용할 수 있게 하는 여러가지 기술을 Hook 이라고 부른다. 예를들어, 기존 함수형 컴포넌트에서 할 수 없었던 상태값 관리(useState), 생명주기 함수(useEffect) 등을 사용할 수 있게 되었다.

 

클래스형 컴포넌트의 단점

- 컴포넌트의 상태 로직 재사용의 어려움

- 복잡한 컴포넌트의 이해하기 어려움

- 코드의 재사용성과 구성의 어려움

- 클래스의 문법의 어려움

위와 같은 클래형 컴포넌트의 단점과 함수형 컴포넌트의 간결함과 편리함 때문에 함수형 컴포넌트가 많이 활용되고 있다. 그리고 이 함수형 컴포넌트를 좀 더 효율적으로 잘 활용할 수 있도록 지원해주는 기능이 Hook이라고 보면 될 것 같다.

 

이제 자주 사용되는 Hook 메소드들을 알아보자

1. useState

상태를 관리하는 Hook이다. useState는 다음과 같은 특징을 갖는다

- 현재 상태를 나타내는 state값과, 이 값을 변경하는 set 함수로 구성

- state는 컴포넌트가 다시 렌더링 되어도 그대로 유지 됨

- state는 초기 값을 받을 수 있음(초기 값은 첫번째 렌더링 시에만 사용됨)

- state는 다양한 타입이 가능(객체, 문자열, 숫자, 배열 등)

 

아래 샘플에서 숫자 타입의 useState를 사용했고, 버튼 클릭을 통해 이 숫자 state를 변경한다.

import React, { useState } from "react";

const Counter = () => {
  const [value, setValue] = useState(0); // useState 선언

  return (
    <div>
      {/* state 값 조회  */}
      <p>Count: {value}</p>  
      
      {/* setValue로 state 값 변경  */}
      <button onClick={() => setValue(value + 1)}>+1</button>  
      <button onClick={() => setValue(value - 1)}>-1</button>
    </div>
  );
};

export default Counter;

 

 

2. useEffect

클래스형 컴포넌트의 라이프사이클 메소드 지원 Hook(ex. 렌더링 직후 작업 설정 등)
  - componentDidMount 나 componentDidUpdate, componentWillUnmount 를 하나의 API로 통합

  - useEffect(function, deps) 형식. 2번째 파라메타 배열에 어떤 값을 넣는지에 따라 실행조건이 달라짐

 

아래 코드에서 useEffect 2가지 예시가 있는데 주석을 잘 보면서 2가지를 확인해보자

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

const Info = () => {
  const [name, setName] = useState("");
  const [nickName, setNickName] = useState("");

  // 예시 1	
  useEffect(() => {
       console.log("렌더링 완료!!");
       console.log(name, nickName);
     }, []); // 처음렌더링 시에만 실행, 업데이트 시에는 실행하지 않기 위해 2번째 파라메타에 빈 배열([]) 입력

  // 예시 2
  useEffect(() => {
    console.log("렌더링 완료!!");
    console.log(name);
    return () => {
      // 컴포넌트 언마운트 되기전, 업데이트 되기 직전 수행 시 cleanup 함수 RETURN
      // 언마운트 되기전에만 실행 시 useEffect() 2번째 배열을 빈배열로 처리 한다
      console.log("cleanup", name);
    };
  }, [name]); // 'name' 이 변경될때만 실행(배열의 의존하는 값을 넣어줌)

  const onChangeName = (e) => {
    setName(e.target.value);
  };

  const onChangeNickName = (e) => {
    setNickName(e.target.value);
  };

  return (
    <div>
      <div>
        <input type="text" value={name} onChange={onChangeName} />
        <input type="text" value={nickName} onChange={onChangeNickName} />
      </div>
      <div>
        <div>
          <b>Name: </b> {name}
        </div>
        <div>
          <b>NickName: </b> {nickName}
        </div>
      </div>
    </div>
  );
};

export default Info;

참고로 로컬 개발환경(React.StrictMode가 적용된 상태)에서는 useEffect 코드가 2번 실행되는데 useEffect 사용한 코드에 문제를 감지하기 위해서 2번 실행되는 것으로 신경안써도 된다.

 

 

3. useContext

기존 React의 Context를 더 편리하게 사용할 수 있게 해준다. 

Context: 각 단계마다 props를 넘져주지 않고 컴포넌트 트리 전체에 데이터를 제공할 수 있는 것

Context API를 사용하기 위해서는 Provider, Consumer, createContext 알아야 함

- createContext: context 객체 생성

- Provider: 생성한 context를 하위 컴포넌트에 전달

- Consumer: context의 변화를 감시하는 컴포넌트

 

// App.js 파일

import React, { createContext } from "react";
import Children from "./Children";

export const AppContext = createContext();  // AppContext 객체 생성

const App = () => {
  const user = {
    id: "user1",
    name: "사용자1"
  };

  return (
    <>
      {/* context provider 에 user 값을 전달  */}
      <AppContext.Provider value={user}>
        <div>
          <Children />
        </div>
      </AppContext.Provider>
    </>
  );
};

export default App;



// Children.js 파일

import React, { useContext } from "react";
import { AppContext } from "./App";

const Children = () => {
  const user = useContext(AppContext);  // useContext를 이용해서 props를 불러옴
  return (
    <>
      <h3>user의 name은 {user.name}입니다.</h3>
      <h3>user의 age는 {user.age}입니다.</h3>
    </>
  );
};

export default Children;

 

 

4. useMemo

컴포넌트 내부 연산 최적화를 위한 Hook이다. 

렌더링 과정에서 특정 값이 바뀌었을때만 연산을 실행하고 원하는 값이 바뀌지 않았다면 이전값을 재사용하는 방식.

성능 최적화를 위해 연산된 문자열, 숫자 객체 등의 '값'을 재활용 한다.

 

아래 예시에서 리스트의 숫자 값의 평균을 계산하는 함수에 useMemo를 적용했다. useMemo를 적용하기 전에는 input의 값만 변경되어도(onChange) 평균 계산 함수가 실행되는데,  useMemo를 적용하면 list의 값이 변경이 있을 경우에만(onInsert) 평균 계산 함수가 실행된다.

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

// 평균 계산 함수
const getAverage = (nums) => {
  console.log("평균 계산 실행!!");
  if (nums.length === 0) return 0;
  const sum = nums.reduce((a, b) => a + b);
  return sum / nums.length;
};

const Average = () => {
  const [list, setList] = useState([]);
  const [num, setNum] = useState("");

  const onChange = (e) => {
    setNum(e.target.value);
  };

  // list에 값 추가
  const onInsert = (e) => {
    const nextList = list.concat(parseInt(num));
    setList(nextList);
    setNum("");
  };

  // useMemo 활용
  const avg = useMemo(() => getAverage(list), [list]);

  return (
    <div>
      <input value={num} onChange={onChange} />
      <button onClick={onInsert}>ADD</button>
      <ul>
        {list.map((val, idx) => (
          <li key={idx}>{val}</li>
        ))}
      </ul>
      {/*  useMemo()활용: list 배열이 변경될때만 getAverage() 함수 호출 됨 */}
      <div>평균값: {avg}</div>
      
      {/* useMemo() 미활용: Input 값만 바뀌어도 평균값이 계속 계산(getAverage()호출) 됨 */}
      <div>평균값: {getAverage(list)}</div>  
      
    </div>
  );
};

export default Average;

 

 

5. useCallback

렌더링 성능 최적화를 위해 주로 사용

  • 컴포넌트 리렌더링 될때마다 함수도 매번 다시 만들어짐(비효율적)
  • 함수가 자식 컴포넌트 props로 전달될때도 자식 컴포넌트 리렌더링 발생함(비효율적)

컴포넌트가 렌더링 할때마다 생성하는 함수를 필요할때만 생성하도록 함

값이 아닌 함수의 재사용

컴포넌트 렌더링이 자주 발생하거나, 렌더링 해야할 컴포넌트의 개수가 많은 경우 주로 활용

props로 전달해야 할 함수 작성 시 useCallback 사용해서 함수를 감싸는 것이 좋음

 

import React, { useState, useMemo, useCallback } from "react";

const getAverage = (nums) => {
  console.log("평균 계산중..");
  if (nums.length === 0) return 0;
  const sum = nums.reduce((a, b) => a + b);
  return sum / nums.length;
};

const Average = () => {
  const [list, setList] = useState([]);
  const [num, setNum] = useState("");

  // useCallback 적용 1
  const onChange = useCallback((e) => {
    setNum(e.target.value);
  }, []); // 컴포넌트가 처음 렌더링될 때만 함수 생성(2번째 파라메타 빈배열[] 넘겨줌)

  // useCallback 적용 2
  const onInsert = useCallback(
    (e) => {
      const nextList = list.concat(parseInt(num));
      setList(nextList);
      setNum("");
    },
    [num, list] // num or list가 변경됐을 때만 함수 생성
  );
 
  const avg = useMemo(() => getAverage(list), [list]);

  return (
    <div>
      <input value={num} onChange={onChange} />
      <button onClick={onInsert}>ADD</button>
      <ul>
        {list.map((val, idx) => (
          <li key={idx}>{val}</li>
        ))}
      </ul>
      <div>평균값: {avg}</div>{" "}
    </div>
  );
};

export default Average;

 

 

6. useRef

특정 DOM을 선택할 수 있게 하는 Hook(함수형 컴포넌트에서 ref 지원)

 - DOM을 직접 선택해야하는 경우: 엘리먼트 크기, 스크롤바 위치, 엘리먼트 포커스 설정 등 필요한 경우

useRef 객체 내의 current 값이 실제 엘리먼트를 가리킴

ref 내부의 값은 변경해도 컴포넌트가 리렌더링 되지 않음

로컬 변수(렌더링과 관련이 없는 변수)로 활용 가능

 

아래 샘플은 dom ref와 로컬변수 2가지 모두를 적용한 샘플이다.

import React, { useState, useMemo, useCallback, useRef } from "react";

const getAverage = (nums) => {
  console.log("평균 계산중..");
  if (nums.length === 0) return 0;
  const sum = nums.reduce((a, b) => a + b);
  return sum / nums.length;
};

const Average = () => {
  const [list, setList] = useState([]);
  const [num, setNum] = useState("");
  const inputEl = useRef(null); // useRef() 선언
  const localVar = useRef(1); // useRef() 로컬변수 선언

  const onChange = useCallback((e) => {
    setNum(e.target.value);
    localVar.current += 1;  // 로컬 변수 변경
  }, []);

  const onInsert = useCallback(
    (e) => {
      const nextList = list.concat(parseInt(num));
      setList(nextList);
      setNum("");

      inputEl.current.focus(); // 해당 DOM에 focus() 호출
      console.log(localVar.current); // 로컬변수 출력
    },
    [num, list]
  );

  const avg = useMemo(() => getAverage(list), [list]);

  return (
    <div>
      {/*  선택하고 싶은 DOM에 (여기서는 input) 속성으로 ref값 설정 */}
      <input value={num} onChange={onChange} ref={inputEl} />  
      <button onClick={onInsert}>ADD</button>
      <ul>
        {list.map((val, idx) => (
          <li key={idx}>{val}</li>
        ))}
      </ul>
      <div>평균값: {avg}</div>{" "}
    </div>
  );
};

export default Average;

 

 

7. useReducer

useState보다 다양한 컴포넌트 상황에 따라 다양한 상태 업데이트 가능

다수의 하위값을

컴포넌트 업데이트 로직을 컴포넌트 외부로 뺄수 있는 장점있음

 

본문 상단의 'useState'의 샘플 코드를 useReducer를 적용하면 다음과 같다.

import React, { useReducer } from "react";

function reducer(state, action) {
  console.log(state, action);

  // action.type에 따라 작업 수행
  switch (action.type) {
    case "INC":
      return { value: state.value + 1 };
    case "DEC":
      return { value: state.value - 1 };
    default:
      return state;
  }
}

const Counter = () => {
  // useReducer 적용(1번째 파라메타: Reducer함수, 2번째 파라메타: 초기 값)
  const [state, dispatch] = useReducer(reducer, { value: 0 });

  return (
    <div>
      <p>Count: {state.value}</p>
      <button onClick={() => dispatch({ type: "INC" })}>+1</button>
      <button onClick={() => dispatch({ type: "DEC" })}>-1</button>
    </div>
  );
};

export default Counter;

 

 

8. Custom Hook(사용자 정의 Hook)

여러 컴포넌트에서 비슷한 기능을 공유할 경우 커스텀 Hook을 직접 작성하여 로직 재사용 가능

 

// useInputs.js 파일, useInputs() custom hook 작성

import React, { useReducer } from "react";

// reducer 함수
function reducer(state, action) {
  return {
    ...state,
    [action.name]: action.value,
  };
}

export default function useInputs(initForm) {
  const [state, dispatch] = useReducer(reducer, initForm);

  const onChange = (e) => {
    dispatch(e.target);
  };

  return [state, onChange];
}



// Info.js, useInputs() custom hook 활용

import useInputs from "./useInputs";

const Info = () => {
  const [state, onChange] = useInputs({   // useInputs Hook 적용
    name: "",
    nickName: "",
  });
  const { name, nickName } = state;

  return (
    <div>
      <div>
        <input type="text" name="name" value={name} onChange={onChange} />
        <input
          type="text"
          name="nickName"
          value={nickName}
          onChange={onChange}
        />
      </div>
      <div>
        <div>
          <b>Name: </b> {name}
        </div>
        <div>
          <b>NickName: </b> {nickName}
        </div>
      </div>
    </div>
  );
};

export default Info;

 

 

기타

좀 더 다양하고 추가적인 내용은 다음 사이트들을 참조하자

- Built-in React Hooks: https://react.dev/reference/react

- https://github.com/rehooks/awesome-react-hooks

 

 

참고:

https://velog.io/@khy226/%EC%9E%90%EC%A3%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-React-Hook-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0

https://velog.io/@velopert/react-hooks

반응형

댓글