React 불변성(feat. useState)

유석현(SeokHyun Yu)·2022년 11월 21일
0

정보

목록 보기
4/9
post-thumbnail

서론

React를 배우고 있던 와중에 특이한 일이 하나 생겼다.

import React from "react";
import { useState } from "react";

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

  return (
    <div
      className="container"
      onMouseMove={({ clientX, clientY }) => {
        setPosition((prev) => {
          prev.x = clientX;
          prev.y = clientY;

          return prev;
        });
      }}
    >
      <div
        className="pointer"
        style={{
          transform: `translate(${position.x}px, ${position.y}px)`,
          color: "red",
        }}
      >
        mouse
      </div>
    </div>
  );
}

이 컴포넌트는 마우스가 움직이면 event객체로부터 x, y 좌표를 받아서 "pointer" 클래스를 가진 div요소가 그 좌표대로 translate하면서 따라다닌다.

하지만 위 코드는 원하는대로 작동하지 않는다.

이유를 찾아보니, React가 setPosition 콜백에서 리턴하는 객체(prev)의 참조값과 인자로 받는 이전 상태값(prev)의 참조값을 비교(얕은 비교)한 뒤 값이 같으면 막아놓은 것이었다.

그렇게 해야 이전 상태값의 불변성이 유지되기 때문이다.

따라서 다음과 같이 코드를 작성해야 한다.

import React from "react";
import { useState } from "react";

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

  return (
    <div
      className="container"
      onMouseMove={({ clientX, clientY }) => {
        setPosition((prev) => {
          prev.x = clientX;
          prev.y = clientY;

          return {...prev}
        });
      }}
    >
      <div
        className="pointer"
        style={{
          transform: `translate(${position.x}px, ${position.y}px)`,
          color: "red",
        }}
      >
        mouse
      </div>
    </div>
  );
}

이렇게 하면 prev{...prev}의 참조값은 다르기 때문에 React에서 통과를 시켜준다.

하지만 의문이 드는 점이 있다.

prev.x = clientX 이런식으로 return 되기 전에 prev객체의 프로퍼티 값을 바꿀 수 있는데 리턴되는 객체의 참조값만 비교해서 불변성을 유지한다고 할 수 있을까?

실제로 콘솔을 찍어보면 setPosition에서 객체를 return 해주기 전에 position의 상태가 바뀌는 것을 볼 수 있다.

import React from "react";
import { useState } from "react";

export default function AppXY() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  console.log("🚀 ----------------------🚀");
  console.log("🚀 ~ position1", position);
  console.log("🚀 ----------------------🚀");

  return (
    <div
      className="container"
      onMouseMove={({ clientX, clientY }) => {
        setPosition((prev) => {
          console.log("🚀 ----------------------🚀");
          console.log("🚀 ~ position2", position);
          console.log("🚀 ----------------------🚀");
          prev.x = clientX;
          prev.y = clientY;

          console.log("🚀 ----------------------🚀");
          console.log("🚀 ~ position3", position);
          console.log("🚀 ----------------------🚀");
          //   return prev;
          return { ...prev };
        });
      }}
    >
      <div
        className="pointer"
        style={{
          transform: `translate(${position.x}px, ${position.y}px)`,
          color: "red",
        }}
      >
        mouse
      </div>
    </div>
  );
}

애초에 setPosition의 인자(prev)로 이전 상태값이 깊은 복사가 된 객체를 넘겨주도록 되어있었다면 더 좋지 않았을까라는 생각이 든다.


잘못된 생각이었다

setPosition함수에 넘어온 인자 prev를 만지는대로 콘솔에 찍히는 것은 생각해보면 당연한 것이었다.

코드딴에서는 내가 값을 바꿨으니 그대로 바뀌는 것 뿐이다.

하지만 실제로 상태를 바꾸는 것은 setPosition함수가 return 해주는 값이기 때문에 다른 곳에서 상태를 아무리 바꿔도 콘솔에서 바뀌는 것처럼 보일 뿐이고 실제 상태는 바뀌지 않는다.

그래서 리턴되는 객체의 주소값만 비교해도 불변성이 유지될 수 있다고 하나보다.


참조

  • 원시타입과 참조타입

https://velog.io/@nomadhash/Java-Script-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC%EC%99%80-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC

  • 얕은 복사와 깊은 복사

https://velog.io/@nomadhash/Java-Script-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC%EC%99%80-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC-1dus9z79

  • 리액트 불변성이란 무엇이고, 왜 지켜야 할까?

https://hsp0418.tistory.com/171

profile
Backend Engineer

0개의 댓글