React State 고급 스킬

김명원·2024년 12월 19일
0

learnReact

목록 보기
4/26
post-thumbnail

🛠️ State 고급 스킬

React에서 객체 상태(state)를 업데이트하는 방법을 배우고, 효율적으로 다룰 수 있는 다양한 기법을 살펴보겠습니다.


1️⃣ 객체 State 업데이트

React의 state는 객체를 포함하여 모든 JavaScript 값을 가질 수 있습니다.
하지만 state 객체를 직접 수정하면 안 됩니다!
새로운 객체를 생성하여 React의 상태 관리 방식과 일치하도록 해야 합니다.


🖍️ 객체를 업데이트하는 이유

React는 객체의 변경을 감지하지 않습니다. 따라서 객체의 참조를 새롭게 바꿔줘야 상태 변경을 인식하고 리렌더링이 발생합니다.

// 잘못된 방법
position.x = e.clientX;
position.y = e.clientY;

// 올바른 방법
setPosition({
  x: e.clientX,
  y: e.clientY,
});

2️⃣ 마우스 포인터를 따라다니는 빨간 점 만들기

코드 작성: AppMovingDot.jsx

다음 코드를 작성해 마우스 포인터를 따라 움직이는 빨간 점을 구현해 보겠습니다.

import { useState } from "react";

export default function AppMovingDot() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  return (
    <div
      onPointerMove={(e) => {
        setPosition({
          x: e.clientX,
          y: e.clientY,
        });
      }}
      style={{
        position: "relative",
        width: "100vw",
        height: "100vh",
      }}
    >
      <div
        style={{
          position: "absolute",
          backgroundColor: "red",
          borderRadius: "50%",
          transform: `translate(${position.x}px, ${position.y}px)`,
          left: -10,
          top: -10,
          width: 20,
          height: 20,
        }}
      />
    </div>
  );
}

결과 확인

  1. 마우스 포인터를 움직이면 콘솔에 X, Y 좌표가 출력됩니다.
  2. 빨간 점이 포인터를 따라 움직입니다.

3️⃣ 전개 구문을 활용한 State 업데이트

React에서는 전개 구문(Spread Syntax)을 사용해 객체의 나머지 속성을 유지한 채로 특정 속성만 업데이트할 수 있습니다.

예제: 강의 폼

CourseForm.jsx에서 강의 제목과 설명을 업데이트하는 폼을 구현합니다.

import { useState } from "react";
import Card from "../card/Card";

export default function CourseForm() {
  const [form, setForm] = useState({
    title: "리액트 강의",
    description: "리액트 기초부터 실전까지!",
  });

  const handleTitleChange = (e) => {
    setForm({
      ...form,
      title: e.target.value,
    });
  };

  const handleDescriptionChange = (e) => {
    setForm({
      ...form,
      description: e.target.value,
    });
  };

  function handleCourseForm(e) {
    e.preventDefault();
  }

  return (
    <Card title="강의 등록">
      <form
        style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
        onSubmit={handleCourseForm}
      >
        <input
          type="text"
          placeholder="강의 제목"
          value={form.title}
          onChange={handleTitleChange}
        />
        <input
          type="text"
          placeholder="강의 한줄 설명"
          value={form.description}
          onChange={handleDescriptionChange}
        />
        <input type="submit" value="등록" />
        {(form.title || form.description) && (
          <div
            style={{
              marginTop: "16px",
              padding: "16px",
              backgroundColor: "#eee",
              borderRadius: "6px",
            }}
          >
            {form.title && <p>제목 - {form.title}</p>}
            {form.description && <p>설명 - {form.description}</p>}
          </div>
        )}
      </form>
    </Card>
  );
}

전개 구문을 사용하는 이유

  1. 속성이 많아질수록 전개 구문은 코드의 가독성과 유지보수를 높여줍니다.
  2. 객체의 기존 속성은 유지하면서 특정 속성만 간단히 업데이트할 수 있습니다.

4️⃣ 단일 이벤트 핸들러로 여러 필드 관리하기

필드가 많아질수록 이벤트 핸들러를 개별적으로 정의하는 방식은 비효율적입니다.

기존 방식

const handleTitleChange = (e) => {
  setForm({
    ...form,
    title: e.target.value,
  });
};

const handleDescriptionChange = (e) => {
  setForm({
    ...form,
    description: e.target.value,
  });
};

개선된 방식: 동적 속성 이름 사용

