[React JS] 리액트를 다루는 기술 - 컴포넌트

 

1. 클래스형 컴포넌트

컴포넌트를 선언하는 방식은 두 가지이다.

  • 함수 컴포넌트
  • 클래스형 컴포넌트
import { Component } from "react";

class App extends Component {
  render(){
    const name = "react";
    return <div className = "react"> {name} </div>;
  }
}

export default App;

클래스형 컴포넌트는 위와 같은 형식이다.

 

▶ 클래스형 컴포넌트는

  • state 기능 및 라이프 사이클 기능을 사용할 수 있다.
  • 임의 메서드를 정의할 수 있다.
  • render() 함수가 꼭 있어야 하며, 그 안에서 보여주어야 할 JSX를 반환해야 한다.

 

▶ 함수형 컴포넌트의 장점

  • 클래스형 컴포넌트보다 선언하기 편하다.
  • 메모리 자원을 클래스형 컴포넌트보다 덜 사용한다.
  • 프로젝트를 완성하여 빌드한 후 배포할 때, 파일 크기가 더 작다.  => 함수형과 클래스형은 성능과 파일 크기에서 사실상 큰 차이는 없다.

 

▶ 함수형 컴포넌트의 단점

  • state와 라이프사이클 API의 사용이 불가능 => 리액트 v16.8이후 Hooks 기능이 도입되면서 해결

 

※ 리액트 공식 메뉴얼에서 컴포넌트를 새로 작성할 때, 함수 컴포넌트와 Hooks를 사용하도록 권장.

 

 

2. 첫 컴포넌트 생성

 

< src 디렉터리에 MyComponent.js 파일 생성 >

컴포넌트 코드를 선언할 파일을 생성한다.

 

 

< 코드 작성하기 >

MyComponent.js 파일에 새 컴포넌트의 코드를 작성한다.

ES6 버전에서 사용하는 화살표 함수를 사용할 수 있다.

function 키워드 대신 ( ) => { }를 사용해 함수를 생성할 수 있다.

const MyComponent = () => {
  return <div>new component</div>;
};

export default MyComponent;

 

< 모듈 내보내기(export) 및 불러오기(import) >

 

<< 모듈 내보내기(export) >>

export default MyComponent;

다른 파일에서 이 파일을 import할 때, 선언한 MyComponent 클래스를 불러오도록 하기 위해 export를 사용해 내보내야 한다.

 

 

<< 모듈 불러오기(import) >>

import MyComponent from "./MyComponent";

const App = () => {
  return <MyComponent />;
};

export default App;

import 구문을 사용해 앞서 만든 MyComponent 컴포넌트를 불러온다.

 

 

 

3. props

props는 컴포넌트 속성을 설정할 때 사용하는 요소로, properties의 줄임말이다.

props 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트에서 설정할 수 있다.

앞선 예제로 보면 App 컴포넌트가 부모 컴포넌트인 것이다.

 

< JSX 내부에서 props 렌더링 >

props 값은 컴포넌트 함수의 파라미터로 받아와 사용할 수 있다.

props를 렌더링할 때는 JSX 내부에서 { } 기호로 감싸주면 된다.

const MyComponent = (props) => {
  return <div>My name is {props.name}</div>;
};

export default MyComponent;

위 예시는 name이라는 props를 렌더링한다.

 

 

< 컴포넌트를 사용할 때 props 값 지정하기 >

import MyComponent from "./MyComponent";

const App = () => {
  return <MyComponent name="React" />;
};

export default App;

 

 

< props 기본 값 설정: defaultProps >

만약 앞선 예제에서 name을 지우면 My name is 만 보이고 아무것도 보이지 않을 것이다.

props를 따로 지정하지 않았을 때, defaultProps를 사용해 기본 값을 설정할 수 있다.

const MyComponent = (props) => {
  return <div>My name is {props.name}</div>;
};

MyComponent.defaultProps = {
  name: "기본 이름",
};

