FrontEnd/React

[ React ] Hook

ChatjihoiPT 2025. 4. 7. 14:03

8단원. HOOK

함수형 컴포넌트에서도 상태 관리를 할 수 있는 useState
렌더링 직후 작업을 설정하는 useEffect


1. useState

- component 내에서 state를 관리시켜주며 초기값에서 사용

- component를 굳이 class형으로 바꿀 필요없음

숫자 카운터]

import React, { useState } from 'react';

const Info = () => {
  const [name, setName] = useState('');
  const [nickname, setNickname] = useState('');

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

  const onChangeNickname = e => {
    setNickname(e.target.value);
  };

  return (
    <div>
      <div>
        <input value={name} onChange={onChangeName} />
        <input value={nickname} onChange={onChangeNickname} />
      </div>
      <div>
        <div>
          <b>이름:</b> {name}
        </div>
        <div>
          <b>닉네임:</b> {nickname}
        </div>
      </div>
    </div>
  );
};

export default Info;

2. useEffect

- react component가 렌더링될때마다 특정 작업을 수행할 수 있도록 설정하는 HOOK

- 클래스형 컴포넌트의 componentDidMount와 componentDidUpdate를 합친 형태

- 컴포넌트가 언마운트되기 전이나 업데이트되기 직전에 어떠한 작업을 수행하고 싶다면 useEffect에서 뒷정리**(cleanup) 함수를 반환**

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

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

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

  const onChangeNickname = e => {
    setNickname(e.target.value);
  };

  return (
    (...)
  );
};

export default Info;

3.useReducer

- 다양한 컴포넌트의 상황에 따라 state의 상태 관리를 구조적으로 할 수 있는 Hook

- 상태 변화 로직을 하나의 함수(리듀서)에서 관리할 수 있어서 복잡한 상태 로직을 처리할 때 유용

- useREducer에서 사용하는 액션 객체는 반드시 type을 지니고 있을 필요가 없음
  (객체가 아니라 문자열이나 숫자도 상관없음)

const [state, dispatch] = useReducer(reducer, state객체)
function reducer(state, action) {
return {...}; // 불변성을 지키면서 업데이트한 새로운 상태를 반환합니다.
}

- reducer : dispatch에 의해 호출되어 state를 관리할 함수
- state객체 : 관리할 state

 

고정값]

reducer(state, action)

import React, { useReducer, useState } from "react";
import "./Todo.css";

const command = {
  ADD: "ADD",
  TOGGLE: "TOGGLE",
  REMOVE: "REMOVE",
};
// TODO 상태를 위한 reducer 함수
const reducer = (state, action) => {
  switch (action.type) {
    case command.ADD:
      return [...state, { id: Date.now(), text: action.text, compeleted: false }];
    case command.TOGGLE:
      return state.map((todo) =>
        todo.id === action.id ? { ...todo, compeleted: !todo.compeleted } : todo
      );
    case command.REMOVE:
      return state.filter((todo) => todo.id !== action.id);
    default:
      return state;
  }
};