[e.target.name]을 활용해 동적으로 속성을 지정합니다.

const handleChange = (e) => {
  setForm({
    ...form,
    [e.target.name]: e.target.value,
  });
};

Input 요소에 name 속성 추가

<input
  type="text"
  name="title"
  placeholder="강의 제목"
  value={form.title}
  onChange={handleChange}
/>
<input
  type="text"
  name="description"
  placeholder="강의 한줄 설명"
  value={form.description}
  onChange={handleChange}
/>

결과

  • 단일 핸들러로 모든 필드의 값을 처리할 수 있습니다.
  • 새로운 필드를 추가해도 동일한 로직을 재사용할 수 있습니다.


🏗️ 중첩된 객체를 업데이트하는 방법

객체를 업데이트할 때는 새로운 객체를 생성해야 합니다.
하지만 중첩된 객체의 경우, 전개 구문(...)은 얕은 복사만 수행한다는 점에 유의해야 합니다.
즉, 한 레벨 깊이의 내용만 복사합니다.
중첩된 프로퍼티를 업데이트하려면 전개 구문을 한 번 이상 사용해야 합니다.

예제 코드: 중첩된 객체 업데이트

export default function CourseForm() {
  const [form, setForm] = useState({
    title: "리액트 강의",
    description: "리액트 기초부터 실전까지!",
    info: {
      level: 1,
      skill: "React",
    },
  });

  function handleCourseForm(e) {
    e.preventDefault();
  }

  const handleChange = (e) => {
    setForm({
      ...form,
      [e.target.name]: e.target.value,
    });
  };

  const handleSkillChange = (e) => {
    setForm({
      ...form,
      info: {
        ...form.info,
        skill: e.target.value,
      },
    });
  };

  const handleLevelChange = (e) => {
    setForm({
      ...form,
      info: {
        ...form.info,
        level: e.target.value,
      },
    });
  };
}

🧩 Immer로 간결한 갱신 로직 작성

use-immer는 React 애플리케이션에서 상태 관리를 간편하게 만들어주는 라이브러리입니다.
Immer는 상태를 불변으로 유지하면서도 가변적인 스타일로 코딩할 수 있게 해줍니다.

Immer의 특징

  • 기존 상태를 직접 수정하는 것처럼 보이지만, 실제로는 불변성을 유지하며 새로운 상태를 생성합니다.
  • 상태를 draft라는 Proxy 객체로 관리하며, 변경 내용을 기록하여 새로운 상태를 생성합니다.

예제 코드: Immer로 중첩된 객체 업데이트

import { useImmer } from "use-immer";

export default function CourseForm() {
  const [form, updateForm] = useImmer({
    title: "리액트 강의",
    description: "리액트 기초부터 실전까지!",
    info: {
      level: 1,
      skill: "React",
    },
  });

  function handleCourseForm(e) {
    e.preventDefault();
  }

  const handleChange = (e) => {
    updateForm((draft) => {
      draft[e.target.name] = e.target.value;
    });
  };

  const handleSkillChange = (e) => {
    updateForm((draft) => {
      draft.info.skill = e.target.value;
    });
  };

  const handleLevelChange = (e) => {
    updateForm((draft) => {
      draft.info.level = e.target.value;
    });
  };
}

💡 Immer가 동작하는 방식

Immer가 제공하는 draftProxy 객체입니다.
이 객체는 사용자의 작업을 "기록"하며, 이를 통해 객체를 원하는 만큼 자유롭게 변경할 수 있습니다.

  • draft에서 작업한 내용은 Immer 내부적으로 분석됩니다.
  • 변경된 부분만 포함된 새로운 객체를 생성하여 불변성을 유지합니다.

🚀 요약

  1. State 객체는 항상 새 객체로 업데이트해야 합니다.
  2. 전개 구문은 간단한 업데이트에 유용하지만, 중첩된 객체에서는 반복적으로 사용해야 합니다.
  3. 단일 핸들러와 동적 속성 이름을 활용하면 필드 관리가 더욱 효율적입니다.
  4. Immer를 사용하면 중첩된 객체의 갱신 로직을 간결하게 작성할 수 있습니다.
  5. React는 선언적 업데이트 방식을 통해 간결하고 유지보수 가능한 코드를 지원합니다.

📋 배열 State 업데이트

