TIL 45 / 46일차 : REACTJS 입문 : State

minjun kim·2024년 11월 21일
0

1. State

✨ state는 리액트에서 가장 기초가 되면서,
✨ 렌더링에 가장 큰 영향을 끼치는 요소입니다.

1-1. State란?

State란 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다.
이뉴는 바로 UI 엘리먼트의 반영을 위해서이다.

우리가 앞에서 name이라는 정보를 const name = "민준" 라고 했다면,
만약 name이라는 값이 바뀌어야만 하는 정보였어야 한다면 state로 생성합니다.

state를 생성할 때는 useState() 를 사용합니다.

아래 코드를 보면

GrandFather 커포넌트에서 기존에 있었던 const name = "김할아" 라는 코드가
const [name,setName] = useState("김할아"); 라는 코드로 사용되고 있다.

import React, { useState } from 'react';

function GrandFather() {
  const [name, setName] = useState("김할아"); // 이것이 state!
  return <Mother grandFatherName={name} />;
}

// .. 중략 

앞으로 useState 라는 함수를 이용해 state를 만들게 됩니다.
useStatestate를 만들어주는 리액트에서 제공하는 기능입니다.

그래서 리엑트에만 존재하는 개념이자 기능입니다.
앞으로 우리는 이것을 기능이 아닌 훅이라고 표현합니다.

1-2. useState 훅을 사용하는 방식은

const [ value, setValue ] = useState( 초기값 ); // 배열의 구조분해 할당이 사용된걸 확인할 수 있다.

먼저 const 로 선언을 하고 [] 빈 배열을 생성한 다음,
배열의 첫번째 자리엔 이 state의 이름, 그리고 두번째 자리에는 set을 붙여서 state의 이름을 붙입니다.

그리고 useState() 의 인자에는 이 state의 원하는 처음 값을 넣어줍니다.

const [name, setName] = useState("김할아");

위에서는 name이라는 state를 만들었고, name state의 초기값은 "김할아" 로 지정했습니다.
이때 초기값을 initial state 라고 부릅니다.

state의 정의처럼 언제든지 변할 수 있는 값이기 때문에 초기값이라는 개념이 존재하는 것입니다.

1-3. state 변경하기

state를 변경할때는 setValue(바꾸고 싶은 값) 를 사용한다.

state란 컴포넌트안에서 변할 수 있는 값입니다.
예를 들어서 김할아라는 이름이 백할아로 바뀌었다고 한다면

우리는 setName을 통해 이름을 바꿀 수 있습니다.
setName("백할아") 로 사용을 하면 이름이 바뀌게 될 것입니다.

// src/App.js

import React, { useState } from "react";

function Child(props) {
  return (
    <div>
      <button
        onClick={() => {
					props.setName("박할아"); // 드디어 받은 setName을 실행합니다.
        }}
      >
        할아버지 이름 바꾸기
      </button>
      <div>{props.grandFatherName}</div>
    </div>
  );
}

function Mother(props) {
  return (
    <Child grandFatherName={props.grandFatherName} setName={props.setName} /> // 받아서 다시 주고
  );
}

function GrandFather() {
  const [name, setName] = useState("김할아");
  return <Mother grandFatherName={name} setName={setName} />; // 주고
}

function App() {
  return <GrandFather />;
}

export default App;

그러나 위처럼 바뀐 값은 브라우저를 샐로고침하면 다시 초기값으로 바뀝니다.
바꾼 값은 어디에 저장되는 것은 아니기 때문에 단순히 화면에서만
바뀐 값으로 리렌더링 되는 것입니다.

2. state 기본 응용

✨ state와 각종 DOM handler event와의 조합을 알아보자

2-1. useState + onClick event

우리는 버튼을 눌렀을 때 하고 싶은 행동을 함수로 제작할건데,
onClickHandler 라는 함수를 만들고 onClick={} 에 넣어주었습니다.
React에는 이러한 방식으로 함수와 컴포넌트를 연결시킵니다.

우리는 이 함수를 이벤트 핸들러라고 표현합니다.

import React from "react";

function App() {
  // 버튼을 눌렀을 때 하고 싶은 행동 
  function onClickHandler() {
    console.log("hello button");
  }
  return (
    <div>
      <button onClick={onClickHandler}>버튼</button>
    </div>
  );
}

export default App;

