React Hooks

nara_lee·2025년 3월 12일
0
post-thumbnail

Table of Contents
1. useState
2. useEffect
3. useReducer
4. useMemo
5. useCallback
6. useRef
7. Customized Hooks
8. Other Hooks

1. useState

  • Function Component 에서도 state 관리할 수 있게함.
import React, { useState } from "react";

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

  return (
    <div>
      <p>
        현재 카운터 값은 <b>{value}</b>입니다
      </p>
      <button onClick={() => setValue(value + 1)}>+1</button>
      <button onClick={() => setValue(value - 1)}>-1</button>
    </div>
  );
};

export default Counter;

useState 사용시 주의할 점

function createBulkTodo(){
  ...}

(...)
const App = () => {
const [todos, setTodos] = useState(createBulkTodo);
(...)
}

⚠️ useState의 기본 값에 createBulkTodo()를 넣었으면 리렌더링 될 때마다 createBulkTodo 함수가 호출되지만 createBulkTodo 파라미터를 함수 형태로 넣어 주면 컴포넌트가 처음 렌더링 될 때만 createBulkTodo 함수가 호출된다.

2. useEffect

useEffect(() => {
  console.log("렌더링이 완료되었습니다!");
}, []);
  • [] 는 옵션. 생략 되었을 시 모든 state가 업데이트 될 때마다 실행된다.
  • []안에 있는 state는 업데이트 될 때마다 실행; 그 밖에 있는 state 는 컴포넌트의 마운트/언마운트 때만 실행.
  • 대부분의 경우 (빈 배열이나 의존 값이 들어있는 배열을 넣는 경우는 있어도) 배열을 아예 생략하는 상황은 거의 없다.

의존 값이 있는 경우 예시

const [name, setName] = useState("");
const [nickname, setNickname] = useState("");
useEffect(() => {
  console.log("렌더링이 완료되었습니다!"); 
}, [name]);

component가 언마운트 되기 직전이나 업데이트 되기 직전에 어떠한 작업을 수행하고 싶다면?

  • useEffect() 에서 cleanup 함수 반환
  • 오직 언마운트 될 때만 cleanup함수를 호출하고 싶다면 [name] 대신 []을 넣자.

cleanup function:

useEffect(() => {
  console.log("effect");
  console.log(name);
  return () => {
    console.log("cleanup");
    console.log(name);
  };
}, [name]);
import Info from "./Info";
import { useState } from "react";

const App = () => {
  const [visible, setVisible] = useState(false);

  return (
    <div>
      <button
        onClick={() => {
          setVisible(!visible);
        }}
      >
        {visible ? "숨기기" : "보이기"}
      </button>
      <hr />
      {visible && <Info />}
    </div>
  );
};

export default App;

3. useReducer

  • useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트해 주고 싶을 때 사용하는 Hook이다.
  • 현재 상태, 그리고 업데이트를 위해 필요한 정보를 담은 action값을 전달받아 새로운 상태를 반환하는 함수.
  • ⚠️ 리듀서 함수에서 새로운 상태를 만들 때는 반드시 불변성을 지켜주어야 한다.
  • 가장 큰 장점은 컴포넌트 업데이트 로직을 컴포넌트 바깥으로 빼낼 수 있다는 것
import React, { useReducer } from "react";

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

