게임 완성하기
이제 틱택토 게임을 위한 기본 구성 요소를 가지고 있음
완전한 게임을 위해 게임판의 “X”와 “O”를 번갈아 표시할 필요가 있으며 승자를 결정하는 방법이 필요
State 끌어올리기
현재 게임의 state를 각각의 Square 컴포넌트에서 유지하고 있음
승자를 확인하기 위해 9개 사각형의 값을 한 곳에 유지할 것임
Board가 각 Square에 Square의 state를 요청해야 한다고 생각할 수도 있음
그리고 React에서 이런 접근이 가능하기는 하지만 이 방식은 코드를 이해하기 어렵게 만들고 버그에 취약하며 리팩토링이 어렵기 때문에 추천하지 않음
각 Square가 아닌 부모 Board 컴포넌트에 게임의 상태를 저장하는 것이 가장 좋은 방법임
각 Square에 숫자를 넘겨주었을 때와 같이 Board 컴포넌트는 각 Square에게 prop을 전달하는 것으로 무엇을 표시할 지 알려줌
여러개의 자식으로부터 데이터를 모으거나 두 개의 자식 컴포넌트들이 서로 통신하게 하려면 부모 컴포넌트에 공유 state를 정의해야 함
부모 컴포넌트는 props를 사용하여 자식 컴포넌트에 state를 다시 전달할 수 있음
이것은 자식 컴포넌트들이 서로 또는 부모 컴포넌트와 동기화 하도록 만듦
state를 부모 컴포넌트로 끌어올리는 것은 React 컴포넌트를 리팩토링할 때 흔히 사용함
이번 기회에 시험해 보자
Board에 생성자를 추가하고 9개의 사각형에 해당하는 9개의 null 배열을 초기 state로 설정하기
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
};
}
renderSquare(i) {
return <Square value={i} />;
}
나중에 board를 채우면 this.state.squares 배열은 아래와 같이 보일 것임
[
'O', null, 'X',
'X', 'X', 'O',
'O', null, null,
]
Board의 renderSquare 함수는 현재 아래와 같은 형태임
renderSquare(i) {
return <Square value={i} />;
}
처음에는 모든 Square에서 0부터 8까지 숫자를 보여주기 위해 Board에서 value prop을 자식으로 전달했음
또 다른 이전 단계에서는 숫자를 Square의 자체 state에 따라 “X” 표시로 바꿈
그렇기 때문에 현재 Square는 Board에서 전달한 value prop을 무시하고 있음
이제 prop을 전달하는 방법을 다시 사용할 것입니다. 각 Square에게 현재 값('X', 'O', 또는 null)을 표현하도록 Board를 수정할 것임
Board의 생성자에서 squares 배열을 이미 선언했으며 renderSquare 함수를 아래와 같이 수정할 것임
renderSquare(i) {
return <Square value={this.state.squares[i]} />;
}
Square는 이제 빈 사각형에 'X', 'O', 또는 null인 value prop을 받음
다음으로 Square를 클릭할 때 발생하는 변화가 필요함
Board 컴포넌트는 어떤 사각형이 채워졌는지를 여부를 관리하므로 Square가 Board를 변경할 방법이 필요함
컴포넌트는 자신이 정의한 state에만 접근할 수 있으므로 Square에서 Board의 state를 직접 변경할 수 없음
대신에 Board에서 Square로 함수를 전달하고 Square는 사각형을 클릭할 때 함수를 호출할 것임
이제 Board의 renderSquare 함수를 아래와 같이 변경해보자
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
주의
반환되는 엘리먼트를 여러 줄로 나누어 가독성을 확보하였고 괄호를 추가하여 JavaScript가 return 뒤에 세미콜론을 삽입하지 않아도 코드가 깨지지 않음
이제 Board에서 Square로 value와 onClick 두 개의 props를 전달하였음
onClick prop은 Square를 클릭하면 호출되는 함수임
Square를 아래와 같이 변경하자
- Square의 render 함수 내부의 this.state.value를 this.props.value로 바꾸기
- Square의 render 함수 내부의 this.setState()를 this.props.onClick()으로 바꾸기
- Square는 게임의 상태를 유지할 필요가 없기 때문에 constructor를 지워주기
이렇게 바꾼 후에 Square는 아래와 같은 모습이 됨
class Square extends React.Component {
render() {
return (
<button
className="square"
onClick={() => this.props.onClick()}
>
{this.props.value}
</button>
);
}
}
Square를 클릭하면 Board에서 넘겨받은 onClick 함수가 호출됨
이 때 일어나는 일을 정리해보겠음
- 내장된 DOM <button> 컴포넌트에 있는 onClick prop은 React에게 클릭 이벤트 리스너를 설정하라고 알려줌
- 버튼을 클릭하면 React는 Square의 render() 함수에 정의된 onClick 이벤트 핸들러를 호출함
- 이벤트 핸들러는 this.props.onClick()를 호출합니다. Square의 onClick prop은 Board에서 정의되었음
- Board에서 Square로 onClick={() => this.handleClick(i)}를 전달했기 때문에 Square를 클릭하면 Board의 handleClick(i)를 호출함
- 아직 handleClick()를 정의하지 않았기 때문에 코드가 깨질 것임
지금은 사각형을 클릭하면 “this.handleClick is not a function”과 같은 내용을 표시하는 붉은 에러 화면을 보게됨
주의
DOM <button> 엘리먼트의 onClick 어트리뷰트는 내장된 컴포넌트라는 점에서 React에게 특별한 의미가 있음
Square같은 사용자 정의 컴포넌트의 경우 이름 지정은 자유로움
Square의 onClick prop이나 Board의 handleClick 함수에는 어떤 이름도 붙일 수 있으며 코드는 동일하게 작동함
React에서 이벤트를 나타내는 prop에는 on[Event], 이벤트를 처리하는 함수에는 handle[Event]를 사용하는 것이 일반적임
handleClick을 아직 정의하지 않았기 때문에 Square를 클릭하려고 할 때 에러가 발생함
이제 Board 클래스에 handleClick을 추가해보자
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
};
}
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});
}
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
이제 이전과 마찬가지로 Square를 클릭하여 사각형을 채울 수 있음
그러나 이제는 state가 각 Square 컴포넌트 대신에 Board 컴포넌트에 저장됨
Board의 상태가 변화할 때 Square 컴포넌트는 자동으로 다시 렌더링함
Board 컴포넌트의 모든 사각형의 상태를 유지하는 것으로 이후에 승자를 결정하는 것이 가능
Square 컴포넌트가 더 이상 state를 유지하지 않기 때문에 Square 컴포넌트는 Board 컴포넌트에서 값을 받아 클릭될 때 Board 컴포넌트로 정보를 전달함
React 용어로 Square 컴포넌트는 이제 제어되는 컴포넌트임
Board는 이들을 완전히 제어함
handleClick에서는 .slice()를 호출하는 것으로 기존 배열을 수정하지 않고 squares 배열의 복사본을 생성하여 수정하는 것에 주의하기
왜 squares 배열의 사본을 생성하였는지 다음 단락에서 설명할것임
참고 자료 : https://ko.reactjs.org/tutorial/tutorial.html#overview
'프로그래밍 > React' 카테고리의 다른 글
[React] React Hooks (1) - useState (0) | 2023.01.20 |
---|---|
[React] Hooks란? (0) | 2023.01.19 |
[React] props와 state 확인하기 - 리액트 자습서5 (0) | 2022.11.17 |
[React] 사용자와 상호작용하는 컴포넌트 만들기 - 리액트 자습서4 (0) | 2022.11.16 |
[React] 리액트 조건문 (0) | 2022.11.08 |