아래는 state를 구현하고 이벤트 핸들러와 연결한 버전입니다.

import React, { useState } from "react";

function App() {
  const [name, setName] = useState("길동이");

  function onClickHandler() {
    setName("누렁이");
  }

  return (
    <div>
      {name}
      <button onClick={onClickHandler}>버튼</button>
    </div>
  );
}

export default App;

버튼을 누르면 setName() 안의 값이 누렁이기 때문에,
최초 state 값 길동이에서 누렁이로 바뀌게 됩니다.

2-2. useState + onChange event

input과 state 구현하기

input에서는 보통 사용자가 입력한 값을 state로 관리하는 패턴을 자주 사용합니다.

import React, { useState } from "react";

const App = () => {
  const [value, setValue] = useState("");

  return (
    <div>
      <input type="text" />
    </div>
  );
};

export default App; 

이와 같이 inputuseState를 사용해 input의 값을 넣을 value라는 state를 생성합니다.
App을 보면 function keyword를 사용하지 않고 화살표 함수를 사용해보았다.

화살표 함수, function 키워드 모두 똑같이 함수 컴포넌트를 만들 수 있습니다.

이벤트 핸들러 구현하고 state와 연결하기

먼저 inputonChange라는 이벤트를 불러내,
생성한 이벤트 핸들러 함수를 넣습니다.

import React, { useState } from "react";

const App = () => {
  const [value, setValue] = useState("");

  const onChangeHandler = (event) => {
    const inputValue = event.target.value;
    setValue(inputValue);
    console.log(inputValue);
    
  };

	console.log(value) // value가 어떻게 변하는지 한번 콘솔로 볼까요?

  return (
    <div>
      <input type="text" onChange={onChangeHandler} value={value} />
    </div>
  );
};

export default App;

그러면 이벤트 핸들러 안에서 자바스크립트의 event 객체를 꺼내 사용할 수 있고,
사용자가 입력한 input의 값은 event.target.value로 꺼내 사용할 수 있게 됩니다.

마지막으로 statevalueinputattributevalue에 넣어주면 inputstate 연결이 끝났습니다.

onChangevalue를 둘 다 연결해야만 한다면
React에서는 input요소의 value 속성과 onChange 이벤트를 함께 사용하여
입력 필드의 상태를 관리하는 것이 일반적입니다.

이러한 패턴을 제어 컴포넌트라고 합니다.

React에 의해 값이 제어되는 컴포넌트가 제어 컴포넌트
= state에 의해 값이 제어되는 input 컴포넌트는 제어 컴포넌트

제어 컴포넌트에서는 React의 상태가 소스 오브 트루스 역할을 하며,
이를 통해 입력 필드의 현재 값이 항상 React 컴포넌트의 상태와 동기화됩니다.

source of truth란 어떤 정보의 정확하고 최신의 상태를 유지하는 주된 위치 또는
저장소를 가리킵니다.

위의 예시에서 리엑트의 stateinputvalue와 엮여있으므로 state 자체가 input
변경된 정보 자체를 가리키게 됩니다.

데이터가 일관성 있고, 정확성을 유지하기 위해 필수입니다.

이와 반대되게 value를 연결하지 않는 비제어 컴포넌트가 있는데,
useState를 사용하면서 input 요소의 value 속성에 상태를 연결하지 않는 경우,
이는 비제어 컴포넌트와 유사한 동작을 하게 됩니다.

아래는 비제어 컴포넌트의 예시 코드이다.

import React, { useState } from "react";

const App = () => {
  const [inputValue, setInputValue] = useState("");

  const onChangeHandler = (event) => {
    setInputValue(event.target.value);
  };

  return (
    <div>
      <input type="text" onChange={onChangeHandler} />
      <p>입력값: {inputValue}</p> {/* 입력값을 화면에 표시 */}
    </div>
  );
};

export default App;

주의할 점은 상태 업데이트 지연과 비제어 컴포넌트이다.
위의 방식은 특정 상황에서 유용할 수도 있지만,

일반적으로 폼 데이터를 더 명확하게 제어하고 싶다면 제어 컴포넌트 방식이 좋다.

3. 불변성

✨ 불변성은 리액트의 state 개념을 이해하기 위해 필요한 개념이다.

3-1.불변성 (+ 메모리에 저장되는 원리?)

불변성이란?

