React : state 객체 사용

최혜린·2024년 10월 19일

리액트에서 state 객체 사용하기

리액트에서 state 객체는 컴포넌트의 동적인 데이터를 관리하는 데 필수적인 역할을 한다. state 객체를 활용하여 UI의 변화를 관리하고, 사용자와의 상호작용에 따라 상태를 업데이트할 수 있다. 이번 포스팅에서는 state 객체의 기본 개념, 사용 방법, 그리고 몇 가지 유용한 팁을 정리해 보겠다.


State 객체에서 배열 사용 시 함수 정리

https://react.dev/learn/updating-arrays-in-state#removing-from-an-array

리액트에서 배열을 상태로 사용할 때는 배열을 직접 변경하는 함수를 피하고, 새로운 배열을 반환하는 함수를 사용하는 것이 중요하다. 아래는 배열 조작 시 피해야 할 함수와 지향해야 할 함수의 목록이다.

구분피해야 할 함수 (Mutates the array)지향해야 할 함수 (Returns a new array)
추가push, unshiftconcat, [...arr] (spread syntax)
제거pop, shift, splicefilter, slice
교체splice, arr[i] = ... (assignment)map
정렬reverse, sort배열을 복사한 후 정렬 (예: const sortedArr = [...arr].sort(...))

설명

  1. 추가:

    • 피해야 할 함수: pushunshift는 기존 배열을 직접 수정하므로, 리액트의 상태 관리 원칙에 어긋난다.
    • 지향해야 할 함수: concat과 스프레드 문법([...arr])은 기존 배열을 변경하지 않고 새로운 배열을 생성한다.
  2. 제거:

    • 피해야 할 함수: pop, shift, splice는 배열을 직접 수정하므로, 상태 관리에 문제가 생길 수 있다.
    • 지향해야 할 함수: filterslice는 조건에 따라 새로운 배열을 반환하여 상태 업데이트에 적합하다.
  3. 교체:

    • 피해야 할 함수: splice와 배열 인덱스를 사용한 대입은 기존 배열을 변경하므로 피해야 한다.
    • 지향해야 할 함수: map을 사용하면 각 요소를 변형하여 새로운 배열을 생성할 수 있다.
  4. 정렬:

    • 피해야 할 함수: reversesort는 기존 배열을 직접 수정하므로 사용을 피해야 한다.
    • 지향해야 할 방법: 배열을 복사한 후 정렬하는 방법을 사용하여 새로운 배열을 생성해야 한다. 예를 들어, const sortedArr = [...arr].sort(...)와 같이 작성할 수 있다.

결론

리액트에서 배열을 사용할 때는 항상 배열이 직접 수정되지 않도록 주의해야 한다. 위의 목록을 참고하여, 상태 관리의 원칙에 맞는 방법으로 배열을 조작하도록 하자.



1. state 객체의 기본 개념

  • state의 역할: state는 컴포넌트가 렌더링할 데이터를 저장한다. 사용자 입력, API 응답, 시간 변화 등 다양한 요소에 따라 변할 수 있는 데이터이다.
  • 불변성 유지: state를 직접 변경하는 대신, 새로운 객체를 만들어 상태를 업데이트해야 한다. 이는 리액트의 성능을 최적화하고, 변경 사항을 감지하는 데 도움을 준다.

2. useState 훅 사용하기

리액트에서 상태를 관리하기 위해 useState 훅을 사용한다. 기본적인 사용 방법은 다음과 같다.

import { useState } from "react";
import './App.css'

function App(){
  const [count, setCount] = useState(0);
  const increase=()=>{
    setCount(cnt => cnt+1)
  }
  const decrease=()=>{
    setCount(cnt => cnt-1)
  }
  const reset=()=>{
    setCount(0)
  }
  return(
    <div>
      <p>count:{count}</p>
      <button onClick={increase}>증가</button>
      <button onClick={decrease}>감소</button>
      <button onClick={reset}>리셋</button>
    </div>
  )
}
export default App

리액트 불변성

  • 불변성이란, 초기 할당한 값을 변경하지 않아야 함을 의미한다.
  • state는 내부 값이 변경되어도 주소 자체가 변하지 않으면 변화를 인식하지 못한다.
  • state 안의 값을 직접 변경하면 변화가 즉시 반영되지 않는다.
import { useState } from "react";
import './App.css'

