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

1. props 전달

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

자습서는 코드를 이렇게 바꾸라고 안내한다. 당연하지만 제대로 될 리가 없다. value를 찾을 수 없다고 아우성을 친다.

 

React에서 Props를 정의하는 방법의 안내를 참조하여 보면, Props의 타입을 정의해줘야 한다고 나와있다.

interface SquareProps {
    value: number;
}

 

 

리액트 컴포넌트 타입스크립트로 작성하기 를 참조하면, type 키워드를 사용하는 방법도 있다고 한다.

type SquareProps = {
    value: number;
};

 

위와 같이 Props를 정의하고, 컴포넌트에 제네릭으로 해당 타입을 바인딩한다.

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

최종적으로 위와 같은 형태가 된다.

 

 

2. 상호작용 하는 컴포넌트 (1)

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

자습서는 위와 같이 고치라고 안내한다. 그대로 넣어도 돌아가게 생기긴 했으나, onClick이 길어지는거 보기 싫다.

함수를 분리해 넣어야 겠다.

 

자습서에서는 this의 혼란스러운 동작을 막기 위해 화살표 함수를 사용한다고 나와있다. 이걸 제대로 안읽고 내려가면 삽질한다. (나처럼)

render() {
    return (
        <button className="square" onClick={this.onClick}>
        { this.props.value }
        </button>
    );
}
    
onClick() {
    console.log('click');
}

위와 같이 멤버 함수를 정의하고, 해당 멤버 함수를 onClick 이벤트에 바인딩하였다.

 

 

3. 상호작용 하는 컴포넌트 (2)

constructor(props) {
  super(props);
  this.state = {
    value: null,
  };
}

위와 같은 생성자를 넣으라고 자습서가 안내한다. 여기에 생성자의 인자인 props에 타입이 들어있지 않다. SquareProps 타입을 넣는다.

 

constructor(props: SquareProps) {
    super(props);
    this.state = {
        value: null,
    };
}

위와 같이 되었다.

 

 

render() {
  return (
    <button
      className="square"
      onClick={() => this.setState({value: 'X'})}
    >
      {this.state.value}
    </button>
  );
}

위와 같이, this.setState 함수를 호출해 state값을 X로 바꾸라고 안내한다. 

따라서 함수로 따로 분리했던 내 방식대로, 함수를 분리해서 넣었다.

 

 

render() {
    return (
        <button className="square" onClick={this.onClick}>
        { this.state.value }
        </button>
    );
}

onClick() {
    this.setState({value: 'X'});
}

그렇게 해서, 위와 같이 되었다. 그런데, this.state.value 에서 에러가 난다.

this.state.'value' 에서 빨간 밑줄과 함께 컴파일 에러가 나오는데, 에러 내용은 다음과 같다.

Property 'value' does not exist on type 'Readonly<{}>'.ts(2339)

 

타입에서 해당 value 속성을 가져올 수 없다는 것이다. 그리고, 이 역시 스택오버플로는 답을 알고 있었다.

Typescript로 React에서 State사용하기 질문에 답변이 잘 달려있었다.

결론은, State 역시도 Probs와 마찬가지로 인터페이스 타입을 정의하고 제네릭에 바인딩 할 필요가 있다는 것이었다.

 

type SquareState = {
    value: string;
}

class Square extends React.Component<SquareProps, SquareState> {
    constructor(props: SquareProps) {
        super(props);
        this.state = {
            value: null,
        };
        this.onClick = this.onClick.bind(this);
    }
    // ...
}

위와 같이 State인터페이스를 정의하고, 제네릭에 타입을 추가해준다. 그러나 이번에는 다른 에러가 나온다.

 

Type 'null' is not assignable to type 'string'.ts(2322)

'string' 타입에는 'null'을 대입할 수 없다는 것인데, 이것은 이전에 타입스크립트 튜토리얼 사이트를 훑어보면서 보았던 기억이 있다. null을 허용하려면 타입에 null을 사용할 수 있게 해주어야 한다는 것.

 

SquareState 타입 정의에 value를 null 허용 타입으로 바꾸어준다.

type SquareState = {
    value: string | null;
}

위와 같이 수정하고, 오류가 사라짐을 확인할 수 있다.

 

 

4. 함수 바인딩

....근데 안된다! ㅋㅋ!

 

Uncaught TypeError: Cannot read properties of undefined (reading 'setState')
index.tsx:27

setState를 읽으려고 하였으나, undefined의 속성들을 읽을 수 없다..? 이게 무슨 말이야 빙구야?

 

하지만 스택오버플로는 답을 알고 있었다. 이유인 즉슨, 함수가 바인딩되지 않았기에 생기는 문제라고 한다.

더욱 자세한 설명은 모던 자바스크립트 튜토리얼 사이트에서도 볼 수 있고, 모질라 자바스크립트 공식 문서에도 자세하게 설명되어 있다.

 

다른 언어와 다르게, 자바스크립트는 '클래스 와 함수' 의 관계가 아니라, '객체(클래스)의 객체(함수)'의 형태를 띈다. 다시 말해, 단순히 객체에 객체가 담겨있는 형태이다. 자바스크립트에는 배열은 물론, 함수나 클래스 조차도 모두 객체(Object)로 취급된다. 따라서, 내가 클래스의 함수를 전달하더라도, 그것은 함수라는 '객체'를 전달한 것이고, 어느 객체에 있던 객체(함수)인지는 신경쓰지 않는다. 라는 판단이 되는 것 같다. 

 

자바스크립트의 클래스는 ES6부터 상속과 다형성의 객체지향 개념을 어거지로 사용하기 위해서, 문법적 설탕을 추가하는 과정에서 단순히 객체를 문법적으로 확장한 것에 불과하다. 따라서 태생적으로 이러한 한계점을 가지는 것으로 보인다.

타입스크립트 역시, 실제 코드로써 실행되기 위해서는 결국 자바스크립트 코드가 되므로, 이러한 한계에서 벗어날 수 없었던 것 같다.

 

따라서 이를 해결하려면, 함수 객체 어딘가에 'this'를 강제로 할당해서 고정시켜야 하고, 이 것을 함수 바인딩이라고 부른다.

 

모든 함수에는 bind 함수가 제공된다. 또한 한 번 바인딩 된 형태는, 다시 바인딩 될 수 없다.

 

constructor(props: SquareProps) {
    super(props);
    this.state = {
        value: null,
    };
    this.onClick = this.onClick.bind(this);
}

따라서 위와 같이 생성자에서 함수 바인딩을 수행해주면, this.onClick에 this.onClick을 this로 바인딩 한 무언가가 들어가게 되고, 

button의 onClick이벤트에 해당 함수를 넣으면 this가 바인딩 된 상태로 정상적으로 호출이 될 것이다.

 

이제 잘됨 ㅋ

자습서대로 정상적으로 구현 되었다 :)

 

댓글

Designed by JB FACTORY