export default MyComponent;

 

 

< 태그 사이의 내용을 보여주는 children >

children은 컴포넌트 태그 사이의 내용을 보여주는 props이다.

const MyComponent = (props) => {
  return (
    <div>
      My name is {props.name}
      <br />
      children 값은 {props.children}
    </div>
  );
};

MyComponent.defaultProps = {
  name: "기본 이름",
};

export default MyComponent;

MyComponent.js 파일을 위와 같이 작성한다.

 

import MyComponent from "./MyComponent";

const App = () => {
  return <MyComponent>React</MyComponent>;
};

export default App;

App.js를 위와 같이 작성한다.

 

=> 그 결과 컴포넌트 태그 사이의 값은 React 이므로 children 값은 React가 될 것이다. 따라서 props.children에는 react가, props.name은 '기본 이름' 값이 들어갈 것이다.

 

 

< 비구조화 할당 문법을 통해 props 내부 값 추출하기 >

이전 예제들을 보면, props 값을 조회할 때마다 props.name, props.children 처럼 props.라는 키워드를 앞에 붙여서 사용했다. 이 작업을 더 간편하게 하기 위해 ES6의 비구조화 할당 문법을 사용해 내부 값을 바로 추출할 수 있다.

 

▶ 비구조화 할당

: 객체에서 값을 추출하는 문법

: 구조 분해 문법이라고도 불리며, 함수의 파라미터 부분에서도 사용할 수 있다. 즉, 함수의 파라미터가 객체라면 그 값을 바로 비구조화해서 사용하는 것이다.

 

const MyComponent = (props) => {
  const { name, children } = props;
  return (
    <div>
      My name is {name}
      <br />
      children 값은 {children}
    </div>
  );
};

MyComponent.defaultProps = {
  name: "기본 이름",
};

export default MyComponent;

위와 같이 코드를 작성하면 name과 children 값을 더 짧은 코드로 사용할 수 있다.

 

const MyComponent = ({ name, children }) => {
  return (
    <div>
      My name is {name}
      <br />
      children 값은 {children}
    </div>
  );
};

MyComponent.defaultProps = {
  name: "기본 이름",
};

export default MyComponent;

또한 props위치에 {name, children}등 객체를 주어 더 간편하게 코드를 위와 같이 작성할 수 있다.

 

 

 

< propTypes를 통한 props 검증 >

propTypes: 컴포넌트의 필수 props를 지정하거나 props의 타입을 지정할 때 사용

컴포넌트의 propTypes를 지정하는 방법은 defaultProps 설정하는 것과 비슷하다.

 

1. 코드 상단에 import 구문을 사용해 prop-types를 불러와야 한다.

import PropTypes from "prop-types";

 

2. propTypes를 불러오면 코드 하단에 propTypes를 아래와 같이 작성해준다.

import PropTypes from "prop-types";

const MyComponent = ({ name, children }) => {
  return (
    <div>
      My name is {name}
      <br />
      children 값은 {children}
    </div>
  );
};

MyComponent.defaultProps = {
  name: "기본 이름",
};

MyComponent.propTypes = {
  name: PropTypes.string,
};

export default MyComponent;

현재 name은 string 타입으로 전달될 것을 의미한다.

만약 컴포넌트에서 설정한 props 가 propTypes과 일치하지 않으면 콘솔에 경고 메시지를 출력하게 된다.

 

 

<< isRequired를 사용해 필수 propTypes 지정 >>

isRequired를 propTypes를 지정할 때 뒤에 붙여주면, propTypes를 지정하지 않았을 때 경고 메시지를 띄워준다.

MyComponent.propTypes = {
  name: PropTypes.string,
  favoriteNum: PropTypes.number.isRequired,
};

 

 

<< 더 많은 PropTypes 종류 >>

