본문 바로가기

# Tech/React

[Typescript React] 컴포넌트 만들기


컴포넌트가 시작이자 끝입니다.


리액트에서 컴포넌트는 알파이자 오메가입니다.


사용자가 보게될 페이지에서 작은 부분들을 컴포넌트로 추출하고,

이러한 컴포넌트를 알맞게 조립해서 페이지가 완성되는 것이죠.


프로그래머가 할 일은 작은 컴포넌트를 클래스나 함수로 구현하고,

이 컴포넌트를 조합해서 더 큰 컴포넌트나 페이지를 만드는 것 뿐입니다.


로그인 페이지의 컴포넌트 구조




컴포넌트는 오직 두가지.   상태가 있느냐, 없느냐.


컴포넌트는 상태State의 유무에 따라 분류할 수 있습니다.

  • 상태가 있는 컴포넌트 State Component
  • 상태가 없는 컴포넌트 Stateless Component


어떤 데이터에 의해 보여줘야하는 구조가 바뀐다면 상태가 있는 것이고,

항상 똑같은 구조만 보여준다면 상태가 없는 것이죠.


가장 간단한 예시로 카운터를 들 수 있는데,

상태가 변경됨에 따라 보여주는 화면(렌더링)이 달라지게 됩니다.


카운트에 따라 렌더링 결과가 바뀐다.



사용자가 버튼을 누르면 카운트 수가 변경되고,

카운터의 상태가 변경되었다고 리액트에게 알려주면,

리액트는 변경되어야 할 부분을 계산한 뒤 업데이트하는 것이죠.


프로그래머가 상태 컴포넌트를 만들 때 해야할 일은,

사용자에게 데이터를 어떻게 보여줄지 기술하는 것 뿐입니다.





컴포넌트를 구현하는 방법 (1) - 함수


먼저 컴포넌트를 구현하는 방법에는 두 가지가 있습니다.

  • 리액트 컴포넌트를 반환하는 함수를 작성
  • 리액트 컴포넌트를 상속받는 클래스를 작성


함수로 작성하는 경우에는 내부 데이터를 가질 수 없으므로,

보통 상태없는Stateless 컴포넌트를 만들 때 사용합니다.

function HelloWorld(): JSX.Element {
    return <h1>Hello, World!</h1>;
}

ReactDOM.render(
    <HelloWorld />,
    document.getElementById("root")
);

Attention!

사용자 정의 컴포넌트의 이름은 반드시 대문자로 시작해야 합니다. (관례적으로 UpperCamelCase를 사용합니다.)

소문자로 시작하는 것은 div, p 같은 HTML 빌트인 컴포넌트라는 것을 의미하며,

사용자 정의 컴포넌트가 소문자로 시작하면 불안정한 동작이 발생할 수 있습니다.




물론, 전역변수나 클로져를 사용하면 상태를 흉내낼 수 있겠지만,

상태의 변경을 리액트에게 알려줄 수 있는 방법이 없기 때문에,

아래처럼 수동으로 다시 렌더링해야 합니다.

let userInput: string = "";
let yourName : string = "";
function Hello(): JSX.Element {
    return <h1>Hello, {yourName}!</h1>;
}

// 1초마다 인풋이 들어왔는지 확인하고,
// 인풋이 들어왔으면 상태를 변경한 뒤, 다시 렌더링 함수를 호출한다.
setInterval(() => {
    if (userInput !== "") {
        yourName  = userInput;
        userInput = "";
        ReactDOM.render(<Hello />, document.getElementById("root"));
    }
}, 1000);

Attention!

대부분의 리액트 어플리케이션은 최상위에서 ReactDOM.render( )를 단 한번만 호출하므로,

위의 코드는 잘못 설계된 어플리케이션입니다.



하지만, 그 순간만 렌더링 할것이라면 문제가되지 않습니다.

그 순간의 상태만 렌더링하고, 이후의 상태변경은 신경쓰지 않는것이죠.

문제가 되는것은 함수 컴포넌트가 지속적으로 변화하는 상태를 감지해야 하는 경우입니다.





컴포넌트를 구현하는 방법 (2) - 클래스


반면에 컴포넌트 클래스를 상속받는 경우에는 내부 데이터를 가질 수 있으므로,

상태있는State 컴포넌트를 만들 때 사용할 수 있습니다.


