Development/ReactJs

[React] 리액트 데이터 관리 - Props, State

재은초 2024. 11. 19. 18:25
반응형

Props(properties)란?

  • React 컴포넌트가 자바스크립트 함수의 개념과 비슷하다. 이와 같은 맥락으로 props는 함수에서 인수(argument)의 개념과 비슷하게 이해할 수 있다.
  • props는 컴포넌트에 데이터를 전달하고자 할 때 사용하며, 이렇게 전달된 값은 변수를 통해 참조할 수 있다.
  • React에서 데이터의 흐름은 위에서 아래로(부모 컴포넌트로부터 자식 컴포넌트에게) 전달되므로, props의 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트에서 설정할 수 있다.

props 설정하고 사용하기

  • React에서 props를 설정하는 방법은 HTML 요소에 속성을 설정하는 문법과 동일하다.
// App.js

const Laptop = (props) => {
  return <h1>내 노트북은 {props.brand} 노트북입니다.</h1>;
};

// App 컴포넌트에서 Laptop 컴포넌트를 사용할 때 brand라는 이름으로 데이터를 전달
const App = () => {
  return <Laptop brand="Samsung" />;
};

export default App;
  • 이렇게 전달된 props를 사용하는 방법은 자바스크립트 함수에 인수를 전달하는 문법과 동일하다. 또한 props는 객체 형태로 전달되기 때문에 props의 값을 참조하기 위해서는 자바스크립트의 속성 접근자(.)를 사용해야 한다.
return <h1>내 노트북은 {props.brand} 노트북입니다.</h1>;
  • 화살표 함수를 사용할 때 인수가 하나 밖에 없을 경우에는 인수를 감싸는 소괄호(())를 생략할 수 있다. 하지만 인수가 하나도 없을 때에는 소괄호를 생략할 수 없다.

props로 여러 데이터 전달하기

// App.js

const Laptop = (props) => {
  return (
    <>
      <h1>내 노트북은 {props.brand} 노트북입니다.</h1>
      <h2>내 노트북의 OS는 {props.os} 입니다.</h2>
    </>
  );
};

// App 컴포넌트에서 Laptop 컴포넌트로 여러 개의 데이터를 전달
const App = () => {
  return <Laptop brand="Samsung" os="Windows" />;
};

export default App;
  • 위의 코드에서는 전달된 props를 참조하기 위해서 매번 ‘props.’이라는 구문을 사용하고 있다. 하지만 전달되는 props의 개수가 많아질수록 작성되는 코드에도 ‘props.’라는 구문이 함께 늘어나게 된다.
  • 이와 같이 불필요하게 중복되는 코드를 줄이기 위해서 ES6 문법부터 추가된 구조 분해 할당(destructuring assignment) 구문을 사용해 볼 수 있다. 구조 분해 할당 구문이란 배열로부터는 값을, 객체로부터는 속성을 해체하여 그 값들을 개별적인 변수에 각각 저장할 수 있도록 해 주는 자바스크립트 표현식이다.
// App.js

const Laptop = ({ brand, os }) => {
  return (
    <>
      <h1>내 노트북은 {brand} 노트북입니다.</h1>
      <h2>내 노트북의 OS는 {os} 입니다.</h2>
    </>
  );
};

const App = () => {
  return <Laptop brand="Samsung" os="Windows" />;
};

export default App;

defaultProps로 기본값 설정하기

  • 부모 컴포넌트로부터 해당 데이터가 전달되지 않았기에 해당 부분이 공백으로 표시된다. 이와 같은 경우를 대비하여 자식 컴포넌트에서 defaultProps를 미리 설정해 놓으면 부모 컴포넌트에서 props를 설정하지 않았을 경우 기본적으로 defaultProps의 값을 사용하게 된다.
// App.js

const Laptop = ({ brand, os }) => {
  return (
    <>
      <h1>내 노트북은 {brand} 노트북입니다.</h1>
      <h2>내 노트북의 OS는 {os} 입니다.</h2>
    </>
  );
};


Laptop.defaultProps = {
  brand: "LG",
  os: "Linux"
};


const App = () => {
  return <Laptop brand="Samsung" />;
};


export default App;

children으로 태그 사이의 내용 참조하기

  • React 컴포넌트의 시작 태그와 종료 태그 사이의 내용(문자열, 자식 엘리먼트 등)을 참조하고 싶다면 children이라는 props를 사용하면 된다.
// App.js

const Wrapper = (props) => {
  return <h1>{props.children}</h1>;
};


const App = () => {
  return <Wrapper>Hello, World!</Wrapper>;
};


export default App;

props는 읽기 전용이다

  • props는 부모 컴포넌트에서 그 값을 설정하므로, 자식 컴포넌트에서는 해당 props를 읽을 수 밖에 없다. 만약 해당 props의 값을 변경하고 싶다면 부모 컴포넌트에서 그 값을 다시 설정해야 한다.
