본문 바로가기

Web + APP/React

[리액트 스터디] - Choosing the State Structure

반응형
SMALL

State를 얼마나 잘 구조화 하는가가 리액트 숙련도를 좌지우지함


State 구조화 원칙

state를 만들 때 더 나은 선택을 할 수 있는 선택지들이 있음

 

1. 연관된 state 그룹화하기 => 단일 state로 변수를 하나로 합치도록 병합하기

2. state의 모순 피하기 => 여러 state가 모순되면 제거하기

3. 불필요한 state 피하기 => props나 기존의 state 변수로 일부 정보를 계산할 수 있으면, 제외하기

4. state의 중복 피하기 => 동기화 유지가 어려우니 중복을 없애자

5. 깊게 중첩된 state 피하기 => 업데이트가 쉽지 않으니, 평탄화를 하자

 

결국 최종 목표는 오류 없이 상태를 쉽게 업데이트 하는 것

 

아이슈타인이 아래와 같은 말을 했다.

당신의 state를 가능한 단순하게 만들어야한다. 더 단순하게가 아니라

 

근데 이거 진짜임 ??

 

Everything should be made as simple as possible, but no simpler (모든건 가능한 단순해야한다. 더 단순하게가 아니라)

 

아니네

 

이 짤 뭐야 ㅋㅋㅋㅋㅋㅋ


연관된 state 그룹화하기

// 이렇게 할까 ?
const [x, setX] = useState(0);
const [y, setY] = useState(0);

// 이렇게 할까 ?
const [position, setPosition] = useState({ x: 0, y: 0 });

 

둘 다 되지만, 두 개의 state 변수가 함께 변경된다면, 단일 state 변수로 통합하는 것이 좋음

 

사용자가 폼을 사용할 때 커스텀 필드를 추가하게 되면 그룹화하는게 좋음

 

참고로 객체인 경우 다른 필드를 복사하지 않곤 하나의 필드만 업데이트 할 수 없음.


State의 모순 피하기

특정 변수가 동시에 하나의 값을 로직상으로 가질 수 없는 경우엔, status와 같은 state 변수를 두는게 좋다.


불필요한 state 피하기

props나 기존 state 변수로 일부 정보를 계산할 수 있으면, state를 새로 만들지 말자.

 

firstName / lastName / fullName

 

여기서 fullName은 필요없겠지 ??

 

Props를 state에 미러링하지 마세요.

이게 뭔소리냐

function Message({ messageColor })
	const [color, setColor] = useState(messageColor);

 

이런 코드가 있으면, color가 messageColor로 초기화가 되는데, 문제는 부모에서 나중에 다른 값으로 messageColor를 전달하더라도 color state 변수가 업데이트 되지 않음.

 

state는 첫 번째 렌더링 중에만 초기화된다.

 

그러니 prop을 미러링하지말자.

 

근데 원할 때가 있을 수 있음

 

그럴 때는 initial / default라는 prefix를 붙여서 사용하자. 단, 일회용 느낌으로

 

걍 안 쓰는게 좋을거 같음 ;;


State의 중복 피하기

import { useState } from 'react';

const initialItems = [
  { title: 'pretzels', id: 0 },
  { title: 'crispy seaweed', id: 1 },
  { title: 'granola bar', id: 2 },
];

export default function Menu() {
  const [items, setItems] = useState(initialItems);
  const [selectedItem, setSelectedItem] = useState(
    items[0]
  );
  
  ...
}

 

이런식으로 했을 때, items가 변경된다하더라도 selectedItem이 변경되지 않는다.

 

그래서 selectedId를 만들고 이를 이용해서 items에서 찾아사용하면 중복을 피할 수 있다.


깊게 중첩된 state 피하기

 

[리액트 스터디] - Updating Arrays in State

배열 JS에서는 변경 가능하지만, state로 저장할 때에는 변경할 수 없도록 처리해야함. 객체랑 마찬가지로, state에 저장된 배열을 업데이트하고 싶을 땐, 새 배열을 생성한 뒤 이를 이용하여 업데

ggodong.tistory.com

 

