본문 바로가기

내용 복습/Next.js

[React] State 정리(1)

1. 파일 구조 설명

  • 파일 구조
    • index.html파일은 리액트가 제어한다. 그리고 index.jsx에는 reactDOM.createRoot(document.getElementById로 시작하는 코드가 있는데, 전체 코드는 root라는 id를 가진 앱 컴포넌트를 렌더링하는 것이다.
    • index.html은 결국 웹사이트를 방문하면 처음 보게 되는 내용이라는 것이다. 따라서 props나 state의 영향을 받지 않아 변하지 않을 내용(ex. 메인로고)이라면 이 페이지에 삽입해도 되는 것이다. 모든 내용을 컴포넌트에 넣으려고 애쓰지 않아도 된다는 것이 골자이다.
    • src/assets/ 폴더에 이미지를 저장하면 웹사이트 방문자에게 공개적으로 제공되지 않기에 웹사이트 방문자가 접근할 수 없지만, 코드파일에서 사용할 수는 있다. 코드 파일에서 가져온 이미지는 빌드 프로세스에 의해 인식되어 최적화되며, 웹사이트에 제공하기 직전에 public/ 폴더에 삽입된다.
    • 빌드 프로세스에 의해 처리되지 않는 이미지는 공개적으로 제공되는 public/ 폴더에 사용해야 하고, 컴포넌트 내에서 사용되는 이미지는 일반적으로 src/ 폴더에 저장하는 것이 일반적이다.
  • 앞으로 tic-tac-toe 게임을 만들어 볼 것이다.

 

1) player 컴포넌트 작성

// Player.jsx

import { useState } from "react";

export default function Player({ name, symbol }) {
  const [isEditing, setIsEditing] = useState(false);

  function handleEditClick() {
    setIsEditing(true);
  }
  
  let playerName = <span className="player-name">{name}</span>
  
  if (isEditing) {
    playerName = <input type="text" required />
  }

  return (
    <li>
      <span className="player">
        {playerName}
        <span className="player-symbol">{symbol}</span>
      </span>
      <button onClick={handleEditClick}>{Edit}</button>
    </li>
  );
}

 

데이터를 조정해서 UI가 업데이트 되도록 하려면 useState를 써야한다. 상태값을 생성함으로써 해당 값을 수정할 수 있는 함수를 가져온다. 이 함수를 통해 리액트가 UI를 수정하는 것이다. 초기값에는 여러가지 값이 들어갈 수 있는데, 대표적으로 boolean도 사용할 수 있다. 예를 들어 false로 설정하면 초기에는 값을 띄우지 않는다는 것이다.

 

1. 초기값을 () 안에 설정한다.

2. 두 요소가 있는 배열을 배열 구조분해할당을 사용해서 두 요소를 각각의 상수에 저장한다.

3. state를 이용하는 함수인 handle~~이라는 함수를 생성한다. 암묵적으로 함수가 식별자 역할을 해서 특정상황에 작동할 것이라는 규칙이다.

4. onClick이라는 프로퍼티에 handle 함수를 넣어준다. 주의할 점은 함수가 이곳에서 실행되는 것이 아니라 button이라는 요소에 실려 리액트가 실행되는 것이기에 ()를 넣으면 안 되고 이름만 적어야 한다.

 

 

2) 조건적 상태 업데이트

// Player.jsx

import { useState } from "react";

export default function Player({ name, symbol }) {
  const [isEditing, setIsEditing] = useState(false);

  function handleEditClick() {
    setIsEditing(true);
  }
  
  let playerName = <span className="player-name">{name}</span>
  // let btnCaption = 'Edit'
  
  if (isEditing) {
    playerName = <input type="text" required />
  // bntCaption = 'Save'
  }

  return (
    <li>
      <span className="player">
        {playerName}
        <span className="player-symbol">{symbol}</span>
      </span>
      <button onClick={handleEditClick}>{isEditing ? 'Save' : 'Edit'}</button>
    </li>
  );
}

 

