TIL/[TIL]

[TIL]React 반복 렌더링과 useEffect

namerong 2026. 4. 17. 15:51

1. React에서 반복 렌더링이 필요한 이유

실제 서비스에서는 같은 형태의 UI를 여러 번 출력해야 하는 경우가 많다.

예를 들어 회원 목록, 상품 목록, 댓글 목록, 할 일 목록처럼 데이터 배열을 기반으로 같은 구조를 반복해서 보여줘야 한다.

React에서는 이런 반복 렌더링을 map()을 사용해 처리한다.

배열의 각 요소를 JSX로 변환하여 화면에 렌더링하는 방식이다.


2. 배열을 화면에 출력하기

2.1 문자열 배열 렌더링

const fruits = ['사과', '바나나', '포도', '오렌지'];

<ul>
    {fruits.map((fruit, index) => (
        <li key={index}>{fruit}</li>
    ))}
</ul>
  • 배열의 각 요소를 map()으로 순회한다.
  • 각 요소마다 <li>를 만들어 출력한다.
  • 반복 렌더링된 요소에는 key가 필요하다.

2.2 객체 배열 렌더링

const users = [
    { id: 1, name: '홍길동' },
    { id: 2, name: '유관순' },
    { id: 3, name: '이순신' }
];

<ul>
    {users.map(user => (
        <li key={user.id}>{user.name}</li>
    ))}
</ul>
  • 객체 배열도 같은 방식으로 렌더링할 수 있다.
  • 이때 key는 가능한 한 고유한 값을 사용하는 것이 좋다.
  • id처럼 데이터 자체의 고유값을 사용하는 것이 가장 안정적이다.

3. key의 역할

React에서 리스트를 렌더링할 때 key는 각 요소를 구분하기 위한 식별자이다.

React는 key를 사용해 어떤 요소가 추가, 수정, 삭제되었는지 효율적으로 판단한다.

<li key={user.id}>{user.name}</li>

정리

  • key는 반복 렌더링된 요소를 구분하는 기준이다.
  • 가능하면 index보다 id 같은 고유값을 사용하는 것이 좋다.
  • key가 안정적일수록 React가 변경 사항을 더 정확하게 추적할 수 있다.

4. 입력값과 리스트 추가

리스트를 단순히 출력하는 것에서 더 나아가, 사용자가 입력한 값을 목록에 추가하는 기능을 만들 수 있다.

이 과정에서는 useState를 사용해 입력값과 목록 상태를 함께 관리한다.

const [inputValue, setInputValue] = useState('');
const [names, setNames] = useState([{ id: 1, text: '판다' }]);
const [nextId, setNextId] = useState(2);

이후 입력창의 값은 onChange로 상태에 반영하고, 추가 버튼을 누르면 새로운 객체를 배열에 넣는다.

const handleAdd = () => {
    if (inputValue.trim() === '') return;

    const newName = { id: nextId, text: inputValue };

    setNames([...names, newName]);
    setNextId(nextId + 1);
    setInputValue('');
};

정리

  • 입력값은 상태로 관리한다.
  • 새 항목을 추가할 때는 기존 배열을 직접 수정하지 않고 새 배열을 만들어야 한다.
  • 전개 연산자 ...를 사용하면 기존 배열을 복사한 뒤 새 값을 추가할 수 있다.
  • 입력 후에는 상태를 초기화해 입력창을 비울 수 있다.

5. 리스트 삭제와 filter

배열에서 특정 항목을 삭제할 때는 filter()를 사용한다.

삭제할 대상을 제외한 나머지만 모아서 새로운 배열을 만들면 된다.

const handleRemove = (idToRemove) => {
    const updatedNames = names.filter(name => name.id !== idToRemove);
    setNames(updatedNames);
};

정리

  • filter()는 조건을 만족하는 요소만 남긴 새 배열을 반환한다.
  • 삭제 기능은 원본 배열을 직접 변경하지 않고 새 배열로 상태를 갱신하는 방식으로 구현한다.
  • React 상태는 불변성을 지키는 방식으로 다루는 것이 중요하다.

6. Todo List 구현

Todo List 예제에서는 지금까지 배운 반복 렌더링, 상태 관리, 이벤트 처리, 배열 메서드를 한 번에 적용했다.