React에서 배열을 State로 사용할 경우, 객체와 마찬가지로 읽기 전용으로 처리해야 합니다.
배열을 업데이트하려면 새로운 배열을 생성하거나 기존 배열의 복사본을 생성한 뒤, 이를 State로 설정해야 합니다.


✏️ 기본 규칙: 배열 State를 다룰 때

배열을 변경할 때는 기존 배열을 변경하지 않고, 아래와 같은 방식을 사용해야 합니다:

❌ 비추천 방식 (기존 배열 변경)

todos.push({ id: nextId, text: "새로운 할 일" });

✅ 추천 방식 (새 배열 생성)

setTodos([...todos, { id: nextId, text: "새로운 할 일" }]);

1️⃣ 기본 TodoList 구현

코드: AppTodo.jsx

import { useState } from "react";
import "./App.css";
import TodoList from "./components/todo/TodoList";

function AppTodo(props) {
  const [todoText, setTodoText] = useState("");
  const [todos, setTodos] = useState([
    { id: 0, text: "HTML&CSS 공부하기" },
    { id: 1, text: "자바스크립트 공부하기" },
  ]);

  const handleTodoTextChange = (e) => {
    setTodoText(e.target.value);
  };

  const handleAddTodo = () => {
    const nextId = todos.length;
    setTodos([...todos, { id: nextId, text: todoText }]);
    setTodoText("");
  };

  return (
    <div>
      <h2>할일 목록</h2>
      <input type="text" value={todoText} onChange={handleTodoTextChange} />
      <button onClick={handleAddTodo}>추가</button>
      <div>Preview: {todoText}</div>
      <TodoList todos={todos} />
    </div>
  );
}

export default AppTodo;

📝 추가 버튼의 작동 원리

  1. Input 값 업데이트:
    사용자가 텍스트를 입력하면 setTodoText를 사용하여 todoText State를 업데이트합니다.

  2. 할 일 추가:
    handleAddTodo 함수가 실행되면 새로운 할 일이 추가됩니다:

    setTodos([...todos, { id: nextId, text: todoText }]);

    여기서 새로운 배열을 생성하여 State로 설정합니다.

  3. Input 값 초기화:
    할 일을 추가한 뒤, setTodoText("")를 호출하여 입력 필드를 초기화합니다.


2️⃣ 배열 항목 제거 기능 추가

코드: TodoList.jsx

function TodoList({ todos = [], onDeleteTodo }) {
  return (
    <ul>
      {todos.map((item) => (
        <li key={item.id}>
          <span>{item.text}</span>
          <button onClick={() => onDeleteTodo(item.id)}>X</button>
        </li>
      ))}
    </ul>
  );
}

export default TodoList;

코드: AppTodo.jsx

const handleDeleteTodo = (deleteId) => {
  const newTodos = todos.filter((item) => item.id !== deleteId);
  setTodos(newTodos);
};

🛠️ 배열 업데이트 함수의 종류

React에서 배열을 다룰 때, 다음과 같은 함수를 사용해야 합니다:

작업비추천 (배열을 변경)추천 (새 배열 반환)
추가push, unshiftconcat, [...arr] (전개 연산자)
제거pop, shift, splicefilter, slice
수정splice, 직접 할당map
정렬reverse, sort복사 후 정렬

3️⃣ Enter 키로 항목 추가하기

기능 추가: Enter 키를 눌렀을 때 항목 추가

const handleKeyDown = (e) => {
  if (e.key === "Enter") {
    handleAddTodo();
  }
};

Input에 이벤트 핸들러 추가

<input
  type="text"
  value={todoText}
  onChange={handleTodoTextChange}
  onKeyDown={handleKeyDown}
/>

4️⃣ 배열에서 항목 토글 기능 추가

코드: TodoList.jsx

function TodoList({ todos = [], onToggleTodo }) {
  return (
    <ul>
      {todos.map((item) => (
        <li key={item.id}>
          <input
            type="checkbox"
            checked={item.done}
            onChange={(e) => onToggleTodo(item.id, e.target.checked)}
          />
          <span>{item.done ? <del>{item.text}</del> : item.text}</span>
        </li>
      ))}
    </ul>
  );
}

export default TodoList;

코드: AppTodo.jsx

const handleToggleTodo = (id, done) => {
  const nextTodos = todos.map((item) => {
    if (item.id === id) {
      return { ...item, done };
    }
    return item;
  });
  setTodos(nextTodos);
};

5️⃣ 특정 위치에 항목 삽입