불변성이란 메모리에 있는 값을 변경할 수 없는 것을 의미합니다.
자바스크립트 데이터 형태중에 원시 데이터는 불변성이 있고,
원시 데이터가 아닌 객체 배열 함수는 불변성이 없습니다.

// 불변성 깨뜨리는 방법
let numbers = [1, 2, 3];
numbers.push(4); // 배열에 직접 요소를 추가
console.log(numbers); // [1, 2, 3, 4]

// 불변성 유지하는 방법
let numbers = [1, 2, 3];
let newNumbers = [...numbers, 4]; // 새 배열을 생성하여 기존 배열을 변경하지 않음
console.log(numbers); // [1, 2, 3]
console.log(newNumbers); // [1, 2, 3, 4]

변수를 저장하면 메모리에서 어떻게 저장이 될까?

우리가 let number = 1 이라 선언을 하면 메모리에 1이라는 값이 저장됩니다.
그러면 number 라는 변수 메모리에 있는 1을 참조하게 되고,
그리고 우리가 let secondNumber = 1 이라고 다른 변수를 선언했다 가정해보면...

이때도 자바스크립트는 이미 메모리에 생성되어 있는 1이라는 값을 참조합니다.
numbersecondNumber는 변수의 이름은 다르지만,
같은 메모리의 값을 바라보고 있는 것이다.
그래서 콘솔에 number === secondNumber를 하면 true가 보이게 됩니다.

그러나 원시데이터가 아닌 값 (객체, 배열, 함수) 는 이렇지 않습니다.
let obj_1 = {name: "kim"} 이라는 값을 선언하면 메모리에 obj_1이 저장됩니다.
그리고 이어서 let obj_2 = {name:'kim'} 이라고 같은 값을 선언하면
obj_2라는 메모리 공간에 새롭게 저장이 됩니다.
그래서 obj_1 === obj_2 는 false가 됩니다.

데이터를 수정하면 어떻게 될까?

원시데이터로 돌아와서 만약 기존에 1이던 numbernumber = 2 라고 새로운 값을 할당하면
메모리에서는 어떻게 될까요? 원시 데이터불변성이 있기 때문에
기존 메모리에 저장이 되어 있는 1이라는 값이 변하지 않고, 새로운 메모리 저장공간에 2가 생기고,
number라는 값을 새로운 메모리 공간에 저장된 2를 참조하게 됩니다.

그래서 secondNumber를 콘솔에 찍으면 여전히 1이라고 콘솔에 보인다.
numbersecondNumber는 각각 다른 메모리 저장공간을 참조하고 있기 때문입니다.

obj_1를 수정해보면 obj_1.name = 'park' 라고 새로운 값을 할당하면
객체는 불변성이 없기 때문에 기존 메모리 저장공간에 있는 {name:'kim'} 이란 값이
{name:'park'}으로 바뀌어 버립니다.

원시 데이터는 수정을 했을 때 메모리에 저장된 값 자체는 바꿀 수 없고,
새로운 메모리 저장공간에 새로운 값을 저장합니다.
원시데이터가 아닌 데이터는 수정했을 때 기존에 저장되어 있던
메모리 저장공간의 값 자체를 바꿔버립니다.

3-2. 리액트에서 불변성이 가지는 의의

리액트에서는 화면을 리랜더링 할지 말지 결정할 때 state의 변화를 확인합니다.
state가 변했으면 리렌더링 하는 것이고, state가 변하지 않았으면 리렌더링 하지 않습니다.

그때, state가 변했는지 변하지 않았는지 확인하는 방법이 state의 변화 전,
후의 메모리 주소를 비교합니다.

만약 리액트에서 원시데이터가 아닌 데이터를 수정할 때 불변성을 지켜주지 않고,
직접 수정을 가하면 값은 바뀌지만 메모리주소는 변함이 없게 되는 것입니다.

그래서 개발자가 값은 바꿨지만 리액트는 state가 변했다고 인지하지 못하게 됩니다.
결국 마땅히 일어나야 할 리렌더링이 일어나지 않게 되는 것이 됩니다.

3-3. 불변성 지키기 예시

배열을 setState 할 때 불변성을 지켜주기 위해 spread operator 를 사용해서
기존의 값을 복사하고, 그 이후의 값을 수정하는 방식을 사용합니다.

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;

0개의 댓글

관련 채용 정보