isEditing(수정중) 상태일 때는 Save를 출력하고 아니면 Edit을 출력하도록 삼항연산자를 썼다. 주석처리된 부분으로 처리할 수 있지만 삼항연산자를 쓰는 것이 코드가 간결하기 때문에 두개 다 명시해 놓았다.

 

  function handleEditClick() {
    setIsEditing(isEditing ? false : true);
  }
  
  let playerName = <span className="player-name">{name}</span>
  // let btnCaption = 'Edit'
  
  if (isEditing) {
    playerName = <input type="text" required value={name} />
  // bntCaption = 'Save'
  }

 

코드에 약간의 변화가 있는데 value값이 추가되어 input에 name 값이 존재하게 된다는 것과 isEditing의 삼항연산자를 통해 true/false를 오갈 수 있다는 것이다. 하지만 어차피 boolean 값이기 때문에 !isEditing으로 대체할 수 있다. 이것으로 해당값이 반전된다. 이 테크닉은 유용하지만 이 상황에서 상태함수를 쓰는 방식은 틀렸다.

 

상태를 이전 값에 기반하여 변경할 경우, 함수를 하나 전달해야하는데 해당 상태변경 함수로 보내야 한다.
즉, 반환하고자 하는 새로운 상태값으로 보내면 안 된다.

 

왜냐하면 여기서 전달하는 이 함수를 리액트가 호출하여 자동적으로 현재 상태값을 가지게 되기 때문이다. 이것은 상태 변경 전의 값이 입력되는 것과 같다고 할 수 있다.

 

  function handleEditClick() {
    setIsEditing((editing) => !editing);
  }

 

매개변수 editing은 true 혹은 false로, isEditing과 동일하나 이 매개변수는 값으로 인식되어 리액트가 함수 호출 시 동적으로 설정 및 전달하게 된다. 이 함수를 setIsEditing로 전달할 때 설정하고자 하는 새로운 상태를 반환하게 된다.

 

그냥 !isEditing만 쓰게 되면 이 작업을 수행하는 리액트가 상태에 대한 변화의 스케줄을 조율하게 된다. 이것은 setIsEditing과 같은 상태 변경 함수를 통해 실행하므로 상태변경이 즉각 수행되는 것이 아니라, 리액트가 미래에 수행하고자 하는 상태 변경 스케줄을 조율한다.

 

  function handleEditClick() {
    setIsEditing(!isEditing); // true
    setIsEditing(!isEditing); // false
  }

 

뭐가 문제인지는 이렇게 두 줄을 연속해서 써보면 알 수 있는데, 언뜻 보기에 함수를 실행하면 주석처럼 true -> false로 연속해서 변경되어 아무일도 일어나지 않을 것이라 생각이 들지만, 사실 위에서 작성한 코드와 같이 작동한다. 왜냐하면 두 줄 모두 isEditing이 false인 시점을 기준으로 하기 때문이다. 이 내장함수는 생성될 당시 컴포넌트 함수 실행에 포함되어 있기에 isEditing은 시작 시점의 값을 가지고 있다.

setIsEditing이 isEditing을 호출할 때 상태 변경 스케줄을 조율하게 되는데, 첫째줄이 즉시 실행되지 않기 때문에 다음 줄이 실행 될 때도 이전과 동일한 상태가 유지된다. 동일한 컴포넌트 함수 실행의 사이클을 돌고 있기 때문이다. 그리고 이 두 상태변화의 일정은 각자의 작업 이후에 실행된다. 그래서 상태 변경 시 이전의 상태값에 기반하여 변경하고 싶을 때, 함수 방식으로 쓰게 되면 상태 값이 언제나 최신 버전을 보장한다는 것이다.

 

 

'내용 복습 > Next.js' 카테고리의 다른 글

[React] 상태관리 라이브러리 Recoil 연습  (0) 2024.04.01
[React] state(2)  (1) 2024.03.28
[React] Props(2)  (1) 2024.03.14
[React] Fragment에 대해 알아보자  (0) 2024.03.05
[React] props의 원리  (0) 2024.02.15