지난번 게시글에서 아래 부분까지 완료하였다.
- 3개의 컴포넌트 생성
- 매개변수 i를 받는 renderSquare 함수 생성
- 매개변수 i를 Board 컴포넌트에서 Square 컴포넌트로 props 전달
- 리액트 dev 확장파일을 통해 props 잘 전달된 점 확인
오늘은 나머지 부분을 시작해보자.
우선은 버튼을 클릭했을 때 'X' 또는 'O'로 플레이어가 나타날 수 있게 하자!
이를 위해
- board 컴포넌트 안에 state를 선언
- board 컴포넌트 안에 handleClick이라는 함수를 선언해서 props로 Square 컴포넌트에 전달
- Square 컴포넌트에서 온클릭 이벤트 작성 후 이벤트 동작 여부 확인
- handleClick 함수의 내용 작성
를 할 것이다.
우선, board 컴포넌트 안에 상태(state)를 선언해보자
1. 상태(state) 선언
- 각 버튼의 인덱스를 나타낼 수 있는 squares와
- 다음 플레이어 또는 승자를 나타낼 flag 역할을 할 xIsNext라는 속성을 작성
- state 선언 이유?
- 버튼 클릭 시 데이터가 변경됨
- 데이터가 변경되면 화면이 변경되어야 함
- 변경된 부분을 상태 변경(setState)을 통해 감지해야 함
코드는 다음과 같다
state = {
squares: Array(9).fill(null),
xIsNext: true
}
- squares 속성
- 버튼을 눌렀을 때 각각의 위치를 알 수 있어야 하므로 인덱스가 있는 배열로 작성
- 버튼을 누르기 전에는 공란이었다가 버튼을 누르고 난 후에는 플레이어가 표시되어야 하므로 null값으로 채움
- xIsNext 속성
- 단순히 flag의 역할만 하면 되므로 true 또는 false의 불리언 값이면 된다.
2. handleClick 함수 선언 후 하위 컴포넌트에 props로 전달 후 이벤트 작성
2-1. Board 컴포넌트에 함수 생성
우선은 해당 함수를 props로 보낸 뒤, Square 컴포넌트에서 props를 전달받아
onClick 이벤트를 걸어서 이벤트가 잘 작동하는 지 확인하기 위해서 코드를 단순히 작성할 것이다.
아래와 같이 함수를 선언하자.
handleClick = i =>{
console.log('hey')
}
handleClick 함수는 Board 컴포넌트 안에있는 renderSquare 함수의 리턴을 통해서
Square 컴포넌트에게 props로 전달될 것이므로,
renderSquare의 매개변수 인 i 값을 공유할 수 있다.
따라서, 마찬가지로 i 값을 매개변수로 받아서 각각의 버튼을 식별할 수 있는 인덱스로 사용할 것이다.
2-2. 선언한 함수를 props로 하위 컴포넌트로 전달하기
- 현재의 컴포넌트에서 함수를 실행시킬 것이 아니고 하위 컴포넌트로 단순히 전달만 할 것이므로,
- props로 전달할 onClick 변수의 값으로 함수 선언문 형태로 작성해주자.
renderSquare = i => {
return(
<Square
value = {i}
onClick = { ()=> {this.handleClick(i)} }
/>
)
}
3. Square 컴포넌트의 리턴 인 버튼에 onClick 이벤트 걸기
- Square 컴포넌트는 실제로 이벤트를 동작시킬 컴포넌트이므로,
- 이벤트를 동작시킬 타겟 인 버튼 엘리먼트에 onClick 속성을 주고, 속성값으로 props로 받은 onClick변수를 작성하자
<button
className="square"
onClick = {this.props.onClick}
>
{this.props.value}
</button>
<주의>
엘리먼트에 이벤트 작성할 때, 이벤트명은 반드시 camelCase로 작성하여야 한다
예시: onClick, keyDown 등
여기까지 코드를 작성하고 나면 아래와 같이 버튼을 클릭할 때마다
콘솔창에 hey가 입력되며 onClick 이벤트가 잘 작동하는 것을 확인할 수 있다.
이벤트가 잘 동작하는 것을 확인하였으면 이제는
handleClick 함수 안에 실제로 이벤트 동작 시 상태를 변경을 적용시킬 코드를 작성해보자
4. handleClick 함수 내부 코드 작성
이전에 작성된 state 값을 변경하는 코드를 작성해보도록 하자.
컴포넌트 내의 초기 생성자로 선언된 state는 불변성을 가지고 있는 객체이므로,
state 변경은 setState() 메서드를 통해서 변경을 적용시킬 것이다.
따라서 우선 새로운 변수를 선언하여 이전 상태값을 가져오도록 하자.
코드는 다음과 같다.
const squares = [...this.state.squares]
const {xIsNext} = {...this.state}
위의 코드를 콘솔을 찍어보면 아래와 같이 나온다.
console.log(squares) //Array(9) [ null, null, null, null, null, null, null, null, null ]
console.log(xIsNext) //true
console.log(squares===this.state.squares) //false
여기서 주목할 점은 바로 아래 false로 반환되는 부분이다.
<주의>
handleClick 안에 선언된 squares 변수는
state 변수 안에 있는 속성명과 변수명도 동일하고 실제 반환되는 값이 동일하다.
하지만 깊은 복사로 반환 값만을 가져왔으므로 실제로 둘은 독립된 메모리를 가지고 있다.
따라서, 이 둘을 놓고 비교하게 되면 true 값이 아닌 false가 반환되는 것이다.
자, 그럼 다시 본론으로 돌아와서
먼저 squares의 각 엘리먼트를 클릭 시, 다음 차례의 플레이어가 버튼에 표시되도록 하는 코드를 작성해보자
편의를 위해 삼항연산자를 사용한 코드를 작성해보자
squares[i] = xIsNext ? 'X' : 'O'
위 코드는 squares 함수의 i 번째 인덱스에 삼항연산자로 작성한 코드의 결과값을 대입하는 코드이다.
삼항연산자를 읽는 방법.
영어 문법 공부할 때 끊어 읽기 한 것처럼 정해진 특수문자('?'와 ':')에서 한번씩 끊으면 된다.
<참고>
삼항연산자를 읽는 방법.
영어 문법 공부할 때 끊어 읽기 한 것처럼 정해진 특수문자('?'와 ':')에서 한번씩 끊으면 된다.
?는 '~~하면'이라고 해석하면된다
그 다음 물음표 다음부터 :(콜론)까지는 ?가 성립할(true일 경우) 경우 반환될 부분이다.
:(콜론)은 '아니면~~'이라고 해석하면된다.
콜론 다음 부분은 ? 앞부분이 false 일 경우 반환될 부분이다.
따라서, 위의 코드를 해석해보면
- xIsNext 변수에 담긴 값이 true이면(?)
- 'O'를 반환하고
- 아니면(:) 'X'를 반환해라
이다.
조건에 따라 반환될 상태값을 변수에 담았으니,
담은 변수를 setStatea 메서드의 속성으로 작성해보자
this.setState({
squares,
xIsNext: !xIsNext
})
squares는 squares이다.
플레이어는 돌아가면서 게임을 플레이하게 되고, 게임을 되돌릴 수 없으므로 xIsNext는 한번 클릭 시 마다 계속해서 반대의 값이 되어야만 한다.
따라서, xIsNext의 속성값으로는 !(not)xIsNext를 주자.
여기까지 코드 작성 후 브라우저에서 버튼을 클릭해보면
다음과 같이 Board 컴포넌트의 state의 속성인 squares와 xIsNext 값이 변화되는 것을 확인할 수 있다.
5. 현재까지 작성한 코드(누적)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link type = "text/css" href = "./game.css" rel = "stylesheet">
<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id = "root"></div>
<script type ="text/babel">
class Square extends React.Component {
render() {
return(
<button
className="square"
onClick = {this.props.onClick}
>
{this.props.value}
</button>
)
}
}
class Board extends React.Component {
state = {
squares: Array(9).fill(null),
xIsNext: true
}
handleClick = i =>{
const squares = [...this.state.squares]
const {xIsNext} = {...this.state}
squares[i] = xIsNext ? 'X' : 'O'
this.setState({
squares,
xIsNext: !xIsNext
})
}
renderSquare = i => {
return(
<Square
value = {i}
onClick = { ()=> {this.handleClick(i)} }
/>
)
}
render() {
const { renderSquare } = this
return(
<div>
<div className="status"></div>
<div className="board-row">
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className="board-row">
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className="board-row">
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
</div>
)
}
}
class Game extends React.Component {
render() {
return(
<div>
<Board />
</div>
)
}
}
const root = document.querySelector('#root')
ReactDOM.render(<Game />,root)
</script>
</body>
</html>
'React > 4.TicTacToe 게임 만들기' 카테고리의 다른 글
틱택토6. 승자 결정하기 (0) | 2022.04.23 |
---|---|
틱택토5. 컴포넌트 작성(3) (0) | 2022.04.23 |
틱태토3. 컴포넌트 작성(1) (0) | 2022.04.21 |
틱택토2. TicTacToe 게임의 명세 (0) | 2022.04.21 |
틱택토1. 레이아웃 및 CSS 셋팅 (0) | 2022.04.21 |
댓글