JavaScript

[React js] Component, State, Props

Gray Park 2020. 10. 8. 10:50
728x90
반응형

하고 많은 웹 프레임워크 ( 혹은 라이브러리 ) 중에서 리액트를 배워야 하는 이유는 너무나 간단하다. 첫 번째 이유는 전 세계에서 사랑받는 대중적인 웹 프레임워크이고, 두 번째 이유는 그만큼 많은 질문과 답변이 커뮤니티에서 오갔기 때문에 쉽게 도움을 받을 수 있다는 것이고, 세 번째 이유는 js, JSX에 대한 이해와 오늘 설명할 component, state, props 정도만 알면 바로 사용해볼 수 있기 때문이다.

 

JSX ( JavaScript Xml )

형태가 html과 유사한 문서 작성용 스크립트 언어이다. Babel ( 바벨 ) 이라는 컴파일러 ( 온라인에서 확인 가능 ) 를 통해 자바스크립트 언어로 변경된다. JSX에서는 특별하게 중괄호{} 를 통해 JavaScript 코드를 삽입할 수 있다.

위의 코드로 작성되는 html의 형태는 아래와 같다.

THIS IS JSX

↑이 부분을 우클릭하여 요소 검사를 해보자

 

 

Component ( 컴포넌트 )

컴포넌트는 하나의 기능을 하는 함수의 단위이다. 일반적으로 코딩할 때에도 기능에 따라 함수나 파일을 나눈다. 그걸 좀 더 강력하게 분별해놓은 것이 컴포넌트이다. JSX에 적용 가능하고, 그 덕분에 직관적으로 이해할 수 있게 해주는 고마운 친구다.

 

예를 들어, 우리가 투표를 만들기 위해 <Button /> 이라는 컴포넌트를 만든다고 하자. 이 컴포넌트는 각각 번호가 붙어있어야 하고, 누를 때마다 해당 번호의 값이 올라가야 한다.

 

먼저, 바닐라 JS로 구현하면 대충 아래와 같은 느낌이다.