const Todo = () => {
  const [todos, dispatch] = useReducer(reducer, []);
  const [text, setText] = useState("");
  return (
    <div className="app">
      <div className="todo-container">
        <h2>📋 Todo List</h2>
        <div className="input-container">
          <input
            value={text}
            onChange={(e) => {
              setText(e.target.value);
            }}
          />
          <button
            onClick={() => {
              dispatch({ type: command.ADD, text }); //text: 속성과 값이 똑같음
              setText("");
            }}
          >
            추가
          </button>
        </div>
        <ul className="todo-list">
          {todos.map((todo) => (
            <li className="todo-item">
              <div className={`todo-text ${todo.compeleted ? "completed" : ""}`}>{todo.text}</div>
              <button onClick={() => dispatch({ type: command.TOGGLE, id: todo.id })}>완료</button>
              <button onClick={() => dispatch({ type: command.REMOVE, id: todo.id })}>삭제</button>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
};

export default Todo;

4. useMemo

- 함수형 컴포넌트 내부에서 발생하는 연산을 최적화할 수 있음

- 렌더링하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고,

   원하는 값이 바뀌지 않았다면 이전에 연산했던 결과(기존의 값)를 다시 사용하는 방식

import React, { useState, useMemo } 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 onChange = e => {
    setNumber(e.target.value);
  };
  const onInsert = () => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber('');
  };

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

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

export default Average;

5. useCallback

- callback 함수를 재사용

- 렌더링 성능을 최적화할때 사용

- useCallback( callback,[ ])  : 컴포넌트가 처음 렌더링될 때만 함수를 생성

- useCallback( callback,[state])  :지정한 state가 변경될 때만 함수를 생성

- array.reduce(callback, initialValue)
배열의 총합, 평균, 합치기에 사용되는 배열 메서드

1. callback(accumulator, current, currentIndex, array)     
2. accumulator : reduce를 한 이전 결과, 처음인 경우  initialValue가 있으면 initialValue      
3. current : 처리할 현재 요소     
4. currentIndex : 처리할 현재 요소의 index,  initialValue가 있으면 0, 없으면 1부터 시작     
5. array : reduce를 시작한 배열

 

callback(accumulator, current, currentIndex, array)

  accumulator : reduce를 한 이전 결과, 처음인 경우  initialValue가 있으면 initialValue

  current : 처리할 현재 요소

  currentIndex : 처리할 현재 요소의 index,  initialValue가 있으면 0, 없으면 1부터 시작

  array : reduce를 시작한 배열

 

render 함수에서 호출되고 있기 때문에 list가 변하지 않았는데도
input 양식의 값이 변경되면 (값을 입력, 수정만 해도) number state가 변경되므로 
render 함수가 호출된다!

 

==> 같이 getAverage 함수도 호출된다.

--> useMemo를 이용해서 변경됐을때만 호출되게 수정하기

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

const getAverage = (numbers) => {
  if (numbers.length === 0) return 0;
  if (numbers.length === 1) return numbers[0];

  const sum = numbers.reduce((a, b) => {
    console.log("a:", a, "b:", b);
    return a + b;
  });
  return sum / numbers.length;
};

const Average = () => {
  const [list, setList] = useState([]);
  const [number, setNumber] = useState("");

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

  const onChange = useCallback((e) => {
    setNumber(e.target.value);
  }, []);
  const onClick = useCallback(() => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber("");
  }, [list, number]);
  console.log(" onChange :", prevOnChange.current === onChange);
  console.log(" onClick :", prevOnClick.current === onClick);
  prevOnChange.current = onChange;
  prevOnClick.current = onClick;
  return (
    <div>
      <input value={number} onChange={onChange} />
      <button onClick={onClick}>등록</button>
      <ul>
        {list.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
      <div>평균값 : {avg}</div>
    </div>
  );
};

export default Average;

6. useRef

함수형 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해줌

- useRef를 사용하여 ref를 설정하면 useRef를 통해 만든 객체 안의 current 이 실제 엘리먼트를 가리킴

import React, { useState, useMemo, useCallback, useRef } 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;

 

로컬 변수 사용하기]

렌더링과 상관없이 값이 바뀔 수 있음

import React, { useRef } from 'react';  //함수형
 
const RefSample = () => {
  const id = useRef(1);
  const setId = (n) => {
    id.current = n;
  }
  const printId = () => {
    console.log(id.current);
  }
  return (
    <div>
      refsample
    </div>
  );
};
 
export default RefSample;

7. 커스텀 HOOK 만들기

import React from 'react';
import useInputs from './useInputs';
 
const Info = () => {
  const [state, onChange] = useInputs({
    name: '',
    nickname: ''
  });
  const { name, nickname } = state;
 
  return (
    <div>
      <div>
        <input name="name" value={name} onChange={onChange} />
        <input name="nickname" value={nickname} onChange={onChange} />
      </div>
      <div>
        <div>
          <b>이름:</b> {name}
        </div>
        <div>
          <b>닉네임: </b>
          {nickname}
        </div>
      </div>
    </div>
  );
};
 
export default Info;

8. HOOK 정리

HOOKs 패턴을 사용하면 클래스형 컴포넌트를 작성하지 않고도 대부분의 기능 구현 가능

따라서 앞으로의 컴포넌트의 경우 함수형 컴포넌트 + HOOKs 사용 권장

'FrontEnd > React' 카테고리의 다른 글

[ React ] TODO LIST 만들기  (3) 2025.04.09
[ React ] 컴포넌트 스타일링  (0) 2025.04.08
[ React ] Component Recycle Method  (0) 2025.04.06
[ React ] Element handling & Dom  (0) 2025.04.03
[ React ] JSX Component & Props & State  (1) 2025.04.02