기능: 특정 인덱스에 새 항목 추가

const handleAddTodoByIndex = () => {
  const newTodos = [
    ...todos.slice(0, insertAt), // 삽입 위치 이전의 항목
    { id: todos.length, text: todoText, done: false }, // 새 항목
    ...todos.slice(insertAt), // 삽입 위치 이후의 항목
  ];
  setTodos(newTodos);
  setTodoText("");
};

추가 UI 구성

<select value={insertAt} onChange={(e) => setInsertAt(e.target.value)}>
  {todos.map((_, index) => (
    <option key={index} value={index}>
      {index} 번째
    </option>
  ))}
</select>
<button onClick={handleAddTodoByIndex}>삽입</button>

🚫 이런 방식은 지양하세요!

배열을 직접 복사하고 항목을 수정하는 방식은 React에서 비효율적입니다.
예를 들어:

const nextTodos = [...copyTodos];
const targetItem = nextTodos.find((item) => item.id === id);
targetItem.done = done;
setCopyTodos(nextTodos);

이 방식은 React의 불변성 원칙을 위반할 가능성이 높습니다.


🚀 요약

  1. 배열 State는 항상 새로운 배열로 업데이트해야 합니다.
    filter, map, slice와 같은 함수로 새 배열을 생성하세요.

  2. 입력 필드와 State를 동기화하세요.
    onChangeonKeyDown을 활용해 입력 값을 실시간으로 업데이트합니다.

  3. 배열 State에서 항목을 추가/제거/수정/삽입할 때는 불변성을 유지해야 합니다.

  4. React의 선호 방식에 따라 배열을 다루면 코드의 가독성과 유지보수성이 향상됩니다.
    변경 가능한 배열 메서드(push, pop 등)는 피하고, 새 배열을 반환하는 메서드(filter, map 등)를 사용하세요.


🆕 최신 Array API로 배열 State 업데이트하기

JavaScript는 배열을 다룰 때 다양한 배열 메서드를 제공합니다.
그중 불변성을 유지하는 메서드는 React와 같은 선언형 라이브러리에서 특히 유용합니다.


🛠️ Mutable API와 불변성의 문제

reverse와 같은 일부 배열 메서드는 원본 배열을 직접 변경합니다.
이는 React의 불변성 원칙을 위반할 수 있으므로 지양해야 합니다.

예제: Reverse 버튼

<button onClick={handleReverse}>Reverse</button>

const handleReverse = () => {
  const nextTodos = [...todos]; // 배열 복사
  nextTodos.reverse(); // 복사본을 뒤집음
  setTodos(nextTodos); // 새로운 배열을 state로 설정
};

위 코드는 원본 배열을 직접 변경하지 않기 위해 전개 연산자를 사용해 새로운 배열을 생성합니다.
하지만 최신 Array API를 사용하면 더 간단하고 효율적으로 불변성을 유지할 수 있습니다.


🚀 최신 Array API

JavaScript의 최신 API는 불변성을 유지하면서 배열을 쉽게 다룰 수 있는 다양한 메서드를 제공합니다.


1️⃣ toReversed()

기능

toReversed는 배열의 순서를 뒤집은 새로운 배열을 반환합니다.
원본 배열은 변경되지 않습니다.

예제

const numbers = [1, 2, 3];
const reversedNumbers = numbers.toReversed();
console.log(reversedNumbers); // [3, 2, 1]
console.log(numbers); // [1, 2, 3] (원본 배열은 변경되지 않음)

React에서 사용

const handleReverse = () => {
  setTodos(todos.toReversed());
};

2️⃣ toSorted()

기능

toSorted는 배열을 정렬한 새로운 배열을 반환합니다.
원본 배열은 변경되지 않습니다.

예제

const numbers = [3, 1, 4, 1, 5, 9];
const sortedNumbers = numbers.toSorted();
console.log(sortedNumbers); // [1, 1, 3, 4, 5, 9]
console.log(numbers); // [3, 1, 4, 1, 5, 9] (원본 배열은 변경되지 않음)

React에서 사용

const handleSort = () => {
  setTodos(todos.toSorted((a, b) => a.id - b.id));
};

3️⃣ toSpliced()

기능

toSpliced는 지정된 인덱스에서 요소를 제거하거나 추가한 새로운 배열을 반환합니다.
원본 배열은 변경되지 않습니다.

예제