// 순수 함수 - 동일한 입력값에 대해 언제나 동일한 결과를 반환

function Add(a, b) {
  return a + b;
}


// 비순수 함수 - 동일한 입력값을 받아도 변수 c에 따라 결과값이 달라질 수 있음

let c = 5;

function Add(a, b) {
  return a + b + c;
}
  • React에서는 모든 컴포넌트가 자신의 props를 다룰 때 반드시 이와 같은 순수 함수처럼 동작할 것을 요구하고 있다. 물론 실제 애플리케이션에서 UI는 언제나 동적으로 변화하기 때문에 사용자의 입력, 네트워크 응답이나 다른 엘리먼트에 대한 응답에 따라 값을 수정해야 할 필요가 생길 수 있다. React에서는 이러한 경우 상태(state)라는 개념을 사용하여 이 문제를 해결하고 있다.

 

State란?

  • React에서는 props와 state라는 객체를 가지고 데이터를 다룬다. 두 객체 모두 View를 렌더링하는데 사용되는 데이터를 가지고 있다는 공통점을 가지고 있지만, 한 가지 중요한 차이점이 있다.
  • props는 함수의 매개변수처럼 부모 컴포넌트로부터 데이터를 전달받지만, state는 함수 내에서 선언된 변수처럼 컴포넌트 내에서 관리된다. 따라서 컴포넌트에서는 props의 값을 변경할 수 없지만, state의 값은 변경할 수 있다.
  • React v16.8 이전까지는 함수 컴포넌트에서는 state를 사용할 수 없었다. 따라서 state를 사용하기 위해서는 어쩔 수 없이 클래스 컴포넌트를 사용해야 했지만, React v16.8 부터 도입된 useState Hook을 사용하면 함수 컴포넌트에서도 state를 사용할 수 있게 되었다.

https://www.tcpschool.com/react/react_data_state

useState Hook으로 state 관리하기

  • 함수 컴포넌트에서는 useState Hook을 사용하여 state를 손쉽게 관리할 수 있습니다. Hook은 React에서 제공하는 특별한 기능을 수행하는 함수들이다.
// Counter.js
// 버튼을 클릭하면 화면의 숫자가 증가하는 카운터 예제

import { useState } from "react";

const Counter = () => {
  // 0을 초기값으로 하는 state 생성
  const [state, setState] = useState(0);

  return (
    <div>
      <h1>State 값 : {state}</h1>
      {/* setState를 사용하여 state의 값을 1씩 증가시킴 */}
      <button onClick={() => setState(state + 1)}>1씩 증가</button>
    </div>
  );
};

export default Counter;
  • 함수 컴포넌트에서 useState Hook을 사용하는 문법은 다음과 같다.
  • useState는 맨 처음 렌더링을 수행할 때 초기 상태 값(initialState)을 인수로 전달받고, 최신 상태를 유지하는 값(state)과 그 값을 업데이트하는 함수(setState)를 반환한다. 이때 우리가 앞서 살펴 본 구조 분해 할당 구문을 사용하여 배열의 값을 각각의 변수에 나누어 저장하고 있다. 이렇게 useState를 통해 반환된 첫 번째 값인 state 변수에는 항상 최신 상태의 state 값이 저장되게 된다.
// useState 문법
const [state, setState] = useState(initialState);

// useState 예제
const [count, setCount] = useState(0);

여러 개의 state 관리하기

  • 상황에 따라 하나의 컴포넌트 내에서 여러 개의 state를 생성하고 관리해야 할 경우가 생길 수 있다. 이때 우리는 두 가지 방법 중 하나를 선택할 수 있다.
  • 첫 번째 방법은 객체와 배열을 활용하여 서로 연관되는 데이터들을 하나의 state 변수로 묶어서 관리하는 방법이다. 이 방법을 사용하면 하나의 state 변수로 관련된 데이터를 한 번에 모두 모아서 관리할 수 있다. 하지만 이 방법을 사용하면 하나의 state 값만을 변경해야 할 경우에도 나머지 state 값들을 잃지 않기 위해서 모두 같이 업데이트 해줘야만 한다.
// 객체를 활용한 state 예시

const [area, setArea] = useState({
  left: 0,
  top: 0,
  width: 200,
  height: 100
});


// left 값을 50 증가하는 코드 - 모두 같이 업데이트
setArea({ left: 50, top: 0, width: 200, height: 100 });
  • 두 번째 방법은 각각의 독립된 여러 개의 state 변수로 나누어 관리하는 방법이다. [state, setState]의 형태로 여러 개의 state 변수를 선언하게 되면, 변수의 이름을 서로 다르게 선언할 수 있기 때문에 각각의 변수를 개별적으로 업데이트 할 수 있다. 또한, 나중에 관련 state 로직이 복잡해지면 사용자 정의 Hook을 사용하여 손쉽게 해당 로직을 별도의 파일로 추출할 수 있다.
