React.JS 자습서를 타입스크립트로.. (3)

1. State를 Square 각각에서 Board로 올리기.

(1) Board의 statesquares 배열 추가하기.

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
    };
  }
}

먼저 이러한 형태로 만들라고 하고 있다.

Board 컴포넌트의 state에 모든 Square의 상태를 저장할 것을 요구한다.

 

이전에 했던 대로, Board컴포넌트의 State에 들어갈 값을 미리 인터페이스로 정의하는 과정이 필요하다.

type BoardStateElement = string | null;
type BoardState = {
    squares: Array<BoardStateElement>;
};

위와 같이 SquareState 타입의 Array가 들어갈 수 있게 인터페이스 타입을 정의하였다. 자습서의 예제에 따르면 null도 들어갈 수 있으므로, null도 들어갈 수 있는 타입으로 정의해준다.

 

마찬가지로, Board 또한 생성자를 넣어준다. 

class Board extends React.Component<any, BoardState> {
    constructor(props: BoardState) {
        super(props);
        this.state = {
            squares: Array<BoardStateElement>(9).fill(null)
        }
        this.state.squares[0] = 'X';
        this.state.squares[1] = 'O';
        this.state.squares[5] = '?';
    }
}

정상적으로 동작하는 것을 확인하기 위해, 0, 1, 5번 인덱스에 각각 'X', 'O', '?' 값을 대입해둔다.

 

※ Board의 컴포넌트의 제네릭의 첫번째 인자로 any를 지정했다. Board에는 별도로 props를 받는 부분이 없으므로, 생략하기 위해서 그렇게 하였다. 대신 State는 BoardState 타입을 사용하기 때문에 두 번째 제네릭 인자로 BoardState가 오는 것은 필수적이다.

 

(2) Square에서 필요 없어진 부분 삭제 및 Props 정리

더 이상 Square에서 state를 관리할 필요가 없다. 따라서 state를 설정하는 생성자를 모두 삭제해도 상관이 없다.

또한, 클릭 시 state를 변경하는 코드도 필요가 없어졌다. 우리는 이제 단순히 상위 컴포넌트인 Board에서 props만 받아오면 된다.

 

type SquareProps = {
    value: BoardStateElement;
};

class Square extends React.Component<SquareProps> {
    render() {
        return (
            <button className="square">
            { this.props.value }
            </button>
        );
    }
}

필요 없는 부분을 모두 쳐내고, 위와 같이 가장 최소한의 Square 코드만이 남았다.

 

예상대로 잘 출력됨을 확인할 수 있다.

 

(3) Square의 value에 'O', 'X', null 만 허용하기

타입스크립트의 타입 정의를 활용한다.

지금은 type BoardStateElement = string | null; 형태로 되어 있는 부분을 수정한다.

방법은, string을 'O', 'X' 만 받을 수 있게 고친다.

다음과 같이 된다.

type BoardStateElement = 'O' | 'X' | null;

 

수정 직후, 아까 Board의 생성자에서 squares에 값을 대입했던 곳에서 오류가 나온다.

ERROR in src/index.tsx:32:9
TS2322: Type '"?"' is not assignable to type 'BoardStateElement'.
    30 |         this.state.squares[0] = 'X';
    31 |         this.state.squares[1] = 'O';
  > 32 |         this.state.squares[5] = '?';
       |         ^^^^^^^^^^^^^^^^^^^^^
    33 |     }
    34 |
    35 |     renderSquare(i: number) {

'?'를 대입할 수 없다는 코드이다. 예상한대로 동작하고 있다!

 

2. 각 사각형에 클릭 이벤트 추가하기

Board의 renderSquare함수를 아래와 같이 수정.

  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => this.handleClick(i)}
      />
    );
  }

Square 컴포넌트를 아래와 같이 수정.

class Square extends React.Component {
  render() {
    return (
      <button
        className="square"
        onClick={() => this.props.onClick()}
      >
        {this.props.value}
      </button>
    );
  }
}

 

(1) 클릭 이벤트 만들기

먼저, Board 컴포넌트에 클릭을 처리할 handleClick 함수를 만든다.

handleClick(i: number) {
    const states = this.state;
    states.squares[i] = 'X';
    this.setState(states);
}

클릭 시 해당 위치의 state가 'X'로 변경되는 간단한 함수이다.

 

이후, 이 handleClick 함수를 Square에 props를 통해 전달해주는 부분을 수정한다. Board에서 넘기는 부분은 문제가 없으나, SquareProps에 onClick 속성이 정의되지 않았으므로 오류가 생긴다.

type SquareProps = {
    value: BoardStateElement;
    onClick: () => void;
};

아래와 같이, void형 함수임을 나타내는 타입과 함께 onClick 속성을 추가로 정의한다. 

나머지는 자습서에 나온 것 이외에는 수정할 부분이 별도로 없는 것 같다.

 

실행 결과, 예상한 대로 정상적으로 잘 된다.

 

3. 오매 불변성...

handleClick 함수가 없어서, 임의로 만들었다. 그런데 변수 대입 시 당연히 복사가 일어날 줄 알았는데, 아니었나보다...

handleClick(i) {
  const squares = this.state.squares.slice();
  squares[i] = 'X';
  this.setState({squares: squares});
}

자습서에는 이렇게 slice() 함수를 사용해서 배열을 복제할 것을 추천하고 있다.

여러가지 장점이 있는데, 리액트의 순수 컴포넌트를 만드는 것에 활용이 된다는 것을 눈여겨 볼 만한 것 같다.

이 부분은 나중에 더 찾아봐야겠다.

 

아무튼 따라서, 내 handleClick 함수도 변경되었다.

handleClick(i: number) {
    const squares = this.state.squares.slice();
    squares[i] = 'X';
    this.setState({squares: squares});
}

 

4. 함수 컴포넌트

함수 컴포넌트는 단순하게 render만 갖는 컴포넌트 클래스를, 단순화 시킬 수 있는 방법.

자습서 코드에서 타입 형태만 인자(props: SquareProps)에 추가하는 것으로 더 이상 수정할 것이 없다.

function Square(props: SquareProps) {
    return (
        <button
            className="square"
            onClick={() => props.onClick()}
        >
            { props.value }
        </button>
    );
}

 

 

 

 

 

 

 

댓글

Designed by JB FACTORY