여기서 얘기했듯이 state를 업데이트 하고 싶을 때, 복사본을 만들어야함

 

이게 깊으면 깊을 수록, 코드가 복잡해지니 평탄화를 노리자.

 

 

State 구조 선택하기 – React

The library for web and native user interfaces

ko.react.dev

근데, filter로 걸렀으면 자동으로 GC가 수거해가는거 아닌가 ??


 

State 구조 선택하기 – React

The library for web and native user interfaces

ko.react.dev

첫번째

import { useState } from 'react';

export default function Clock(props) {
  return (
    <h1 style={{ color: props.color }}>
      {props.time}
    </h1>
  );
}

 

두 번째

import { useState } from 'react';
import AddItem from './AddItem.js';
import PackingList from './PackingList.js';

let nextId = 3;
const initialItems = [
  { id: 0, title: 'Warm socks', packed: true },
  { id: 1, title: 'Travel journal', packed: false },
  { id: 2, title: 'Watercolors', packed: false },
];

export default function TravelPlan() {
  const [items, setItems] = useState(initialItems);

  function handleAddItem(title) {
    setItems([
      ...items,
      {
        id: nextId++,
        title: title,
        packed: false
      }
    ]);
  }

  function handleChangeItem(nextItem) {
    setItems(items.map(item => {
      if (item.id === nextItem.id) {
        return nextItem;
      } else {
        return item;
      }
    }));
  }

  function handleDeleteItem(itemId) {
    setItems(
      items.filter(item => item.id !== itemId)
    );
  }

  return (
    <>  
      <AddItem
        onAddItem={handleAddItem}
      />
      <PackingList
        items={items}
        onChangeItem={handleChangeItem}
        onDeleteItem={handleDeleteItem}
      />
      <hr />
      <b>{items.filter(item => item.packed).length} out of {items.length} packed!</b>
    </>
  );
}

 

세 번째

import { useState } from 'react';
import { initialLetters } from './data.js';
import Letter from './Letter.js';

export default function MailClient() {
  const [letters, setLetters] = useState(initialLetters);
  const [highlightedLetter, setHighlightedLetter] = useState(null);

  function handleHover(letter) {
    setHighlightedLetter(letter.id);
  }

  function handleStar(starred) {
    setLetters(letters.map(letter => {
      if (letter.id === starred.id) {
        return {
          ...letter,
          isStarred: !letter.isStarred
        };
      } else {
        return letter;
      }
    }));
  }

  return (
    <>
      <h2>Inbox</h2>
      <ul>
        {letters.map(letter => (
          <Letter
            key={letter.id}
            letter={letter}
            isHighlighted={
              letter.id === highlightedLetter
            }
            onHover={handleHover}
            onToggleStar={handleStar}
          />
        ))}
      </ul>
    </>
  );
}

 

해당 이슈가 발생하는 이유는 highlightedLetter 문자 객체를 보관하고 있다는 점이 문제임

 

즉 letters가 변경되면 highlightedLetter 문자 객체완ㄴ 다른 새 객체가 발생해서 그런거임

 

네 번째

import { useState } from 'react';
import { letters } from './data.js';
import Letter from './Letter.js';

export default function MailClient() {
  const [selectedIds, setSelectedIds] = useState([]);

  const selectedCount = selectedIds.length;

  function handleToggle(toggledId) {
    if (selectedIds.includes(toggledId)) {
      setSelectedIds(selectedIds.filter(id => id !== toggledId));
    } else {
      setSelectedIds([...selectedIds, toggledId]);
    }
  }

  return (
    <>
      <h2>Inbox</h2>
      <ul>
        {letters.map(letter => (
          <Letter
            key={letter.id}
            letter={letter}
            isSelected={
              // TODO: allow multiple selection
              selectedIds.includes(letter.id)
            }
            onToggle={handleToggle}
          />
        ))}
        <hr />
        <p>
          <b>
            You selected {selectedCount} letters
          </b>
        </p>
      </ul>
    </>
  );
}

저번 시간과 마찬가지로 리액트가 아닌 툴을 쓸 때도 이 내용을 머리에 심고 개발하는게 좋을거 같다.

반응형
LIST