function App() {
  const [obj1, setObj1] = useState({ name: "최혜린" });

  function changeName() {
    obj1.name = "최영준";
    setObj1(obj1);
  }

  return (
    <div>
      <h1>{obj1.name}</h1>
      <button onClick={() => changeName()}>이름 바꾸기</button>
    </div>
  );
}
export default App;

  • 위 코드에서 버튼을 눌러도 변화가 즉시 적용되지 않음을 보여준다.
  • 구조 분해 할당을 사용하여 기존 값을 가져오고 변화를 줄 값만 수정하여 새로운 객체를 생성해야 한다.
  • 기존 주소는 연결이 끊겨 가비지 컬렉션의 대상으로 삭제된다. 이는 성능상의 이슈가 있을 수 있다.
import { useState } from "react";
import './App.css'

function App() {
  const [obj1, setObj1] = useState({ name: "최혜린", age: 30 });

  function changeName() {
    setObj1({
      ...obj1,
      name: "최영준"
    });
  }

  return (
    <div>
      <h1>{obj1.name}</h1>
      <h3>{obj1.age}</h3>
      <button onClick={() => changeName()}>이름 바꾸기</button>
    </div>
  );
}
export default App;

state 배열 갱신 - 리스트 추가하기 예제-1

  • 점수를 올리면 10점씩 올라가고 리셋 버튼을 누르면 0이 되는 코드이다.
  • 이름과 점수를 입력하고 점수를 올리는 버튼을 누르면 해당 사람의 점수가 올라간다.
import { useState } from "react";
import './App.css'

function App() {
  const [obj1, setObj1] = useState({ name: "최혜린", score: 0 });
  const [obj2, setObj2] = useState({ name: "최영준", score: 0 });
  const [input1, setInput1] = useState("");
  const [input2, setInput2] = useState("");

  function score1() {
    setObj1({
      ...obj1,
      score: obj1.score + 10
    });
  }

  function score2() {
    setObj2({
      ...obj2,
      score: obj2.score + 10
    });
  }

  function reset() {
    setObj1({
      ...obj1,
      score: 0
    });
    setObj2({
      ...obj2,
      score: 0
    });
  }

  function inputScore() {
    if (input1 === "최혜린") {
      setObj1({
        ...obj1,
        score: obj1.score + parseInt(input2)
      });
    } else if (input1 === "최영준") {
      setObj2({
        ...obj2,
        score: obj2.score + parseInt(input2)
      });
    } else {
      alert("최혜린, 최영준 중 하나를 입력하세요");
    }
  }

  const style = {
    display: "flex"
  };

  return (
    <>
      <input type="text" value={input1} onChange={e => setInput1(e.target.value)} placeholder="최혜린 or 최영준" />
      <input type="number" value={input2} onChange={e => setInput2(e.target.value)} placeholder="점수를 입력하세요" />
      <button onClick={() => inputScore()}>점수 추가</button>

      <div style={style}>
        <div>
          <h1>{obj1.name}</h1>
          <h3>{obj1.score}</h3>
          <button onClick={() => score1()}>점수 올리기</button>
        </div>
        <div>
          <h1>{obj2.name}</h1>
          <h3>{obj2.score}</h3>
          <button onClick={() => score2()}>점수 올리기</button>
        </div>
        <button onClick={() => reset()}>리셋</button>
      </div>
    </>
  );
}
export default App;


state 배열 갱신 - 리스트 추가하기 예제-1

import { useState } from "react";
import './App.css'

function App() {
  const [arr1, setArr1] = useState(["한식", "중식", "양식"]);

  function loop(st) {
    setArr1([
      ...arr1,
      st
    ]);
  }

  return (
    <div>
      <ul>
        {arr1.map((str, i) => (
          <li key={i}>{str}</li>
        ))}
      </ul>
      <button onClick={() => loop("일식")}>클릭</button>
    </div>
  );
}

export default App;

state 배열 갱신 - 리스트 추가하기 예제-2(concat 이용)

import { useState } from "react";
import './App.css'

function App(){
const [arr1,setArr1] = useState(["한식","중식","양식"])

function insertStr(st){
  setArr1(arr1.concat(st))
}

return(
  <div>
  <ul>
    {arr1.map((str,i)=>(
      <li key={i}>{str}</li>
    ))}
  </ul>
  <button onClick={()=>insertStr("일식")}>클릭</button>
  </div>
)

}

export default App


입력값을 리스트에 추가하는 로직 생성

  1. input 창으로 값을 받는다
  2. 버튼을 누르면 해당 값을 리스트에 추가한다
  3. 추가된 값을 ul>li 형태로 화면에 출력해서 보여준다
import { useState } from "react";
import './App.css'