6.1 할 일 추가

const newTodo = {
    id: Date.now(),
    text: inputValue,
    completed: false
};

setTodos([...todos, newTodo]);
  • 새로운 할 일 객체를 만든다.
  • completed 상태를 함께 넣어 완료 여부를 관리한다.
  • Date.now()를 사용해 간단한 고유 id를 만들었다.

6.2 할 일 삭제

setTodos(todos.filter(todo => todo.id !== id));

6.3 완료 상태 토글

setTodos(
    todos.map(todo =>
        todo.id === id
            ? { ...todo, completed: !todo.completed }
            : todo
    )
);
  • map()을 사용해 배열 전체를 순회한다.
  • 조건에 맞는 항목만 새로운 객체로 바꾸고, 나머지는 그대로 둔다.
  • 체크박스 상태와 연결해 완료 여부를 반영한다.

정리

  • 추가는 ...배열과 새 객체를 이용한다.
  • 삭제는 filter()를 사용한다.
  • 수정은 map()을 사용해 필요한 요소만 바꾼다.
  • 상태는 직접 변경하지 않고 항상 새로운 배열과 객체를 만들어 갱신한다.

7. 컴포넌트 분리와 Props 활용

Todo 예제에서는 TodoItem 컴포넌트를 따로 만들어 각 항목을 렌더링했다.

function TodoItem({ todo, onRemove, onToggle }) {
    return (
        <div>
            <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => onToggle(todo.id)}
            />
            <span>{todo.completed ? <s>{todo.text}</s> : todo.text}</span>
            <button onClick={() => onRemove(todo.id)}>X</button>
        </div>
    );
}

정리

  • 부모 컴포넌트는 데이터를 관리한다.
  • 자식 컴포넌트는 전달받은 props로 화면을 그린다.
  • 삭제 함수와 토글 함수도 props로 내려보낼 수 있다.
  • 컴포넌트를 분리하면 구조가 명확해지고 재사용성이 높아진다.

8. useEffect란

useEffect는 함수형 컴포넌트에서 렌더링이 끝난 뒤 실행할 작업을 처리하는 Hook이다.

  • 콘솔 출력이나 로그 기록
  • 타이머 등록
  • 문서 제목 변경
  • API 호출
  • 구독 설정 및 정리
useEffect(() => {
    console.log('렌더링 이후 동작...');
});

정리

  • 컴포넌트 본문은 렌더링 과정에서 실행된다.
  • useEffect는 렌더링이 끝난 뒤 실행된다.
  • 화면에 직접 보여주는 코드와 부수 효과를 분리할 수 있다.

9. useEffect의 실행 시점

9.1 의존성 배열 생략

useEffect(() => {
    console.log('렌더링될 때마다');
});
  • 렌더링이 일어날 때마다 실행된다.
  • 처음 렌더링 이후에도 실행되고, 상태가 바뀌어 다시 렌더링될 때도 실행된다.

9.2 빈 배열 []

useEffect(() => {
    console.log('컴포넌트가 처음 화면에 나타남');
}, []);
  • 처음 마운트될 때 한 번만 실행된다.
  • 초기 데이터 로딩, 최초 설정 작업 등에 사용한다.

9.3 특정 값 의존

useEffect(() => {
    console.log(`count 값이 ${count}로 변경되어 실행`);
}, [count]);
  • 지정한 값이 변경될 때만 실행된다.
  • 필요한 변화에만 반응하도록 제어할 수 있다.

정리

  • 의존성 배열이 없으면 매 렌더링마다 실행된다.
  • 빈 배열이면 최초 한 번만 실행된다.
  • 배열에 특정 값을 넣으면 그 값이 바뀔 때만 실행된다.

10. 렌더링과 useEffect의 차이

console.log()를 컴포넌트 함수 본문에 작성하면 렌더링 시점에 실행된다.

반면 useEffect() 안에 작성하면 렌더링이 끝난 뒤 실행된다.

function MessagePrinter() {
    console.log('렌더링');

    useEffect(() => {
        console.log('렌더링 이후 동작...');
    });

    return <h1>Hello</h1>;
}

정리

  • 컴포넌트 함수 내부 코드는 렌더링 과정에서 실행된다.
  • useEffect는 렌더링 완료 후 실행된다.
  • 렌더링과 부수 효과의 실행 시점을 구분하는 것이 중요하다.