const voteHandler = (e) => {e.target.value;//something something}

const choice = 5;
for(let i = 1; i <= choice; i++) {
  const vote = document.createElement('button');
  vote.addEventListener('click', voteHandler);
  vote.textContent = `${i}`;
}

그리고 리액트와 JSX로 구현하면 대충 아래와 같은 느낌이다.

const Func = () => {
  const choice = 5;
  const buttonList = new Array(5);
  for ( let i = 1; i <= choice; i++ ) { buttonList[i] = i }
  
  return (
    <>
      { buttonList.map( (choice) => (
          <Button choice={choice} onClick={voteHandler} key={choice} />
        )
      }
    </>
  )
}

조금 더 직관적으로 보기 위해 하드코딩으로 아래와 같이 바꿔보았다.

const Func = () => {
  return (
    <>
      <Button onClick={voteHandler} key=1 >1</Button>
      <Button onClick={voteHandler} key=2 >2</Button>
      <Button onClick={voteHandler} key=3 >3</Button>
      <Button onClick={voteHandler} key=4 >4</Button>
      <Button onClick={voteHandler} key=5 >5</Button>
    </>
  )
}

vanilla js 로도 충분히 가능하지만 직관적이지 않아서 나중에 다시 코드를 열람할 때에 고통받는다. 하지만 리액트는 컴포넌트 단위로 이해하면 되므로 언제 보더라도 상대적으로 쉽고 빠르게 이해할 수 있다.

 

위의 예시에서 <Button />은 하나의 컴포넌트인데, 일반적으로 다른 파일 ( Button.js )에서 코드를 작성한다. 이렇게 작성해두면 언제 어디서든 재사용할 수 있다는 것이 엄청난 강점이다.

 

JSX와 컴포넌트를 이용하여 static 웹 페이지를 만들 수 있을 것 같다.

하지만, 우리가 DOM을 사용하는 이유는 강력한 업데이트 기능 때문이다.

그리고 그 Update는 리액트가 vanilla js 보다 강력하다.

 

컴포넌트는 클래스 컴포넌트와 함수 컴포넌트로 나뉜다. 리액트 훅스 ( React hooks ) 이전에는 아래에서 설명할 상태 관리가 클래스 컴포넌트에서만 가능했다. 다음 기회에 작성할 hooks 로 추가 포스팅을 하기로 하고, 오늘은 상태 그 자체에만 초점을 맞추겠다.

 

State ( 상태 )

리액트에서는 기본적으로 모든 상태를 기억한다. 처음 리액트를 배울 적에 '이게 가능한가?', '엄청 무거워지지 않을까?' 라는 생각이 들었다. 그도 그럴 것이 함수를 작성하는 데, 그 함수가 기본적으로 memoization 한다면 이 함수에서 사용하는 메모리 공간이 늘어날 수밖에 없기 때문이다. 그러나 이 생각은 지극히 프로그램 하나에 대한 사고이고, 우리는 웹에서 사용하는 용도이므로 사용자가 빠르게 업데이트된 상태를 볼 수 있게 해야 한다. 그러기 위해서는 memoization은 선택이 아닌 필수이다. ( 변수에 값을 저장해뒀다가 불러오는 것이 매 번 함수를 호출하는 것보다 당연히 빠르다 )

 

리액트의 상태 관리를 위해서는 리액트의 생태계, 혹은 LifeCycle ( 라이프 사이클 ) 을 알아야 한다.

리액트가 실행될 때 처음에 거치는 단계가 Mounting ( 마운팅 ) 이다.

클래스 컴포넌트의 constructor를 읽고, render 함수를 실행하여 JSX를 Babel을 통해 컴파일한다. 그다음 componentDidMount 상태가 된다.

 

이후 New props, setState(), forceUpdate() 등으로 상태가 업데이트되면 render함수를 재호 출하고, 이후 componentDidUpdate 상태가 된다.

 

마지막으로 Unmounting을 하게 되면 componentWillUnmount 상태가 된다.

위의 다이어그램으로 간략하게나마 흐름을 파악할 수 있어야 한다. 지금 배우는 입장에서 솔직히 쉽지 않다. 그러니까 한 번 해보자.

import React from 'react'

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 }
  }
  
  increaseCount = () => {
    this.setState((prevState) => {
      prevState.count++;
    });
  }
  
  decreaseCount = () => {
    this.setState({ count: count-1 });
  }
  
  render() {
    return (
      <div>
        <span>{this.state}</span>
        <button onClick={decreaseCount}>-1</button>
        <button onClick={increaseCount}>+1</button>
      </div>
    );
  }
}

exprot default App;

constructor에 this.state를 통해 count를 0으로 초기화하였다. 이후 버튼 클릭 이벤트를 통해 increaseCount 혹은 decreaseCount 함수가 호출되면 setState를 통해 state가 업데이트된다.

위의 다이어그램으로 돌아가면, setState가 호출된 다음에는 다시 render가 실행되므로 업데이트된 this.state를 화면에 출력하게 된다.

 

만약 setState가 아닌 this.state++ 와 같은 방식으로 상태를 업데이트하면 어떻게 될까?

 

기능 자체는 동작한다. 하지만 setState를 통해 업데이트되지 않기 때문에 화면의 상태는 변화하지 않는다.

만약 this.state.count = 3 과 같이 특정 값을 입력한 다음 increaseCount 를 실행하게 되면 이전의 상태 ( count = 3 )를 기준으로 함수가 실행되어 출력 값이 4가 된다.

 

따라서 setState를 사용하지 않고 화면이 업데이트되게 해 주려면 state를 업데이트함과 동시에 렌더 된 페이지 중 업데이트된 기존 부분을 전부 날리고 업데이트된 부분을 다시 렌더 하는 방식을 취해야 한다. 그리고 이런 과정을 함수로 작성해둔 게 setState 이다.

