프론트앤드/React

[포스코x코딩온] 웹개발자 입문 과정 9주차 | Hooks

영최 2023. 5. 7. 17:22
728x90

1.Hooks란?

 함수형 컴포넌트에서 React의 state와 lifecycle을 연동할 수 있게 해주는 함수이다.

 즉, 클래스형 컴포넌트의 기능을 함수형 컴포넌트에서 사용할 수 있도록 해주는 기능으로,

 이 기능이 있기에 이제 클래스형 컴포넌트를 함수형 컴포넌트가 완전히 대체할 수 있게 되었다.

 

 가.사용 규칙

  공식 문서에 따르면 Hook의 사용 규칙은 두가지이다.

  https://ko.legacy.reactjs.org/docs/hooks-rules.html#explanation

 

Hook의 규칙 – React

A JavaScript library for building user interfaces

ko.legacy.reactjs.org

 

  ✅ 첫번째, 함수형 컴포넌트에서만 Hook를 사용할 수 있다. 

  당연하다. 클래스형 컴포넌트는 이미 내장 모듈이 있고

  이걸 함수형 컴포넌트에서 사용하기 위해 만든 것이므로, 함수형 컴포넌트에서 사용해야한다.

  두번째, 최상위에서만 Hook을 호출해야한다.

  즉, 반복문, 조건문 또는 중첩된 함수 내에서 호출하면 안된다.

  쉽게 말하면 들여쓰기 맨앞에서 호출되어야한다.

  (최상위 호출이라고 해서 최상위 컴포넌트에서 호출되어야한다는 의미가 아니다.)

 

  아래 예시를 보자

  잘못된 예시 : 조건문 안에 Hook을 사용해서 두번째 규칙을 어겼다.

  if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
  }

  수정된 예시: 만약 if문을 써야한다면 useEffect 내에 작성하자.

  useEffect(function persistForm() {
    // 👍 더 이상 첫 번째 규칙을 어기지 않습니다
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
  });

 

나.Hooks 종류

  • useState() 
    • 함수형 컴포넌트에서 상태 관리를 위해 사용
    • 동적 상태 관리
    • 가장 기본적인 Hook으로 함수형 컴포넌트에서 가변적인 상태를 지니게 해준다.
    • 클래스형 컴포넌트에서의 State와 동일하다.
    • 사용방법: const [message,setMessage] = useState("")
  • useEffect()
    • sideEffect(부수효과; mount/ unmount/ update)를 수행한다.
    • 클래스형 컴포넌트의 componentDidMount와 componentDidUpdate가 합쳐진 형태이다.
    • 사용방법: 
      1. Mount & Update될 때 : useEffect(()=>{})
      2. Mount될 때만 (최초 렌더시) : useEffect(()=>{},[])
      3. 특정 값 Update시에 만: useEffect(()=>{},[data])
      4. Updata 직전 수행: useEffect(()=>{ return()=>{};}), cleanup(뒷정리 함수) 사용
  • useRef()
    • 함수형 컴포넌트에서 ref를 사용하기 쉽게 만들어주는 Hook
    • useRef()는 인자로 받은 값으로 초기화된 변경 가능한 ref객체를 반환한다.
    • ref.current로 현재 가리키는 객체에 접근할 수 있다.
    • 용도: 1) DOM요소 접근 2)로컬 변수로 사용
    • 사용 방법
      1. DOM요소 접근:
        1. const myRef = useRef();
        2. <element> ref = {myRef}</element>
        3. myRef.current;
      2. 로컬 변수로 사용:
        1. const myRef = useRef(초기값);
  • useMemo()
    • 함수형 컴포넌트 내부에서 발생하는 연산을 최적화시켜주는 Hook
    • Rendering과정에서 특정이 바뀌었을 때만 연산을 실행한다.
    • Rendering과정에서 두번째 인자로 받은 의존 배열(dependency) 내 값이 바뀌는 경우에만 첫번째 인자로 받은 콜백함수를 실행해 구한 값을 반환한다.
    • 사용 방법: const memoizedValue = useMemo(callback, dependency);
  • useCallback()
    • Rendering 최적화에 사용되는 Hook
    • useMemo와 유사하나, useMemo는 값을 최적화한다면, useCallback()은 다시 rendering될 때 함수를 다시 불러오는 것을 막는다.
    • 사용 방법: const memoizedCallback = useCallback(callback, dependency);
  • useReducer()
    • Reducer란? 현재 상태와 업데이트를 위해 필요한 정보를 담은 액션 값을 전달받아 새로운 상태를 반환하는 함수이다.
    • useReducer를 사용하면 컴포넌트 업데이트 로직을 컴포넌트 외부로 뺄 수 있다.
    • 또한 useState를 대체하여 다양한 컴포넌트 상황에 따라 상태값을 설정할 수 있다.
    • useReducer가 useState 기반이기는 하지만 더 좋은것은 아니고, 상황에 따라 더 맞는 hook을 설정하자.
      • state가 단순하다 => useState사용
      • state가 복잡하다 => useReducer사용 //객체,배열 등 하위 요소 많은 경우
    • 사용 방법:
      1. Reducer 정의: const [state, dispatch] = useReducer(reducer, initialState)
      2. dispatch 함수로 action값 전달: dispatch는 action값을 전달 받아 state와 함께 Reducer에 전달
        • <button  onClick={()=>dispatch('INCREMENT')}>Plus</button>
      3. Reducer: 현재 state와 action값을 전달 받아 새로운 state 반환
        • const reducer = (prevNumber,action) => {switch (action){ case 'INCREMENT': return prevNumber+1; default: return prevNumber;}}

 