11. 마운트(Mount)

마운트는 컴포넌트가 처음 화면에 나타나는 시점을 의미한다.

useEffect(() => {}, []) 형태는 마운트 시점에 한 번만 실행된다.

useEffect(() => {
    console.log('useEffect 동작');
}, []);

예제에서는 버튼을 눌러 시간을 변경해도 useEffect는 다시 실행되지 않았다.

왜냐하면 의존성 배열이 빈 배열이기 때문에 최초 마운트 때만 동작하기 때문이다.

정리

  • 마운트는 컴포넌트가 처음 생성되어 화면에 나타나는 시점이다.
  • 빈 의존성 배열은 마운트 시 한 번 실행되는 패턴이다.

12. 문서 제목 변경

useEffect를 활용하면 브라우저 탭 제목도 상태에 따라 바꿀 수 있다.

useEffect(() => {
    document.title = `넌 ${count}번 클릭했어`;
});

정리

  • document.title처럼 화면 외부의 브라우저 환경을 다룰 때 useEffect가 적합하다.
  • 상태 변화에 맞춰 문서 제목도 함께 갱신할 수 있다.

13. Cleanup 함수와 Unmount

useEffect는 정리 함수도 반환할 수 있다.

이 함수는 컴포넌트가 화면에서 사라질 때 실행된다.

useEffect(() => {
    const timerId = setInterval(() => {
        console.log('...1초 경과');
    }, 1000);

    return () => {
        clearInterval(timerId);
    };
}, []);

이 패턴은 타이머, 이벤트 리스너, 구독 해제 같은 정리 작업에 매우 중요하다.

정리

  • useEffect에서 함수를 반환하면 cleanup 함수가 된다.
  • cleanup 함수는 컴포넌트가 사라질 때 실행된다.
  • 타이머나 구독을 정리하지 않으면 메모리 누수나 불필요한 동작이 발생할 수 있다.

14. 라이프사이클 흐름 이해

오늘 예제를 통해 함수형 컴포넌트에서도 마운트, 업데이트, 언마운트 개념을 이해할 수 있었다.

14.1 마운트

  • 컴포넌트가 처음 나타남
  • useEffect(..., []) 실행

14.2 업데이트

  • 상태가 바뀌어 다시 렌더링됨
  • 의존성 배열 조건에 따라 useEffect 실행

14.3 언마운트

  • 컴포넌트가 화면에서 사라짐
  • cleanup 함수 실행

버튼으로 showTimer 값을 바꾸어 Timer 컴포넌트를 보이거나 숨기면서 언마운트 시 cleanup이 동작하는 흐름을 확인했다.

{showTimer && <Timer />}

정리

  • React 함수형 컴포넌트도 생명주기 개념을 가진다.
  • useEffect를 통해 라이프사이클별 동작을 제어할 수 있다.
  • 특히 언마운트 시 정리 작업은 매우 중요하다.

15. 오늘 배운 핵심 정리

  1. React에서 배열을 반복 렌더링할 때는 map()을 사용한다.
  2. 반복 렌더링된 요소에는 key가 필요하며, 가능하면 고유한 id를 사용해야 한다.
  3. 리스트에 항목을 추가할 때는 기존 배열을 복사한 새 배열을 만들어 상태를 갱신한다.
  4. 삭제는 filter(), 수정은 map()으로 처리하는 패턴이 중요하다.
  5. Todo List는 추가, 삭제, 완료 토글을 통해 상태 변경 패턴을 종합적으로 연습하기 좋은 예제이다.
  6. useEffect는 렌더링이 끝난 뒤 실행되는 부수 효과 처리용 Hook이다.
  7. 의존성 배열이 없으면 매 렌더링마다, 빈 배열이면 마운트 시 한 번만, 특정 값을 넣으면 그 값이 바뀔 때만 실행된다.
  8. useEffect는 문서 제목 변경, 타이머 등록, 외부 자원 제어에 유용하다.
  9. cleanup 함수는 언마운트 시점에 실행되어 타이머나 이벤트를 정리한다.
  10. 함수형 컴포넌트에서도 마운트, 업데이트, 언마운트 흐름을 이해하는 것이 중요하다.