function App(){
const [input1,setInput1]=useState("")
const [arr1,setArr1]=useState([])

function addList(input1){
  setArr1(arr1.concat(input1))
  setInput1("")
}


return(
  <div>
    <ul>
      {arr1.map((str,i)=>(
        <li key={i}>{str}</li>
      ))}
    </ul>
<input type="text" value={input1} onChange={e=>setInput1(e.target.value)} />
<button onClick={()=>addList(input1)}>추가</button>
</div>
)

}

export default App


key에 index를 사용하면 안되는 이유??

  • 인덱스를 key로 사용하면 삭제 시 의도한 대로 작동하지 않을 수 있다. 따로 id나 고유 값을 넣어 key로 사용하는 것이 좋다.
import { useState } from 'react';
import './App.css'

function App() {
  const [arr1, setArr1] = useState([
    { id: 1, menu: "불고기피자" },
    { id: 2, menu: "고구마피자" },
    { id: 3, menu: "포테이토피자" },
    { id: 4, menu: "불닭피자" },
    { id: 5, menu: "치즈피자" }
  ]);

  function deleteMenu(num) {
    setArr1(arr1.filter((obj, i) => i !== num));
  }

  return (
    <div>
      <ul>
        {arr1.map((obj, i) => (
          <li key={i}>{obj.menu}</li>
        ))}
      </ul>
      <button onClick={() => deleteMenu(2)}>포테이토피자 삭제</button>
    </div>
  );
}
export default App;

  • 인덱스가 삭제 후 변경되면서 잘못된 항목이 삭제될 수 있다.

id 값을 key로 주어야 하는 이유

  • id 값을 key로 사용하면 의도한 대로 삭제할 수 있다.
import { useState } from 'react';
import './App.css'

function App() {
  const [arr1, setArr1] = useState([
    { id: 1, menu: "불고기피자" },
    { id: 2, menu: "고구마피자" },
    { id: 3, menu: "포테이토피자" },
    { id: 4, menu: "불닭피자" },
    { id: 5, menu: "치즈피자" }
  ]);

  function deleteMenu(num) {
    setArr1(arr1.filter((obj) => obj.id !== num));
  }

  return (
    <div>
      <ul>
        {arr1.map((obj) => (
          <li key={obj.id}>{obj.menu}</li>
        ))}
      </ul>
      <button onClick={() => deleteMenu(2)}>포테이토피자 삭제</button>
    </div>
  );
}
export default App;


객체 데이터 조작 예제

  • 객체 데이터를 조작할 때 구조 분해 할당을 사용하여 간결하게 작성할 수 있다.
import { useState } from 'react'
import './App.css'

function App(){
 const [inputs,setInput]=useState({
  name: '',
  nickname :''
 });
 function onchange(e){
  const{value,name} = e.target
  setInput({
    ...inputs,
    [name]:value
  })
 }

 return(
  <div>
    <input type="text" name='name' value={inputs.name} onChange={onchange} />
    <input type="text" name='nickname' value={inputs.nickname} onChange={onchange} />
    <div>
      <p>:</p>
      {inputs.name} | {inputs.nickname}
    </div>
  </div>
 )
}



export default App
  • 구조분해할당을 통해 코드의 가독성을 높일 수 있다.
 const {name,nickname} = inputs
 
  • 위와 같이 inputs를 구조분해할당하여 정의하면, 다음과 같이 코드를 간결하게 작성할 수 있다.
<input type="text" name='name' value={name} onChange={onchange} /> 
  • inputs.name 대신 name을 사용할 수 있어 가독성이 향상된다.

3. 복잡한 상태 관리

리액트에서 객체 또는 배열을 상태로 사용할 때는 복잡한 데이터 구조를 쉽게 다룰 수 있도록 몇 가지 팁을 사용할 수 있다.

a. 객체 상태 관리

객체 상태를 사용할 때는 구조 분해 할당을 활용해 불변성을 유지해야 한다.

const [user, setUser] = useState({ name: "홍길동", age: 25 });

const updateName = (newName) => {
  setUser({ ...user, name: newName });
};

b. 배열 상태 관리

배열 상태를 관리할 때는 배열 메서드를 사용하여 새로운 배열을 반환해야 한다.

const [items, setItems] = useState([]);

const addItem = (item) => {
  setItems([...items, item]);
};

const removeItem = (index) => {
  setItems(items.filter((_, i) => i !== index));
};
profile
산으로 가는 코딩.. 등산 중..🌄

0개의 댓글