Project

[React/State/React상태관리] React TodoList로 삽질하기

Mapin 2022. 9. 30. 21:34

ㅇ정말 "간단"하게 하고 끝낼 생각이었던 TodoList가 생각보다 , 생각하게 된 것이 많아서 따로 글을 적게 됩니다.

 

3가지에 대해서 간단하게 기록하고 넘어가려고 합니다.

  • React의 map 함수를 이용했을 시, Key의 re-rendering 
  • React의 Component의 흐름 
  • React state의 불변성과 filter,map 등의 함수형 프로그래밍

 

TodoList

 

일정관리 어플리케이션입니다. 버튼을 클릭하거나, 옆에 체크박스를 클릭해서 사라지게 하거나, 버튼을 클릭해서 없애는 등 다양한 형태로 구현할 수 있습니다. 저는 CheckBox형태로 클릭하면 하나씩 뿅뿅 사라지는 형태로 구현했습니다. 

기능 생각하기

사실, 생각할 것도 없습니다. 체크박스를 클릭하면 , 없어지면 됩니다. 간단한 기능입니다.

(네, 그걸 전 3시간 정도 삽질했습니다) WireFramming을 하자면, 아래와 같이 설계할 수 있을것 같습니다.

 

삽질 1. 체크를 했는데, 체크가 되지 않는다.

 

문제의 코드

import "./styles.css";
import React from "react";

const todos = [
  { id: 0, value: "Wash the dishes" },
  { id: 1, value: "have lunch" },
  { id: 2, value: "listen to music" },
  { id: 3, value: "running" },
  { id: 4, value: "work out" }
];
export default function App() {
  const [items, setItems] = React.useState(todos);
  const [checkedItems, setCheckedItems] = React.useState(
    new Array(todos.length).fill(false)
  );

  const checkHandler = ({ target }, idx) => {
    checkedItems[idx] = !checkedItems[idx];
    setCheckedItems(checkedItems);
  };
  return (
    <div className="App">
      {items.map((todo, idx) => (
        <div key={idx}>
          <span>{todo.value}</span>
          <input
            type="checkbox"
            checked={checkedItems[idx]}
            onChange={(e) => checkHandler(e, idx)}
          ></input>
        </div>
      ))}
    </div>
  );
}

 

 

왜 클릭이 안됬을까? 항상 How 보단 Why!

React에서 State값이 객체인 경우, setState를 통해 상태값을 업데이트 시킬때, Spread Operator를 사용하는 등 기존의 State값은 유지시키고, 그걸 복사해서 업데이트한 값으로 상태를 바꿔줍니다.

 

객체 뿐만아니라 "참조 형태" Reference value (JS에서 Primitive Value가 아니라)라면 원본을 그대로 쓰기보다는 , 원본을 복사해서 사용하게 됩니다. 왜냐하면, 참조형태를 그대로 쓰게되면 복사한 값을 바꿔도 원본이 바뀌어버리기 때문이죠!

왜 참조값은 원본이 바뀔까요?

* 참조값(Reference Value) , 원시값(primitive value)

JS에서는 자료형이 메모리에 저장되는 방식의 차이로, primitive한 값과 reference한 값 2가지의 형태로 나뉜다.
let a=1

let str= "hello!"

let b=a

 

 

이 3줄의 코드가 있을 때, b=a에서 어떤 동작이 일어날까?  즉, "copy"나 , "할당"부분에서 이 두 가지 자료형 사이의 차이가 발생하게 된다. 또, arr2 =arr1에서 Memory 관점에서는 어떻게 동작이 일어날까 ?를 생각해보자.

 

 

Primitive Value

원시값은 아래와 같이 동작한다. 단순하다!

메모리의 연결을 끊고 ,뭐 이럴 필요없이 단순히 "새로운" 공간을 잡아서 변수 "a"에 할당되어있는 값과 똑같은 값을 b에게 할당해준다.

Reference Value

memory에서 "새로운 공간"을 할당받는게 아니라, 기존의 공간을 그대로 똑같이 가리키는 형태로 만들어진다. 이건, 참조값은 모두 그렇게 동작을 한다. 즉, 참조값의 변수는 "참조값이 시작하는 Memory상의 주소"라고 표현하기도 한다.

결론 

똑같은 "객체"를 가리키기 때문에, "비동기"가 자주 쓰이는 JS 특성상 어느시점에서 참조형태 값이 바뀔지 알 수 없고, 이는 치명적인 Error를 일으킬 가능성이 높아진다. 즉, State에 reference값이 들어온다면, 불변성을 지키는게 권장되어진다.

React 입장

React가 화면을 렌더링(재랜더링) 하는 경우는 4가지라고 합니다.

  • Props가 변경되었을 때
  • State가 변경되었을 때 (setState호출)
  • forceUpdate() 를 실행하였을 때
  • 부모 컴포넌트가 렌더링되었을 때, 자식이 렌더링 된다.

물론, 비동기의 특성상 참조값이 바꿔진다면, 위험할 수 있겠지만,  사실 React에서 state가 Reference value이면 불변객체를 넣어야 하는 가장 직접적인 이유는 "똑같은 객체"이기 때문입니다.

위에서 보시면 알겠지만, "내부의 값"이 변한다고 해도 배열 자체는 변하지 않으므로, State에 "변화"가 없는것 처럼보이게 된다. 즉, rendering이 일어나지 않는다는 이야기이며 , 이건 실시간 반응성을 잃는다는 이야기이다. state를 쓸 이유가 없습니다.

 

따라서, 코드를 고쳐주면 아래와 같은 코드가 결국 완성이 됩니다.

import "./styles.css";
import React from "react";

/*  생략  */

  const checkHandler = ({ target }, idx) => {
    checkedItems[idx] = !checkedItems[idx];
    /// fix!
    setCheckedItems([...checkedItems]);
  };

/*  생략  */
}

 

자, 2탄에서는 클릭이 되면, 이제 사라지는 기능을 만들어봅시다.

 

* stackoverflow 애용하십쇼.. 진짜 질문올리면 다 답해주십니다 ㄷㄷ..