ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React 정리해보기
    REACT 2025. 12. 30. 16:26

     

    오늘은 마지막 리액트수업이다!

    19.2버전 신기술도 배우고 띠용하는 시간이 많았는데 뭐하나 주제를 잡기에 애매해서

    간단히 마지막 실습문제와 기술면접 질문만 정리해보는 시간으로 가졌다!

    정말 공부할게 너무많타,,,wow


    1️⃣ 실전 최적화 연습해보기 try-first

    문제점을 파악하고, 불필요한 렌더링을 막아주세요.

    // 이 코드는 최적화가 잘 안 된 경우입니다.
    <ChildComponent
      config={{ page: 1 }}  
      onClick={() => alert('hi')} 
    />
    

    위 코드의 문제점은 아래와 같습니다.

    • ChildComponent가 React.memo 로 감싸져 있어도 무조건 리렌더링 됩니다.
    • 매번 새로운 객체/함수라서 props 가 바뀐 것으로 간주합니다.
    • 그럼 위 코드의 문제들을 해결하여 불필요한 리렌더링을 막으려면 어떻게 해야할까요?

     

    정답 : useMemo, useCallback으로 메모제이션하기

    import { useState, useMemo, useCallback, memo } from "react";
    
    function ParentComponent() {
        // useMemo: 값을 메모이제이션
        const config = useMemo(() => ({ page: 1 }), []);
    
        // useCallback: 함수를 메모이제이션
        const handleClick = useCallback(() => alert('hi'), []);
        // 사실 useMemo(() => () => alert('hi'), [])와 동일
    
      return (
        <div>
        ...
        </div>
      );
    }

     

     

     

    2️⃣ 아래 코드를 useReducer 로 개선해주세요.

    import { useState } from "react";
    
    let nextId = 3;
    const initialTasks = [
      { id: 0, text: "Visit Kafka Museum", done: true },
      { id: 1, text: "Watch a puppet show", done: false },
      { id: 2, text: "Lennon Wall pic", done: false },
    ];
    
    export default function TaskApp() {
      const [tasks, setTasks] = useState(initialTasks);
    
      function handleAddTask(text) {
        setTasks([
          ...tasks,
          {
            id: nextId++,
            text: text,
            done: false,
          },
        ]);
      }
    
      function handleChangeTask(task) {
        setTasks(
          tasks.map((t) => {
            if (t.id === task.id) {
              return task;
            } else {
              return t;
            }
          })
        );
      }
    
      function handleDeleteTask(taskId) {
        setTasks(tasks.filter((t) => t.id !== taskId));
      }
    
      return (
        <>
          <h1>Prague itinerary</h1>
          <AddTask onAddTask={handleAddTask} />
          <TaskList
            tasks={tasks}
            onChangeTask={handleChangeTask}
            onDeleteTask={handleDeleteTask}
          />
        </>
      );
    }
    
    function AddTask({ onAddTask }) {
      const [text, setText] = useState("");
      return (
        <>
          <input
            placeholder="Add task"
            value={text}
            onChange={(e) => setText(e.target.value)}
          />
          <button
            onClick={() => {
              setText("");
              onAddTask(text);
            }}
          >
            Add
          </button>
        </>
      );
    }
    
    function TaskList({ tasks, onChangeTask, onDeleteTask }) {
      return (
        <ul>
          {tasks.map((task) => (
            <li key={task.id}>
              <Task task={task} onChange={onChangeTask} onDelete={onDeleteTask} />
            </li>
          ))}
        </ul>
      );
    }
    
    function Task({ task, onChange, onDelete }) {
      const [isEditing, setIsEditing] = useState(false);
      let taskContent;
      if (isEditing) {
        taskContent = (
          <>
            <input
              value={task.text}
              onChange={(e) => {
                onChange({
                  ...task,
                  text: e.target.value,
                });
              }}
            />
            <button onClick={() => setIsEditing(false)}>Save</button>
          </>
        );
      } else {
        taskContent = (
          <>
            {task.text}
            <button onClick={() => setIsEditing(true)}>Edit</button>
          </>
        );
      }
      return (
        <label>
          <input
            type="checkbox"
            checked={task.done}
            onChange={(e) => {
              onChange({
                ...task,
                done: e.target.checked,
              });
            }}
          />
          {taskContent}
          <button onClick={() => onDelete(task.id)}>Delete</button>
        </label>
      );
    }

     

     

    정답 : 이벤트들을 switch문으로 핸들링하고
    useState 대신 useReducer으로 변경해 dispatch의 action으로 넘겨 가독성과 유지보수를 높일수있다!
    // 1. Reducer함수만들어 swith문으로 관리
    function tasksReducer(tasks, action) {
      switch (action.type) {
        case 'added':
          return [...tasks, { id: nextId++, text: action.text, done: false }];
        case 'changed':
          return tasks.map((t) => t.id === action.task.id ? action.task : t);
        case 'deleted':
          return tasks.filter((t) => t.id !== action.id);
        default:
          throw Error('Unknown action: ' + action.type);
      }
    }
    
    //2. 상태관리를 useState대신 useReducer로 변경
    const [tasks, setTasks] = useState(initialTasks); // 기존
    const [tasks, dispatch] = useReducer(tasksReducer, initialTasks); // 변경
    
    //3. 핸들러함수 변경
    function handleAddTask(text) {
      setTasks([...tasks, { id: nextId++, text: text, done: false }]);
    } // 기존
    
    function handleAddTask(text) {
      dispatch({ type: 'added', text: text });
    } // 변경

     

     

    3️⃣ 아래 코드의 문제를 찾고 개선해주세요.

    import { useMemo, useState } from "react";
    
    function HeavyCalculator() {
      const [count, setCount] = useState(0);
    
      const result = (() => {
        console.log("복잡한 계산 중...");
        let sum = 0;
        for (let i = 0; i < 1e7; i++) sum += i;
        return sum;
      })();
    
      return (
        <>
          <h3>Result: {result}</h3>
          <button onClick={() => setCount(count + 1)}>Click ({count})</button>
        </>
      );
    }

     

     

    정답: result 계산비용이 클것으로 예상되므로 useMemo를 사용해 최초 마운트 1회만 렌더링시키면됨! 그럼 계속 요걸로 재사용됨
      const result = useMemo(() => {
        console.log("복잡한 계산 중...");
        let sum = 0;
        for (let i = 0; i < 1e7; i++) sum += i;
        return sum;
      }, []); //최초 1번만 실행

     

     

     

    4️⃣ 가볍게 기술 면접 연습해보기

    1. 리액트의 기본 동작 원리에 대해 설명해보세요.

    리액트는 컴포넌트 기반으로 UI를 구성하며 ,state 또는 props가 변경되면 해당 컴포넌트를 재렌더링해요.
    이때 새로운 가상돔(Virtual DOM)을 만들고, 이전 가상돔과 비교(Diffing)해서
    실제로 변경된 부분만 실제DOM에 효율적으로 업데이트합니다!

     

    2. useState 와 useReducer 의 차이를 설명해주세요.

    상태관리를 위한 Hook들로 사용하는 상황마다 적절히 쓰면좋아요.

    useState간단한 상태관리에 사용해요. 예를들어 토클버튼, 단순한 카운터처럼 상태업데이트 로직이 간단할때 좋아요

    반면 useReducer는 상태가 상태로직이 복잡하거나, 여러개의 연관된상태를 한번에 관리해야할때 사용하면 좋아요.
    예를들면 form에 있는 여러가지 input값을 관리하거나
    장바구니페이지에서 추가/삭제/수량변경 등 다양한 액션이 필요한 경우 쓰면 코드가 훨씬 깔끔하고 관리하기 쉬워져요.

    그리고 Reducer는 dispatch 함수를 쓰게되는데 여기서 쓰는 참조값은 바뀌지않기 때문에
    자식컴포넌트에 콜백을 넘겨줄때 불필요한 리렌더링을 방지할수있습니다!

     

    3. useEffect가 언제 실행되고, cleanup 함수는 언제 호출되나요?

    useEffect는 컴포넌트가 렌더링된 후에 실행됩니다. 정확히는 브라우저가 화면을 그린다음에 비동기로 실행되죠.

    실행시점의존성 배열에 따라 달라집니다! 3가지 경우가 있어요

    1. 의존성 배열이 없으면 렌더링할때마다 매번 실행되요

    2. 빈배열을 넣으면 컴포넌트가 첫마운트될때 딱한번만 실행됩니다. 보통 API호출이나 이벤트리스너를 달때 사용해요

    3. 마지막으로 특정값이 들은 배열을 넣으면 그 값이 변경될때마다 렌더링됩니다.

     

    그리고 cleanup return으로 반환하는 함수를 말해요.

    다음 effect가 실행되기 전이나 컴포넌트가 언마운트될때 호출되요

    만약 이벤트리스너를 등록했다면 cleanup에서 함수를 꼭 제거해야 메모리 누수를 막을수있어요.

     

    4. React의 key prop은 왜 필요한가요?

    key는 리스트의 각 요소를 식별하기 위해 필요해요

    리스트가 변경될때 뭐가 추가,삭제,수정을 알아야하는데 그것을 key로 비교해 같은 값인지 빠르게 판단하거든요

    그래서 반드시 유니크한 key값이 꼭 필요합니다

    만약 key가 없거나 index값을 쓴다면 문제가 될수있어요.
    리스트 중간에 추가,삭제되었는데 index가 바뀌어버려 리액트는 이걸 전혀 다른요소로 인식해버리게 되요.

    그렇게되면 불필요한 DOM이나 input값 상태가 꼬여버리는 버그가 생기게 되는거죠

     

    5. React에서 상태 업데이트가 비동기적인 이유는?

    결론적으론 성능최적화 때문이에요.

    만약 setState를 호출할때마다 매번 렌더링한다면, 여러번 호출하게 되면 그만큼 렌더링도 여러번 일어나게 되겠죠.

    엄청 비효율적인 일이에요

     

    리액트는 setState를 여러번 호출해도 딱 한번만 렌더링하는데

    여러 상태 업데이트를 batch로 한번에 모아서 처리합니다. 이렇게하면 불필요한 리렌더링을 줄일수있거든요

    다만 주의해야할점은 업데이트되기 전 값이 나올수도 있어요. 그래서 함수형업데이트를 사용해야해요!

    // 이건 문제가 생길 수 있어요
    setCount(count + 1);
    setCount(count + 1); // 여전히 이전 count 값 참조
    
    // 이렇게 해야 제대로 동작해요
    setCount(prev => prev + 1);
    setCount(prev => prev + 1); // 이전 업데이트 결과를 받아옴

     

    원랜 이벤트핸들러안에서 배칭이 됐는데 React 18부터는 Promise,setTimeout안에서도 자동으로 가능해졌어요.

Designed by Tistory.