사소한 삽질(React의 불변성, 순수함수)

최재홍·2023년 4월 17일
0

React의 불변성을 지키는 예시이다.

import React, { useState } from "react";

function App() {
  const [dogs, setDogs] = useState(["말티즈"]);

  function onClickHandler() {
		// spread operator(전개 연산자)를 이용해서 dogs를 복사합니다. 
	  // 그리고 나서 항목을 추가합니다.
    setDogs([...dogs, "시고르자브종"]);
  }

  console.log(dogs);
  return (
    <div>
      <button onClick={onClickHandler}>버튼</button>
    </div>
  );
}

export default App;

JS에만 익숙하면 현재 ["말티즈"]인 배열에 새로운 요소인 "시고르자브종"을 push메서드를 사용하거나, ["말티즈"][2] = "시고르자브종"으로 넣으면 왜 안 되는지 궁금할 수 있다. 그렇게 넣고나서 setDogs(dogs)로 스테이트를 변화시켜주면 되는거 아닌가 하고 말이다.

결론부터 이야기하면, 이런식으로 state로 관리하고 있는 변수를 조작하거나 훼손하여, setState함수에 넣는 행위는 권장되지 않는다.

두 방식 모두 실제로 적용을 해보면 코드는 다음과 같다.


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

function App() {
  const [dogs, setDogs] = useState(["말티즈"]);
  return (
    <div>
      {dogs}
      <button
        onClick={() => {
          dogs.push("시고르자브종");
          setDogs(dogs);
        }}
      >
        눌러
      </button>
      <input
        onChange={() => {
          console.log(dogs);
        }}
      ></input>
    </div>
  );
}

export default App;

일단 함수를 설명하자면, onClick이 적용된 버튼 태그를 클릭하면 dogs배열에 "시고르자브종"을 추가하여 setDogs의 인자로 들어가게 된다. 그리고 input태그에 변화가 생길 때마다 dogs변수의 상태를 console에 띄우게 된다.

state가 변할 때, 다시 랜더링 된다는 법칙에만 입각하면, "돼야하는거 아냐?"라고 생각할 수 있다. 하지만 되지 않는다. 일단 화면에 랜더링 되는 {dogs}부분은 무슨 짓을 해도 변하지 않는게 당연하고, 신기하게도 버튼을 클릭한 후에 input칸에 변화를 주면, console상으로는 변화가 인식된다. 다시 말해 추가된 배열을 나타낸다는 것이다.

하지만 권장되지 않음에도 어떨 때는 작동한다는 것이 열받는 점인데 그러한 예시는 다음과 같다.

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

function App() {
  let [dogs, setDogs] = useState("말티즈");
  return (
    <div>
      {dogs}
      <button
        onClick={() => {
          dogs = "시고르자브종";
          setDogs(dogs);
        }}
      >
        눌러
      </button>
      <input
        onChange={() => {
          console.log(dogs);
        }}
      ></input>
    </div>
  );
}

export default App;

이번에는 state로 관리되고 있는 변수가 원시데이터인 경우이고, 또 바뀐 점은 state선언을 const가 아닌 let으로 하고 있다는 점이다.


보다시피 되긴 한다.
하지만 권장되지 않는다는 것은 분명하다.

리액트를 공부하는 교재에서 나름의 설명을 찾을 수는 있는데 이유는 다음과 같다.
"순수함수"라는 개념이 있다. 하나 이상의 인자를 받고, 인자를 변경하지 않고, 참조하여 새로운 값을 반환하는 함수를 말한다.

순수함수의 예시는 다음과 같다.

// 매개변수를 복사한 값을 변경하는 순수함수
const addSixPure = (arr) => {
  // 펼침 연산자로 새로운 배열에 6 추가
  newArr = [...arr, 6];
  return newArr;
};

순수함수가 아닌 것의 예시는 다음과 같다.

const num_arr = [1, 2, 3, 4, 5];

// 매개변수의 값을 직접 변경하는 불순함수
const addSixImpure = (arr) => {
  // 매개변수에 직접 6 추가
  arr.push(6);
  return arr;
};

결론만 말하자면,

  1. 리액트는 컴포넌트의 많은 루틴을 순수 함수로서 작성하기를 요구한다고 한다.
    a. 컴포넌트에서 state와 props가 같으면, 항상 같은 값을 반환해야 한다.
    b. 다른 Side effects를 발생시키지 않아야 한다.
  2. 컴포넌트의 상탯값은 불변 객체(Immutable Object)로 관리해야만 한다.
    a. 수정할 때에는 기존 값을 변경하는 것이 아닐, 같은 이름의 새로운 객체를 생성한다.
  3. 이를 통해, UI 개발의 복잡도를 낮추고, 버그 발생 확률도 줄인다.

즉, 상탯값을 임의로 변경시키는 행위는 지양되며, 새로운 변수를 만들어 변경하고 싶은 값을 할당하고 setState의 인자로 넣어야 한다.

0개의 댓글