array 배열
arrayOf(다른 PropType) 특정 PropType으로 이루어진 배열.
arrayOf(PropTypes.number)이면 숫자로 이루어진 배열
bool true 나 false 값
func 함수
number 숫자
object 객체
string 문자열
instanceOf(클래스) 특정 클래스의 인스턴스
oneOf(['dog', 'cat']) 주어진 배열 요소 중 값 하나
shape({name: PropTypes.string, num: PropTypes.number }) 주어진 스키마를 가진 객체

 

 

 

< 클래스형 컴포넌트에서 props 사용하기 >

클래스형 컴포넌트에서 props를 사용할 때는 render() 함수에서 this.props를 조회한다.

import { Component } from "react";
import PropTypes from "prop-types";

class MyComponent extends Component {
  render() {
    const { name, favoriteNum, children } = this.props;
    return (
      <div>
        My name is {name}
        <br />
        children 값은 {children}
        <br />
        좋아하는 숫자는 {favoriteNum}
      </div>
    );
  }
}

MyComponent.defaultProps = {
  name: "기본이름",
};

MyComponent.propTypes = {
  name: PropTypes.string,
  favoriteNum: PropTypes.number.isRequired,
};

export default MyComponent;

위 방법 이외에도 defaultProps와 propTypes를 class 내부에서 지정할 수 있다.

 

import { Component } from "react";
import PropTypes from "prop-types";

class MyComponent extends Component {
  static defaultProps = {
    name: "기본 이름",
  };

  static propTypes = {
    name: PropTypes.string,
    favoriteNum: PropTypes.number.isRequired,
  };
  render() {
    const { name, favoriteNum, children } = this.props;
    return (
      <div>
        My name is {name}
        <br />
        children 값은 {children}
        <br />
        좋아하는 숫자는 {favoriteNum}
      </div>
    );
  }
}

export default MyComponent;

 

 

 

4. state

props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값이었다.

컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있었고, props를 변경하려면 부모 컴포넌트에서 바꿔줘야 한다.

 

state: 리액트의 컴포넌트 내부에서 바뀔 수 있는 값을 의미

리액트에는 두가지 종류의 state가 있다.

  1. 클래스형 컴포넌트가 지니고 있는 state
  2. 함수 컴포넌트에서 useState라는 함수를 통해 사용하는 state

 

 

< 클래스형 컴포넌트의 state >

 

1. constructor 메서드를 작성하여 컴포넌트에 state를 설정

class Counter extends Component{
    constructor(props){
        super(props);
        this.state = {
            number: 0,
        }
    }
}

constructor 메서드는 생성자 메서드이다. 클래스형 컴포넌트에서 constructor를 작성할 때는 반드시 super(props)를 호출해야 한다. 이 함수가 호출되면 현재 클래스현 컴포넌트가 상속받고 있는 리액트의 Component 클래스가 지닌 생성자 함수를 호출해준다.

 

constructor 메서드 안에서 super(props)를 호출한 후, this.state 값에 초깃값을 설정해야 한다. 이때 컴포넌트의 state는 객체 형식이어야 한다.

 

 

2. render() 함수 내에서 state를 조회하고 값을 변경