const numbers = [1, 2, 3];
const splicedNumbers = numbers.toSpliced(1, 1, 4); // 인덱스 1에서 1개 요소 제거, 4 추가
console.log(splicedNumbers); // [1, 4, 3]
console.log(numbers); // [1, 2, 3] (원본 배열은 변경되지 않음)

React에서 사용

const handleSplice = () => {
  setTodos(todos.toSpliced(1, 1, { id: todos.length, text: "새 항목", done: false }));
};

4️⃣ with()

기능

with는 지정된 인덱스의 요소를 새로운 값으로 교체한 새로운 배열을 반환합니다.
원본 배열은 변경되지 않습니다.

예제

const numbers = [1, 2, 3];
const withNewNumbers = numbers.with(1, 4); // 인덱스 1 값을 4로 변경
console.log(withNewNumbers); // [1, 4, 3]
console.log(numbers); // [1, 2, 3] (원본 배열은 변경되지 않음)

React에서 사용

const handleUpdate = () => {
  setTodos(todos.with(1, { ...todos[1], text: "업데이트된 텍스트" }));
};

📝 결론

🔑 최신 Array API의 장점

  1. 불변성 유지: 원본 배열을 변경하지 않으므로 React의 상태 관리와 호환성이 높습니다.
  2. 가독성 향상: 기존 API보다 직관적이고 코드가 간결합니다.
  3. 효율성: 새로운 배열을 반환하기 때문에 상태 변경을 쉽게 처리할 수 있습니다.

✨ 최신 API 요약

메서드기능원본 변경 여부
toReversed배열의 순서를 반대로 한 새 배열 반환
toSorted배열을 정렬한 새 배열 반환
toSpliced요소를 제거하거나 추가한 새 배열 반환
with지정된 인덱스 요소를 교체한 새 배열 반환

React와 최신 JavaScript API를 활용하면 코드의 유지보수성과 효율성을 크게 향상시킬 수 있습니다!


💖 좋아요 기능 구현: useState와 useImmer 적용하기

React에서 useState를 사용해 강의 목록에 좋아요(즐겨찾기) 기능을 구현하는 방법과,
이를 useImmer로 간단히 개선하는 방법을 다룹니다.


🎯 구현 목표

  • 강의 목록에서 좋아요 버튼을 통해 즐겨찾기 상태를 변경합니다.
  • useStateuseImmer를 각각 활용하여 코드를 작성합니다.

🛠️ useState를 활용한 기본 구현

AppCourse.jsx

강의 목록 데이터를 useState로 관리하며, 좋아요 상태를 업데이트합니다.

import './AppCourse.css';
import CourseForm from './components/course/CourseForm';
import CourseListCard from './components/course/CourseListCard';
import { useState } from 'react';

function App() {
  const [items, setItems] = useState([
    {
      id: 0,
      title: '입문자를 위한, HTML&CSS 웹 개발 입문',
      description: '웹 개발에 필요한 기본 지식을 배웁니다.',
      thumbnail: '/img/htmlcss.png',
      isFavorite: false,
      link: 'https://inf.run/JxyyT',
    },
    {
      id: 1,
      title: '입문자를 위한, ES6+ 최신 자바스크립트 입문',
      description: '쉽고! 알찬! 내용을 준비했습니다.',
      thumbnail: '/img/js.png',
      isFavorite: true,
      link: 'https://inf.run/Kpnd',
    },
    {
      id: 2,
      title: '포트폴리오 사이트 만들고 배포까지!',
      description: '포트폴리오 사이트를 만들고 배포해 보세요.',
      thumbnail: '/img/portfolio.png',
      isFavorite: true,
      link: 'https://inf.run/YkAN',
    },
  ]);

  const handleFavoriteChange = (id, isFavorite) => {
    const newItems = items.map((item) =>
      item.id === id ? { ...item, isFavorite } : item
    );
    setItems(newItems);
  };

  const favoriteItems = items.filter((item) => item.isFavorite);

  return (
    <main style={{ flexDirection: 'column', gap: '1rem' }}>
      <CourseForm />
      <CourseListCard title="강의 목록" items={items} onFavorite={handleFavoriteChange} />
      {/* <CourseListCard title="관심 강의" items={favoriteItems} /> */}
    </main>
  );
}

export default App;

⚙️ 컴포넌트 구조

CourseListCard.jsx

강의 목록을 카드 형식으로 렌더링하며, 각 항목에 좋아요 버튼을 포함합니다.

