게으른 컴공생
article thumbnail

React 빠르게 복습하기 1. Tutorial

개요

예~~전에 리액트를 잠깐 깔짝 해본적이 있는데

(여기서 -> https://github.com/osamhack2021/web_Guntor-Guntee_EarlyFried)

그땐 프로젝트의 전반적인 이해 없이 그냥 열심히만 했던 기억이 있다.

 

마침 이번 2학기에 한 교수님의 연구실에서 코딩 알바를 하게 되었는데 거기서 React랑 Spring을 쓸 것 같고,

현재 진행중인 방울이 프로젝트의 랜딩페이지를 만들어볼겸 빠르게 복습해보기로 했다.

 

전체적인 과정은 React 공식 홈페이지를 따라가볼 예정이다.

IDE는 VSCode를 쓰기로 했다.

 

공식홈페이지의 첫번째 자료인 튜토리얼을 따라가보자

 

Tutorial: Tic-Tac-Toe – React

The library for web and native user interfaces

react.dev

 

개발환경세팅

간단한 Tic-Tac-Toe 게임을 만들며 React의 전반을 짚어보는 것이 Tutorial의 목적이다.

 

React 공식 홈페이지에서는 편리한 웹 개발 환경을 제공하지만 나는 나중에 로컬에서 작업을 해야하기 때문에 작업 환경을 세팅했다.

1. node.js 설치

 

Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

여기서 설치하고 설치 이후에 잘 설치되었는지

다음 명령어들로 확인해보자

npm -v
node -v

2. react-create-app 설치

다음 명령어를 입력해 react-create-app 모듈을 설치하자

(앞으로의 명령어들은 권한문제 때문에 실행이 안될 수도 있다. 그러면 앞에 sudo를 붙여주자)

npm install -g creat-react-app

역시나 다음 명령어로 잘 설치되었는지 확인한다

create-react-app -V

3. react 프로젝트 폴더 생성

리액트 프로젝트를 저장할 폴더를 만들고

그 아래 지금 만들 프로젝트가 들어갈 폴더를 만든다

4. 프로젝트 생성

VSCode에서 해당 폴더(예제에선 sample-project)를 연다(File->Open Folder...)

VSCode의 터미널을 켜서 다음을 입력해 프로젝트를 생성한다

create-react-app 앱이름

Finder나 파일탐색기로 그 경로를 봤을 때 node_module을 비롯해 뭔가 많이 생겼다면 잘 만들어진 것이다.

5. 앱 실행

다음을 입력해 앱을 실행하면 브라우저에서 실행된다.

npm start

 

튜토리얼

기본코드

export default function Square() {
  return <button className="square">X</button>;
}

공식홈페이지에서 가장 최초로 제시되는 기본 코드이다.

React는 javascript 라이브러리로서 javascript 문법을 따른다.

나도 문법이 기억이 잘 안나긴 하지만 그때그때 배운다고 생각하자.

 

문법에 관해 체크해볼 것은 다음과 같다

1. export -> javascript 예약어로, 외부 파일에서 이 함수를 볼 수 있음(모듈 개념)

2. default -> main 함수라는 의미

3. return 이후 html 구문 -> React에선 JSX라는, html을 javascript 안에서 쓸 수 있게 해주는 문법을 사용한다. 이때 button은 html에서 그랬듯이 element라고 부른다

 

App.js

import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

VSCode로 React App을 만들었다면 위와 같은 코드의 App.js 파일을 확인할 수 있다.

아까 이해한 기본코드와 눈치를 좀 조합해보면 쉽게 이해할 수 있을 것이다.

보통 앱을 처음 만들면 default function이 App인 것을 볼 수 있다.

공식 홈페이지의 예제를 적용하려면 조금의 수정이 필요하겠다.

 

React는 Component라는 요소로 구성되어 있는데, UI element들을 관리하고, 업데이트 하는 등의 역할을 한다고 한다. 내 생각엔 그냥 javascript function이 Component라고 봐도 될 것 같다.

 

index.js

웹을 해본 적이 있다면 봤을 법한 그 이름 index!

프로젝트의 public 폴더에 최초로 진입하는 파일인 index.html이 있고

거기에 root라는 id를 가진 div가 있는데 index.js에서 그 div에 App을 render하고 있다.

공식 홈페이지의 표현을 빌리자면, web browser와 App.js를 연결해주는 파일이다.

 

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

VSCode에서 최초로 보이는 코드는 공식 홈페이지와는 내용이 사뭇 다르지만, 핵심만 보고 넘어가면 될 것 같다.

 

React, ReactDOM, App이라는 모듈, 라이브러리와 css 파일을 추가하는 부분을 보자

React는 뭐 그냥 React고, App도 아까 본 그 main function이고, index.css도 그냥 css 파일인데,

ReactDOM은 뭘까?

 

웹을 해본 적이 있다면 DOM에 대해 들어본 적이 있을 것이다.

Document Object Model, 그러니까 내가 웹 브라우저로 보는 그 문서가 트리 구조로 옮겨진 것으로 보면 된다.

그렇게 구조화 되어서 접근이 가능하게 해주는 일종의 API인 것이다.

 

근데 그러면 기존의 DOM이 있을텐데 왜 ReactDOM이 따로 필요한가?

이것은 나중에 내용이 나오고 나도 구체적으로는 모르지만

우선 React가 관리하는 DOM이 따로 있고 React가 여기서 업데이트를 진행을 한 뒤에

실제 DOM에 반영하는 것이라고 알고 있자.

 

3x3 board 만들기

이제 공식홈페이지의 예제로 돌아오자

아까 봤던 최초 코드를 이용해서 숫자가 써있는 정사각형이 3x3으로 있는 board를 만들 것이다

요게 결과물

export default function Square() {
  return (
    <>
      <div className="board-row">
        <button className="square">1</button>
        <button className="square">2</button>
        <button className="square">3</button>
      </div>
      <div className="board-row">
        <button className="square">4</button>
        <button className="square">5</button>
        <button className="square">6</button>
      </div>
      <div className="board-row">
        <button className="square">7</button>
        <button className="square">8</button>
        <button className="square">9</button>
      </div>
    </>
  );
}

최종적으로 위와 같은 코드를 작성하게 된다

html 태그가 가장 바깥에는 하나여야하기 때문에 이처럼 빈 태그로 감싸서 리턴해주게 된다.

그리고 3단으로 쌓아줘야 하기 때문에 board-row라는 CSS 속성을 정의해준다.(css 파일에 따로 작성) 

 

이렇게 해놓고 보니 너무 더럽다.

하나의 button을 component로 빼주자.

 

하나의 button을 Square라는 component로 두고

9개의 Square를 포함하는 Component를 Board로 두자

 그러면 다음과 같은 코드로 작성할 수 있다.

function Square({ value }) {
  return <button className="square">{value}</button>;
}

export default function Board() {
  return (
    <>
      <div className="board-row">
        <Square value="1" />
        <Square value="2" />
        <Square value="3" />
      </div>
      <div className="board-row">
        <Square value="4" />
        <Square value="5" />
        <Square value="6" />
      </div>
      <div className="board-row">
        <Square value="7" />
        <Square value="8" />
        <Square value="9" />
      </div>
    </>
  );
}

 

Square가 value라는 인자?를 받는 것 빼고는 구조만 바뀌었다.

이 value라는 인자?는 prop이라고 부르며 함수의 인자와 같은 포지션인 것 같다.

 

코드가 재사용성이 증가해서 좀 보기 편해졌다.

 

State

React의 Component는 State를 가진다. 마치 Class의 property처럼 자체적인 값을 가지는 것이다.

이 state를 사용하는 방식은 정해져있다.

우리는 각각의 Square가 클릭이 될 때마다 빈칸이었다가 X였다가 바뀌도록 해줄 것이다.

그러려면 1. 클릭이 된 상태인지 state 정보와 2. 클릭 시 변경시켜주는 click handler가 필요하다.

 

import { useState } from 'react';

function Square() {
  const [value, setValue] = useState(null);

  function handleClick() {
    setValue('X');
  }

  return (
    <button
      className="square"
      onClick={handleClick}
    >
      {value}
    </button>
  );
}

export default function Board() {
  return (
    <>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
    </>
  );
}

 

결과적인 코드는 위와 같다.

useState라는 친구를 import 한 것과

value를 할당해준 것

Square에서 리턴하는 { value }는

1. state와 관련된 문법이고

handleClick이라는 함수의 정의와

Square에서 리턴하는 div의 onClick 속성에 들어있는 {handleClick}은

2. clickHandler와 관련된 문법이다

 

useState는 React에서 제공하는 Hook 중 하나이다.

Hook은 특별한 기능을 가진 함수 정도로 이해하면 되겠다.

useState는

  const [$valueName, $valueSetterHandle] = useState($initialValue);

와 같은 형태로 사용하면 된다. 이건 그냥 외우면 됨.

useState에는 state변수가 초기에 가질 값을 넘겨주고

useState의 리턴 값으로 중 state 변수 자체와

state 변수를 설정할 수 있는(setter) 함수의 이름이 주어진다

 

import 시에 useState 주위의 중괄호와 useState의 리턴을 할당할 때

대괄호를 써서 할당하는 걸 비구조화할당, destructing이라고 부른다.

javascript 문법이니 한 번 검색해보면 좋을 듯

 

아무튼 이 useState hook을 사용해서 value라는 state 변수를 만든 뒤

button의 text로써 사용해준다. 이때 JSX 문법을 이용해 중괄호로 감싸면

value 변수의 값을 출력해줄 수 있다.(그냥 value로 쓰면 button의 text에 "value" 문자열이 나옴)

 

button의 onClick 속성에 달아줄 handleClick 함수에선 useState Hook을 통해 얻은 setValue 함수를 쓰고 있다

 

State Hoisting

승자가 누구인지 판단하기 위해서는 모든 Square의 값을 알고 판단해야 한다.

그 판단은 parent인 Board에서 하는 것이 자연스러운데, 지금 상태에서 이 판단을 하려면

(모든 state가 각각 Square 안에 있기 때문에) 모든 Square에게 state를 물어봐야 한다.

그래서 모든 State를 Board가 가지고 있고 Board가 각각의 Square에게

표시해야하는 값을 알려주도록 구조를 변경해보자.

완성된 코드는 아래와 같다.

(공식 홈페이지에 따르면, 이런 상황에서는 이런 식으로 children의 상태를 parent에서 총체적으로 관리하는 게 권장된다고 한다.)

 

import { useState } from 'react';

function Square({ value, onSquareClick }) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

export default function Board() {
  const [squares, setSquares] = useState(Array(9).fill(null));

  function handleClick(i) {
    const nextSquares = squares.slice();
    nextSquares[i] = 'X';
    setSquares(nextSquares);
  }

  return (
    <>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

 

우선 Board에 있던 state가 squares가 되었다. 그리고 모든 원소가 null 값인 9칸의 array로 설정되었다.

그리고 이 state들을 Square의 prop에 전달해주고 있다(squares[0], squares[1], squares[2]... 와 같은 형식으로)

 

Square가 하나의 prop을 더 받고 있는 모습을 볼 수 있다.

state는 Board에 있기 때문에 클릭했을 때 실제로 할 행동은 Board에서 handleClick이라는 함수로 정의하고 있다.

그리고 Square들에게 넘겨주고 있는데 잘 보면 화살표 함수(arrow function, 익명 함수)를 쓰고 있다.

왜 그냥 { handleClick(i) }의 형태로 작성하지 않은 것일까?

왜냐하면 그렇게 작성하면 onSquareClick에 handleClick(i)를 실행하는 함수를 등록하는 것이 아니라

handleClick(i) 자체를 할당하게 되는데 이것은 그냥 handleClick(i)를 호출한 것이기 때문이다.

그러니까 onSquareClick에 전달되는 값은 handleClick의 리턴 값이 되는 것이다(물론 현재 리턴 값이 있지도 않음)

 

handleClick에서는 slice 함수를 이용해 배열을 복사한 뒤 클릭된 위치의 값만 바꿔준 뒤

setSquares로 state 값을 업데이트 해주고 있다

 

+ 추가로 공식홈페이지에 따르면, onSquareClick와 handleClick과 같은 형식의 작명이 권장된다고 한다

 

이제 내가 7번 Square를 클릭하면 그 Square의 onClick에 연결된 익명함수가 호출되고 그 익명함수에서는 handleClick(7)을 호출해서

squares state의 index가 7인 부분의 값이 X로 바뀐 값이 squares state에 저장된다.

 

+ handleClick은 closure이기 때문에 내부 속성인 squares에 접근이 가능하다.(closure는 자신이 생성된 scope를 기억하고 있는 함수라고 한다.)

https://poiemaweb.com/js-closure

 

직접 setSquares를 변경하지 않는 이유

왜 배열을 복사한 뒤 변화시키고 setSqaures를 하는 걸까?

바로 squares[0] = 'X'와 같이 변화시켜도 되는 거 아닐까? 싶을 수 있다.

그 첫번째 이유는 바로 재사용에 유리하기 때문이다.

이전의 squares 정보가 필요한 상황이 있을 수 있다(game history 기능 제공 등)

두번째 이유는 React의 component가 re-render를 해야하는지 여부를 결정하는 데 도움이 되기 때문이라고 한다.

(React의 Component는 parent나 본인의 state가 변경되면 re-render를 한다고 한다. re-render를 하면 ReactDOM이 바뀌는 듯?)

 

O 모양의 마킹 추가하기

이제 Tic-Tac-Toe 게임이 돌아가려면 상대 플레이어의 O 마킹이 필요하다.(지금은 X 마킹만 있다.)

그러기 위해선 1. 현재 누구의 턴인지 알려줄 state 변수가 필요하고 2. 현재 턴에 따라서 마킹을 변화시키면 된다.

완성 코드는 다음과 같다.

 

import { useState } from 'react';

function Square({value, onSquareClick}) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

export default function Board() {
  const [xIsNext, setXIsNext] = useState(true);
  const [squares, setSquares] = useState(Array(9).fill(null));

  function handleClick(i) {
    if (squares[i]) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = 'X';
    } else {
      nextSquares[i] = 'O';
    }
    setSquares(nextSquares);
    setXIsNext(!xIsNext);
  }

  return (
    <>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

 

xIsNext라는 state 변수가 추가되었고

handleClick에 xIsNext에 따라 판단하는 if문이 생겼다.

 

맨 앞에 squares 값이 null이 아니면(O나 X로 마킹이 되면) return 해버리는 예외처리 구문이 추가되었다.

저게 있어야 이미 마킹된 블럭을 다른 마킹으로 변경해버리는 조작이 불가능하게 만들 수 있다.

 

게임 승리 판단

게임 승리를 판단하는 것은 뭐 딱히 React를 배우는 것은 아니긴 한디..

암튼 1. 보드 상황을 보고 승리를 판단하는 함수를 만들고 2. 적절한 때에 호출한다.

를 해주면 되겠다.

완성 코드는 아래와 같다.

 

import { useState } from 'react';

function Square({value, onSquareClick}) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

export default function Board() {
  const [xIsNext, setXIsNext] = useState(true);
  const [squares, setSquares] = useState(Array(9).fill(null));

  function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = 'X';
    } else {
      nextSquares[i] = 'O';
    }
    setSquares(nextSquares);
    setXIsNext(!xIsNext);
  }

  const winner = calculateWinner(squares);
  let status;
  if (winner) {
    status = 'Winner: ' + winner;
  } else {
    status = 'Next player: ' + (xIsNext ? 'X' : 'O');
  }

  return (
    <>
      <div className="status">{status}</div>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
}

cacluateWinner 함수의 방식을 보고 배울만 한 것 같다. 나중에 알고리즘 문제풀이에 이용할 수 있을 수도?

이 함수를 이용해 winner 변수의 값을 정하고 이에 따라 출력할 텍스트를 결정할 수 있다.

조금은 비효율적?인 것 같긴 한데 handleClick에서도 게임이 종료되었으면 클릭에 따른 반응이 없게 예외처리를 해줄 수 있다.

 

조금 궁금한 것이 생겼는데 왜 winner를 handleClick에서 사용하지 않는 것일까? 그러면 더 효율적일 것 같은데 말이지.. state만 사용 가능한 건가? 그렇다면 왜 winner를 state로 선언하지 않은 것일까?

 

히스토리 기능 추가

내용이 좀 길어서 지치기 때문에

바로 완성 코드를 보자

 

import { useState } from 'react';

function Square({ value, onSquareClick }) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

function Board({ xIsNext, squares, onPlay }) {
  function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = 'X';
    } else {
      nextSquares[i] = 'O';
    }
    onPlay(nextSquares);
  }

  const winner = calculateWinner(squares);
  let status;
  if (winner) {
    status = 'Winner: ' + winner;
  } else {
    status = 'Next player: ' + (xIsNext ? 'X' : 'O');
  }

  return (
    <>
      <div className="status">{status}</div>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

export default function Game() {
  const [history, setHistory] = useState([Array(9).fill(null)]);
  const [currentMove, setCurrentMove] = useState(0);
  const xIsNext = currentMove % 2 === 0;
  const currentSquares = history[currentMove];

  function handlePlay(nextSquares) {
    const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
    setHistory(nextHistory);
    setCurrentMove(nextHistory.length - 1);
  }

  function jumpTo(nextMove) {
    setCurrentMove(nextMove);
  }

  const moves = history.map((squares, move) => {
    let description;
    if (move > 0) {
      description = 'Go to move #' + move;
    } else {
      description = 'Go to game start';
    }
    return (
      <li key={move}>
        <button onClick={() => jumpTo(move)}>{description}</button>
      </li>
    );
  });

  return (
    <div className="game">
      <div className="game-board">
        <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
      </div>
      <div className="game-info">
        <ol>{moves}</ol>
      </div>
    </div>
  );
}

function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
}

 

우선 export default인 function이 Board에서 Game으로 변경되었다.

이 Game은 Board를 가지고 있는 최상위 component라고 보면 되겠다.

그래서 여러 상태값들이 Square에서 Board 였을 때와 마찬가지로 Board에서 Game으로 lift up(state hoisting)되었다.

 

매 턴 board의 상태값(squares)를 저장하는 history state가 생겼다.

그리고 예전 상태로 돌아간 뒤 턴을 진행하면 거기서부터 history를 다시 만들어야하기 때문에

그 위치 값을 가진 currentMove도 생겼다.

그리고 xIsNext는 currentMove의 홀짝으로 판단이 가능하므로 state에서 내려와서 일반 변수가 되었다.

공식홈페이지에 따르면, 이렇게 다른 state로 표현가능한 중복 state는 제거하는 것이 좋다고 한다.

(당연하다. 추적해야하는 값이 하나 줄면 성능도 좋아질 거고)

 

board는 이제 xIsNext와 squares, onPlay를 prop으로 받는다.

이때 onPlay는 Game에 선언되어있는 Closure로서, 클릭 위치에 따라서 history와 currentMove state를 업데이트한다.

 

board 옆에 history 목록을 보여주고, 클릭 시 그 상태로 이동할 수 있도록

button을 나열해야하는데 그 부분은 moves라는 변수에 구현되어있다.

map 함수는 뒤에 나오는 함수를 적용해서 새로운 배열을 만들어서 리턴해준다.

뒤에 있는 인자 중 squares에는 배열의 각 원소가 들어가고, move에는 index가 들어간다.

이 moves 변수는 jumpTo를 호출하는 button들로 구성되어있고 Game이 리턴하는 <ol> 태그 안에 들어간다.

 

jumpTo는 currentMove를 변화시켜서 currentSquare를 변화시키고 후에 Square가 클릭되면

history를 그 부분부터 다시 만들어가게 된다.

 

moves에 할당하는 map 함수에서 리턴하는 <li>태그에 key라는 속성이 있는 것을 볼 수 있다.

이 key는 React가 요소를 구분하는데 사용하는 identifier로서, 어떤 요소가 삭제되고 생겨났으며 업데이트 되었는지 판단하는데 사용한다.

만약 key를 명시하지 않으면 React는 자동으로 array의 index 값을 넣어주게 되는데 이는 순서가 섞이거나 했을 때 비효율적인 re-render를 초래한다고 한다.

이 key는 전체 코드에서 unique할 필요 없으며 그냥 그 컴포넌트와 sibling들 간에서 unique하면 된다고 한다.

공식 홈페이지에 따르면 dynamic하게 변하는 list에서는 이 key를 잘 할당해주는 것이 중요하다고 한다.

지금의 예제에서는 순서가 섞일 일이 없으니 그냥 move를 할당하고 있다.

 

 

마무리

이렇게 React의 기초(element, component, prop, state)의 대해서 한번 쑥 흝어봤다.

오 생각보다 오래걸려서 놀랐지만 시작이 반이라고 나머지 문서들은 좀 짧아보이니 빠르게 치고 나가보자.

 

 

'기술 > 리액트' 카테고리의 다른 글

React 빠르게 복습하기 2. Thinking in React  (2) 2023.08.23
profile

게으른 컴공생

@노나니노나

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그