render() {
    const { number } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <button
          onClick={() => {
            this.setState({ number: number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }

this.state : 현재 state를 조회할 때 사용

this.setState : state 값을 바꿀 수 있게 해준다.

 

 

3. 코드 작성을 완료한 후, 해당 컴포넌트를 App에서 불러와 렌더링

 

 

<< state 객체 안에 여러 값이 있을 때 >>

import { Component } from "react";

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0,
      fixedNum: 0,
    };
  }
  render() {
    const { number, fixedNum } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <h2>고정된 값: {fixedNum}</h2>
        <button
          onClick={() => {
            this.setState({ number: number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

현재 state 안에는 number, fixedNum 두 개의 값이 있다.

위 코드에서 this.setState는 number의 값만 변경해주고 있다.

이처럼 this.setState() 함수는 인자로 전달된 객체 안에 들어있는 값만 바꾸어 준다.

 

 

<< state를 constructor에서 꺼내기 >>

state의 초깃값을 지정하기 위해 constructor 메서드를 사용하지 않고 지정해줄 수 있다.

import { Component } from "react";

class Counter extends Component {
  state = { number: 0, fixedNum: 0 };
  render() {
    const { number, fixedNum } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <h2>고정된 값: {fixedNum}</h2>
        <button
          onClick={() => {
            this.setState({ number: number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

위 코드를 보면 constructor 메서드를 사용하지 않은 결과 코드를 더 간편하게 하여  state의 초깃값을 지정한 것을 볼 수 있다. constructor 메서드를 이용해 작성한 것 보다 위의 방식이 더 선호된다.

 

 

<< this.setState에 객체 대신 함수 인자 전달하기 >>

 

this.setState를 사용해 state 값을 업데이트하면 상태가 비동기적으로 업데이트된다.

만약 this.setState를 두 번 사용하여 값을 두 번 증가시키고자 한다면, 이는 제대로 실행되지 않는다. this.setState를 사용한다고 해서 state 값이 바로 바뀌지 않기 때문에 한 번만 더해질 것이다.

 

=> 이에 대한 해결책은 this.setState를 사용할 때 객체 대신 함수를 인자로 넣어주는 것이다.

this.setState((prevState, props) => {
  return{ ... }
  })
<button
          onClick={() => {
            this.setState((prevState) => {
              return {
                number: prevState.number + 1,
              };
            });
          }}
        >

 

 

<< this.setState가 끝난 후 특정 작업 실행하기 >>

setState를 사용하여 값을 업데이트한 후, 특정 작업을 하고 싶을 때 setState의 두 번째 파라미터로 콜백 함수를 등록하여 작업을 처리할 수 있다.

<button
          onClick={() => {
            this.setState(
              {
                number: number + 1,
              },
              () => {
                console.log("state 호출");
                console.log(this.state);
              }
            );
          }}
        >

 

 

< 함수 컴포넌트에서 useState 사용하기 >

 

<< 배열 비구조화 할당 >>

: 배열 안에 들어있는 값을 쉽게 추출할 수 있도록 해주는 문법

const array = [1, 2];
const one = array[0];
const two = array[1];

배열 비구조화 할당을 사용하지 않은 예시

 

const array = [1, 2];
const [one, two] = array;

배열 비구조화 할당을 사용한 예시

 

 

<< useState 사용하기 >>

useState() 함수의 인자에는 상태의 초깃값을 넣어줘야 한다.

useState에서는 반드시 객체가 아니어도 되고 값의 형태는 자유이다.

 

함수를 호출하면 배열이 반환된다.

배열의 첫번째 원소는 현재 상태, 두 번째 원소는 상태를 바꾸어 주는 함수이다.

아래 예시를 보면 현재 상태를 나타내는 원소는 message, 상태를 바꾸어 주는 함수는 setMessage이다.

이때 상태를 바꾸어 주는 함수를 Setter 함수라고 부른다.

또한 배열 비구조화 할당을 통해 이름을 자유롭게 정할 수 있다.

 

import { useState } from "react";

const Say = () => {
  const [message, setMessage] = useState("");
  const onClickEnter = () => setMessage("Hello");
  const onClickLeave = () => setMessage("Bye");

  return (
    <div>
      <button onClick={onClickEnter}>입장</button>
      <button onClick={onClickLeave}>퇴장</button>
      <h1>{message}</h1>
    </div>
  );
};

export default Say;

 

 

<< 한 컴포넌트에서 useState 여러 번 사용하기 >>

useState는 한 컴포넌트 안에서 여러 번 사용해도 상관 없다.

 

 

5. state를 사용할 때 주의 사항

state 값을 바꾸어야 할 때는 setState 혹은 useState를 통해 전달받은 Setter 함수를 사용해야 한다.

 

 

 

반응형