( 이만하면 setState에 대해 이해하고 사용하는 게 더 효율적이라는 생각을 지울 수 없다 )

 

참고로 이런 과정을 가능하게 하는 것은 React에서 사용하는 Virtual DOM에 관한 개념이다. 이 개념은 조금의 역사와 더불어 다른 포스트에서 만나볼 수 있게 하겠다.

 

Props ( Properties )

궁금해하는 사람이 있다면 상세히 다뤄보겠다. 프로퍼티는 재산, 성질 등의 사전적 의미와 속성, 메서드 등의 프로그래밍에서 사용하는 뜻에서 조차 다양한 의미를 가진다. 리액트에서 사용하는 프로퍼티는 간단하게 "넘겨받는 속성값" 이라고 이해하자.

 

위의 예시를 다시 보자. Func 컴포넌트에서 Button 컴포넌트로 choice, onClick, key 를 전달하고 있다. 이 중 key에 관해서는 잠시 잊어두고 choice, onClick 이 Button 컴포넌트로 전달되었다는 사실만 기억하자.

import React, { useState } from 'react';

const Func = () => {
  const choice = 5;
  const buttonList = new Array(5);
  for ( let i = 1; i <= choice; i++ ) { buttonList[i] = i }
  
  const [ choices, setChoices ] = useState(new Array(5).fill(0));
  
  const voteHandler = (e) => {
    const val = e.target.value;
    setChoices((prevState)=> {prevState[val]++;})
  }
  
  return (
    <>
      <div>{choices}</div>
      { buttonList.map( (choice) => (
          <Button choice={choice} onClick={voteHandler} key={choice} />
        )
      }
    </>
  )
}

Button 컴포넌트에서는 전달받은 값을 사용해야 한다. 이때 Button 컴포넌트에서 전달받은 값을 출력해보면 다음과 같다.

const Button = (props) => {
  console.log(props);
  return ;
}

/*
{
  choice: 1,
  onClick: voteHandler
}
*/

이처럼 JSX의 컴포넌트 태그 ( 여기서는 <Button /> ) 에 넣어준 값들이 객체화되어서 해당 컴포넌트로 전달된다.

그리고 그 값은 당연히 이용할 수 있다. 하지만, 값의 수정은 상위 컴포넌트에 영향을 주지 못한다.

 

예를 들어, props.choice 의 값을 10으로 바꾸면 당연히 최종 산물인 버튼의 값은 10으로 바뀐다. 하지만 안타깝게도 상위 컴포넌트인 Func는 1~5까지의 choice value 만 다루기 때문에 에러를 뿜을 것이다. ( 그럴 거면 컴포넌트 왜 쓰냐ㅏㅏ!ㅏ!ㅏ!!!!! )

 

그래서 하위 컴포넌트는 전달받은 함수를 통해 상위 컴포넌트의 state에 관여할 수 있다. Button을 마저 작성해보자.

import React from 'react';

const Button = (props) => {
  return (
    <>
      <button onClick={props.onClick}>{props.choice}</button>
    </>
  );
}

export default Button;

버튼을 클릭하면 props.onClick 의 voteHandler 를 찾아 상위 컴포넌트로 접근한다. 상위 컴포넌트의 voteHandler 는 setState ( 여기서는 useState 라는 hook 을 사용 ) 를 통해 클릭이 이루어진 버튼의 choice 에 해당하는 값에 +1 해주고 그 결과를 렌더 한다.

 

여기까지 리액트의 기본 개념이다. 처음에 엄청 헷갈렸는데 대충 강의 10번 듣고 10번 클로닝 하고 혼자서 10번 만들어보면 감이 잡힌다. 오늘 포스팅한 아래의 개념은 반드시 숙지하자.

  •  JSX
  •  Component
  •  LifeCycle
  •  State, setState & props
728x90
반응형