특히 타입스크립트는 제네릭을 사용할 수 있으므로, 

제네릭을 활용하면 안정성이 흘러 넘치는 프로그래밍을 할 수 있죠.


아래 코드를 살펴봅시다.

interface Props {
    givenName: string;
    famillyName?: string;
}

interface State {
    fullName: string;
    isExistFamillyName: boolean;
}

class Hello extends React.Component<Props, State> {
    static calcFullName(props: Props) {
        const g = props.givenName;
        const f = props.famillyName ? props.famillyName : "";
        return g + f;
    }

    constructor(props: Props) {
        super(props);
        // 상태에 대한 대입연산은 생성자에서만 허용된다.
        this.state = {
            fullName: Hello.calcFullName(props),
            isExistFamillyName: props.famillyName != undefined
        };
    }

    handleNameChange(props: Props) {
        // setState(obj: {}) 메소드로 상태를 변경해야만,
        // 리액트에게 상태변경을 알려줄 수 있다.
        this.setState({
            fullName: Hello.calcFullName(props),
            isExistFamillyName: props.famillyName != undefined
        });
    }

    // Override
    render() {
        return <h1>Hello, {this.state.fullName}</h1>;
    }
}

주의해야 할 점은 다음과 같습니다.


1.

render( )함수는 리액트에서 상속받은 메소드이며,

리액트는 이 메소드를 사용해서 컴포넌트를 렌더링합니다.


2.

Props는 컴포넌트를 만들기 위한 데이터의 집합으로 작동하고, 

State는 객체의 상태를 정의하는 데이터의 집합으로 작동합니다.

둘이 같다고 하더라도 <State>축약할 수 없습니다.


3.

this.state는 private 속성이며 State 타입이 아닙니다.

setState의 인자타입이 any: { }임에 주목해주세요.

setState는 인자로받은 객체를 현재 상태와 병합합니다.

즉, State에 정의되지 않은 속성이 있을 수 있고.

private이기 때문에 외부에서 이 객체의 상태를 볼 수 없습니다.



추가적으로 아래와 같이, 상태없는 컴포넌트처럼 사용할 수 있습니다.

class Hello extends React.Component {
    constructor() {
        super({});
    }

    render() {
        return <h1>Hello, World!</h1>;
    }
}





컴포넌트는 어떻게 구현해야 잘했다고 소문나나요?


위의 내용을 정리해보자면,

먼저 컴포넌트는 상태에 따라 두가지로 나뉘겠죠?

  • 상태가 있는 컴포넌트
  • 상태가 없는 컴포넌트


먼저 State Componenet는 클래스로 구현할 수 밖에 없습니다.

리액트에게 상태변경을 알려주는 setState 메소드가 클래스에 있기 때문이죠.

전역변수나 클로져를 이용하여 상태를 흉내내는 방식은 잘못된 설계로 이어집니다.



다만, Stateless Componenet는 두가지 방식으로 구현할 수 있겠네요.

  • 함수로 구현.
  • 클래스로 구현하고 상태가 없는것처럼 사용하기.



여기서는 출처의 내용을 요약만하고 마치겠습니다.

자세한 실험결과를 보고 싶다면, 옆의 링크를 눌러주세요. (출처)



먼저, 클래스 컴포넌트와 함수 컴포넌트의 렌더링 속도차이는 거의 없습니다.

약간의 차이는 있었지만, 그렇게 큰 차이는 아니라고 하네요.




다만, 클래스 컴포넌트는 컴파일 이후에 용량이 뻥튀기된다고 합니다.

함수 컴포넌트의 용량이 97 bytes로 늘어났다면, 클래스는 1200 bytes 가까이 늘어났네요.

상속받은 메소드들이 많은 탓입니다.



즉, 프로그램의 용량을 줄이기 위해서 함수 컴포넌트를 섞으라고 말합니다.

순간의 상태만 보여주는 컴포넌트나, 무상태 컴포넌트는 함수로 구현하는 것이 좋아보이네요.




'# Tech > React' 카테고리의 다른 글

[Typescript React] 이벤트 핸들링  (0) 2019.12.06
[Typescript React] 컴포넌트 생명주기  (0) 2019.12.06
[Typescript React] JSX 문법  (0) 2019.12.05
[Typescript React] 리액트 프리뷰  (0) 2019.12.05
TypeScript로 React 시작하기  (0) 2019.12.05