const Info = () => {
  const [state, dispatch] = useReducer(reducer, {
    name: "",
    nickname: "",
  });
  const { name, nickname } = state;
  const onChange = (e) => {
    dispatch(e.target);
  };

  return (
    <div>
      <div>
        <input name="name" value={name} onChange={onChange} />
        <input 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;

4. useMemo

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

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

const Average = () => {
  const [list, setList] = useState([]);
  const [number, setNumber] = useState('');
  const inputEl = useRef(null);

  const onChange = useCallback(e => {
    setNumber(e.target.value);
  }, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성
  const onInsert = useCallback(() => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber('');
    inputEl.current.focus();
  }, [number, list]); // number 혹은 list 가 바뀌었을 때만 함수 생성

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

  return (
    <div>
      <input value={number} onChange={onChange} ref={inputEl} />
      <button onClick={onInsert}>등록</button>
      <ul>
        {list.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
      <div>
        <b>평균값:</b> {avg}
      </div>
    </div>
  );
};

export default Average;
// Before
(useMemo가 없음)

(...)

<div>
  <b>평균값:</b> {getAverage(list)}
</div>

해당 코드에서는 input value 가 onchange 일 때는 평균값을 다시 계산할 필요가 없는데 숫자를 등록할 때뿐만 아니라 인풋 내용이 수정될 때도 (즉 리렌더링 될 때마다) 우리가 만든 getAverage함수가 호출되는 것을 확인할 수 있다.

useMemo는 렌더링하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고, 원하는 값이 바뀌지 않았다면 이전에 연산했던 결과를 다시 사용한다.

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

(...)
 
 <div>
 	<b>평균값:</b> {avg}
 </div>

5. useCallback

  1. useMemo 에 있는 예제 참고
// Before
const onChange = e => {
  setNumber(e.target.value);
}; 
const onInsert = e => {
  const nextList = list.concat(parseInt(number));
  setList(nextList);
  setNumber('');
}; 

👎 The code above forces components to use newly created function whenever rerendering happens.

// After
const onChange = useCallback(e => {
  setNumber(e.target.value);
}, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성
const onInsert = useCallback(() => {
  const nextList = list.concat(parseInt(number));
  setList(nextList);
  setNumber('');
  inputEl.current.focus();
}, [number, list]); // number 혹은 list 가 바뀌었을 때만 함수 생성

props로 전달해야 할 함수를 만들 때는 useCallback을 사용하여 함수를 감싸는 것을 습관화 (컴포넌트 성능 이슈)
If component renders often or there are many components that has to be rendered, use useCallback for optimization.

UseMemo 와 UseCallback 의 차이

차이UseMemoUseCallback
메모이제이션 대상계산 결과 값함수자체
사용목적비용이 높은 계산 결과를 재사용함수의 참조 일관성 유지, 불필요한 리렌더링 방지
사용 시나리오복잡한 계산자식 컴포넌트에 함수를 props를 전달하는 경우, 이벤트 핸들러가 자주 변경 되지 않아야 하는 경우

6. useRef

  1. useMemo 에 있는 예제 참고
const inputEl = useRef(null);

(...)
const onInsert = useCallback(() => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber('');
    inputEl.current.focus();
  }, [number, list]);

(...)
 
 <input value={number} onChange={onChange} ref={inputEl} />

7. Customized Hooks

// usePromise.js
import { useState, useEffect } from "react";

export default function usePromise(promiseCreator, deps) {
  // promiseCreater = 프로미스를 사용하는 (감싸는) 함수
  // deps = usePromise 내부에서 사용한 useEffect의 의존배열
  // 로딩중 / 완료 / 실패에 대한 상태 관리
  const [loading, setLoading] = useState(false);
  const [resolved, setResolved] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    const process = async () => {
      setLoading(true);
      try {
        const resolved = await promiseCreator();
        setResolved(resolved);
      } catch (e) {
        setError(e);
      }
      setLoading(false);
    };
    process();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, deps);

  return [loading, resolved, error];
}

8. Hook 관련 주의사항

useEffect 흔한 실수

  • 무한 루프: 렌더 => useEffect => setState => 렌더
  • 클린업 누락: 타이머나 리스너 헤제를 안해서 메모리 누수
  • 과도한 의존성: 필요 없는 의존성이 들어가면 빈번한 재실행
    Collection of React Hooks
    Hook Related Resources

본 후기는 [한글과컴퓨터x한국생산성본부x스나이퍼팩토리] 한컴 AI 아카데미 (B-log) 리뷰로 작성 되었습니다.

#한컴AI아카데미 #AI개발자 #AI개발자교육 #한글과컴퓨터 #한국생산성본부 #스나이퍼팩토리 #부트캠프 #AI전문가양성 #개발자교육 #개발자취업

0개의 댓글