2.예제

 예제를 통해 이전 포스팅에서 다뤄보지 못했던 useMemo(), useCallback(). useReducer()에 관해 알아보자.

 가.useMemo()

import { useState, useMemo } from "react";

//평균값 계산 함수
//숫자를 [등록] 버튼을 틀릭할 때 뿐만 아니라
//input내용을 수정할 때도 getAverage함수가 호출되고 있음
// ('평균값 계산중 ..!')이 input 내용 수정할 떄마다 콘솔에 출력됨
//=> useMemo를 사용해서 최적화할 수 있음 , 등록 버튼만 눌렀을 떄 실행 되도록

const getAverage = (numbers) => {
  //numbers: 숫자 저장하고 있는 배열
  console.log("평균값 계산중 ...!");

  //만약 numbers 배열에 원소가 없다면 평균값은0
  if (numbers.length === 0) return 0;
  // numbers 배열에 모든 원소 더함
  const sum = numbers.reduce((a, b) => a + b);
  // 평균값을 계산하여 반환
  return sum / numbers.length;
};
const useMemoTest = () => {
  const [number, setNumber] = useState("");
  const [list, setList] = useState([]);

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

  const onInsert = () => {
    const newList = list.concat(parseInt(number));
    setList(newList);
    setNumber("");
  };
  // [after] useMemo hook 적용
  // 랜더링 과정에서 list 값이 변경될 때만 callback 함수를 실행 =>  [list]
  // useMemo() 요약
  // '수행한 연산의 결과 값을 기억' 함으로써 계산을 최소화함
  const avg = useMemo(() => {
    return getAverage(list);
  }, [list]);

  return (
    <>
      <h1>useMemo hook</h1>

      <input type="number" value={number} onChange={onChange} />
      <button onClick={onInsert}>등록</button>

      <ul>
        {list.map((li, idx) => {
          return <li key={idx}>{li}</li>;
        })}
      </ul>
      {/* useMemo 사용전: <h1>평균값 : {getAverage(list)}</h1> */}
      <h1>평균값 : {avg}</h1>
    </>
  );
};

export default useMemoTest;

   

 나.useCallback()

import { useState, useCallback } from "react";

const useCallbackTest = () => {
  const [text, setText] = useState("");
  // 매번 전체 렌더링 ->onChangeText다시 만듦
  // useCallback 사용시 함수 저장해서 다시 만들지 않도록함
  // useCallback()
  // 반복해서 생성되는 이벤트 핸들러 함수를 useCallback으로 감싸주면
  // 함수를 메모이제이션(기억)해서 컴포넌트가 다시 랜더링되더라도
  // 기억하고 있는 기존 함수를 그대로 사용
  // 실행 결과가 같으나 내부적으로 성능은 향상됨

  // useCallback vs useMemo
  // const memoizedCallback  = useCallback(function,deps)
  // const memoizedValue  = useMemo(() => function,deps)

  // useCallback: useMemo를 기반으로 만든 hook
  // 단, "함수를 사용할 때" 편의성을 증진시킨 hook

  //공통점: 성능 최적화
  //차이점:
  // - useMemo: "값"을 재사용
  // => 값의 재사용을 위해 전달된 함수를 실행하고 "그 결과"를 메모이제이션
  // - useCallback: "함수"를 재사용
  // => "함수의 재사용"을 위해 전달된 "함수 자체"를 메모이제이션
  const onChangeText = useCallback((e) => {
    setText(e.target.value);
  }, []);
  return (
    <>
      <h1>UseCallback hook</h1>
      <input onChange={onChangeText} />

      <h2>작성한값:{text || "없음"}</h2>
    </>
  );
};

export default useCallbackTest;

 

 .useReducer()

import { useReducer } from "react";

const reducer = (prevNumber, action) => {
  //prevNumber: 현재 state
  //action: dispatch에서 인자로 받고 있는 현재 액션 값

  switch (
    action.type // {type: 'xx}
  ) {
    case "INCREMENT":
      return { value: prevNumber.value + 1 };
    case "DECREMENT":
      return { value: prevNumber.value - 1 };
    case "RESET":
      return { value: 7 };
    default:
      return { value: prevNumber.value };
  }
};

const UseReducerTest = () => {
  const [number, dispatch] = useReducer(reducer, { value: 7 });

  const increment = () => {
    dispatch({ type: "INCREMENT" });
  };
  const decrement = () => {
    dispatch({ type: "DECREMENT" });
  };

  const reset = () => {
    dispatch({ type: "RESET" });
  };

  return (
    <>
      <h1>useReducer hook</h1>
      <h2>{number.value}</h2>
      <button onClick={increment}>Plus</button>
      <button onClick={decrement}>Minus</button>
      <button onClick={reset}>Reset</button>
    </>
  );
};
export default UseReducerTest;

 

728x90