// 여러 개의 state로 나누어 관리하는 예시

const [left, setLeft] = useState(0);
const [top, setTop] = useState(0);
const [width, setWidth] = useState(200);
const [height, setHeight] = useState(100);
  • 일반적으로는 이 두 가지 방법을 혼용하여 서로 관련 있는 state를 몇 개의 독립된 state 변수로 그룹화하여 관리하는 것이 코드의 가독성이나 효율성 측면에서 좋은 방안이 될 수 있다. 나중에 프로젝트의 규모가 커져서 state와 관련된 로직이 복잡해지면 Reducer나 사용자 정의 Hook 등을 활용하여 state를 관리하는 것이 좋다.
// 두 가지 방법의 혼용

const [position, setPosition] = useState({ left: 0, top: 0 });
const [size, setSize] = useState({ width: 200, height: 100 });

state를 직접 수정해서는 안된다

  • React에서 state를 사용할 때에는 몇 가지 주의해야 할 사항들이 있다.
  • 우선 state의 값을 바꿀 때에는 state의 값을 직접 수정해서는 안되며, useState를 통해 반환된 setState() 함수를 사용하여 수정해야 한다.
// 잘못된 코드
const [left, setLeft] = useState(0);
left += 20;

// 올바른 코드
const [left, setLeft] = useState(0);
setLeft(left + 20);
  • 또한, state의 값이 객체나 배열인 경우에는 setState() 함수를 사용하여 해당 state의 값을 곧바로 변경해서는 안된다.
  • React에서는 참조 타입인 객체나 배열의 경우 불변성(immutability)을 지켜야만 한다. 즉, 객체나 배열을 직접 수정해서는 안된다는 의미이며, 해당 객체를 업데이트하기 위해서는 원하는 값으로 새로운 객체를 만들어 덮어쓰는 방식으로 업데이트해야만 한다.
  • 이렇게 객체나 배열의 불변성을 지켜야 하는 이유는 바로 렌더링에서의 최적화 방식 때문이다. React에서는 부모 컴포넌트가 업데이트될 경우에 해당 컴포넌트의 자식 컴포넌트들도 모두 함께 리렌더링 된다. React의 Virtual DOM은 특정 컴포넌트의 업데이트 필요성을 컴포넌트가 가지고 있는 이전의 state 값과 새로 업데이트된 state 값을 비교하여 판단한다. 즉, 불변성이 지켜지지 않는다면 객체나 배열의 내부 데이터가 변경되어도 React는 해당 데이터가 바뀐 것을 감지하지 못하게 되는 것이다.
  • 따라서 ES6 문법부터 제공되는 spread 연산자(…)를 사용하여 기존의 객체를 새로운 객체로 먼저 복사한 다음에 특정 state 값을 업데이트하고 setState() 함수를 통해 새로운 state 값으로 업데이트해야만 한다.
// Spread 연산자로 size 객체의 모든 값을 복사하여 새로운 copy 객체를 생성

const copy = { ...size };
import { useState } from "react";

const Area = () => {
  const [size, setSize] = useState({ width: 200, height: 100 });

  return (
    <div>
      <h1>
        너비 : {size.width}, 높이 : {size.height}
      </h1>
      <button
        onClick={() => {
          const copy = { ...size };
          copy.width += 20;
          setSize(copy);
        }}
      >
        너비 증가
      </button>
      <button
        onClick={() => {
          const copy = { ...size };
          copy.height += 10;
          setSize(copy);
        }}
      >
        높이 증가
      </button>
    </div>
  ); };

export default Area;

state의 업데이트는 비동기적이다

  • React는 인지 성능(perceived performance)의 향상을 위해 setState() 함수의 실행을 미루거나 여러 컴포넌트를 일괄적으로 업데이트 할 수 있다. 즉, setState() 함수는 컴포넌트를 항상 즉각 업데이트하는 것은 아니라는 점을 기억해야 한다. 이와 같은 특성으로 인해 setState() 함수를 호출하자마자 state 객체에 접근하는 것은 잠재적으로 문제의 원인이 될 수 있다.
// 잘못된 코드

import { useState } from "react";

const Counter = () => {
  const [count, setCount] = useState(0);

  const incrementCount = () => {
    setState(count + 1);
  };

  const manyIncrementCount = () => {
    incrementCount();
    incrementCount();
    incrementCount();
  };

  return (
    <div>
      <h1>Count 값 : {count}</h1>
      <button onClick={manyIncrementCount}>3씩 증가</button>
    </div>
  );
};

export default Counter;


// 수정된 코드
const incrementCount = () => {
  setState((count) => {
    return count + 1;
  });
};

 

 
반응형