import { Fragment } from 'react';
import Card from '../Card';
import CourseItem from './CourseItem';

function CourseListCard({ title, items, onFavorite }) {
  const lastIndex = items.length - 1;

  return (
    <Card title={title}>
      <div className="courses">
        {items.map((item, index) => (
          <Fragment key={item.id}>
            <CourseItem {...item} onFavorite={onFavorite} />
            {index !== lastIndex && <hr className="divider" />}
          </Fragment>
        ))}
      </div>
    </Card>
  );
}

export default CourseListCard;

CourseItem.jsx

강의 항목을 표시하며, 좋아요와 링크 버튼을 제공합니다.

function HeartIconBtn({ onClick, isFavorite = false }) {
  return (
    <button className="btn" onClick={(e) => onClick(e)}>
      <img
        className="btn__img"
        src={isFavorite ? '/img/heart-fill-icon.svg' : '/img/heart-icon.svg'}
      />
    </button>
  );
}

function LinkIconBtn({ link }) {
  return (
    <a className="btn" href={link} target="_blank" rel="noreferrer">
      <img className="btn__img" src="/img/link-icon.svg" />
    </a>
  );
}

export default function CourseItem({
  id,
  onFavorite,
  title,
  description,
  thumbnail,
  isFavorite,
  link,
}) {
  const handleFavorite = (e) => {
    e.stopPropagation();
    onFavorite(id, !isFavorite);
  };

  const handleItemClick = () => {
    open(link, '_blank');
  };

  return (
    <article className="course" onClick={handleItemClick}>
      <img className="course__img" src={thumbnail} alt="강의 이미지" />
      <div className="course__body">
        <div className="course__title">{title}</div>
        <div className="course__description">{description}</div>
      </div>
      <div className="course__icons">
        <HeartIconBtn isFavorite={isFavorite} onClick={handleFavorite} />
        {link && <LinkIconBtn link={link} />}
      </div>
    </article>
  );
}

useImmer로 구현 간소화

React의 useImmer를 활용하면 상태 관리가 더욱 간단해집니다.
draft 객체를 직접 수정하면 불변성을 유지하면서 새로운 상태를 생성합니다.

수정된 AppCourse.jsx

import './AppCourse.css';
import CourseForm from './components/course/CourseForm';
import CourseListCard from './components/course/CourseListCard';
import { useImmer } from 'use-immer';

function App() {
  const [items, updateItems] = useImmer([
    {
      id: 0,
      title: '입문자를 위한, HTML&CSS 웹 개발 입문',
      description: '웹 개발에 필요한 기본 지식을 배웁니다.',
      thumbnail: '/img/htmlcss.png',
      isFavorite: false,
      link: 'https://inf.run/JxyyT',
    },
    {
      id: 1,
      title: '입문자를 위한, ES6+ 최신 자바스크립트 입문',
      description: '쉽고! 알찬! 내용을 준비했습니다.',
      thumbnail: '/img/js.png',
      isFavorite: true,
      link: 'https://inf.run/Kpnd',
    },
    {
      id: 2,
      title: '포트폴리오 사이트 만들고 배포까지!',
      description: '포트폴리오 사이트를 만들고 배포해 보세요.',
      thumbnail: '/img/portfolio.png',
      isFavorite: true,
      link: 'https://inf.run/YkAN',
    },
  ]);

  const handleFavoriteChange = (id, isFavorite) => {
    updateItems((draft) => {
      const targetItem = draft.find((item) => item.id === id);
      targetItem.isFavorite = isFavorite;
    });
  };

  const favoriteItems = items.filter((item) => item.isFavorite);

  return (
    <main style={{ flexDirection: 'column', gap: '1rem' }}>
      <CourseForm />
      <CourseListCard title="강의 목록" items={items} onFavorite={handleFavoriteChange} />
      {/* <CourseListCard title="관심 강의" items={favoriteItems} /> */}
    </main>
  );
}

export default App;

🚀 요약

  1. useState를 사용해 상태를 직접 업데이트할 수 있지만, 불변성을 유지해야 합니다.
  2. useImmer를 활용하면 상태를 보다 간단하고 직관적으로 업데이트할 수 있습니다.
  3. 좋아요 기능을 구현할 때 불변성을 유지하면서 필요한 부분만 업데이트하여 React의 최적화 원칙을 따릅니다.
profile
개발자가 되고 싶은 정치학도생의 기술 블로그

0개의 댓글