Study/React

3. Component(컴포넌트)

delay100 2022. 7. 10. 19:02
728x90
반응형
SMALL

안녕하세요! delay100입니다. 이번 포스팅에서는 Component에 대해 공부해봅시다.

대부분의 설명은 주석으로 달아놓았으니 코드에 대한 설명은 주석을 확인해주세요!

 리액트를 다루는 기술, 개정판 3장 내용을 다루고 있습니다.

프로젝트 실행 방법에 대한 설명은 여기에 있습니다.

이번 포스팅의 Github 링크

https://github.com/delay-100/study-react/tree/main/ch3/hello-react
 

GitHub - delay-100/study-react

Contribute to delay-100/study-react development by creating an account on GitHub.

github.com


1. Component 기본 

 

Component(컴포넌트)란?

- 데이터가 주어졌을 때 UI를 만들어줌
- 라이프사이클 API를 이용해 컴포넌트가 화면에서 나타날때 등 변화가 일어날 때 주어진 작업을 처리할 수 있음
- 임의 메서드를 만들어 특별한 기능을 붙여줄 수 있음

 

함수 컴포넌트 VS 클래스형 컴포넌트?

=> 리액트 공식 메뉴얼에서는 컴포넌트를 새로 작성 시, 함수 컴포넌트와 Hooks를 사용하도록 권장하고 있음


1. 함수 컴포넌트 특징

- 클래스형 컴포넌트보다 선언하기 편함
- 메모리 자원을 (클래스형 컴포넌트보다) 덜 사용함
- 프로젝트를 완성하여 빌드 시 결과물의 파일 크기가 (클래스형 컴포넌트보다) 더 작음
  => 사실, 클래스형 컴포넌트와 별 차이가 없으므로 이 특징은 중요하진X
- state와 라이프사이클 API의 사용이 불가능함
  => 리액트 v16.8 이후  Hooks라는 기능이 도입되면서 가능하게 됨

  • 함수 컴포넌트 예시 - src/App.js 中 함수형 컴포넌트
// 함수 컴포넌트
import './App.css';

class App() {
    const name = "리액트";
    return <div className="react">{name}</div>;
  }
}



2. 클래스형 컴포넌트 특징

- render 함수가 꼭 있어야 함
- render 함수 내에 보일 JSX를 반환해야 함
- state 기능 및 라이프 사이클 기능을 사용할 수 있음
- 임의 메서드를 정의할 수 있음

  • 클래스형 컴포넌트 예시 - src/App.js 中 클래스형 컴포넌트
// 클래스형 컴포넌트
import { Component } from "react";

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

export default App;

 

초반부에서는 클래스형 컴포넌트로 감을 익히고, 8장 이후에 함수 컴포넌트와 Hooks를 해보겠습니다.

책이 그렇게 하기 때문에 저도 한번 따라가 볼게요


2. Component 생성 

 

  • 컴포넌트 생성 예시(모듈 내보내기, export) - src/MyComponent.js 
const MyComponent = () => {
  return <div>나의 새롭고 멋진 컴포넌트</div>;
};

export default MyComponent; // 모듈 내보내기, 다른 파일에서 이 파일을 import할 때, 위에서 선언한 MyComponent 클래스를 불러오도록 설정함

* () => {} 는 화살표 함수인데, 이전 포스팅에서 다뤘습니다. (여기 클릭)

더보기
+ 화살표 함수 추가 설명

- 주로 함수(function)를 파라미터로 전달할 때 유용함
- 기존 function을 대체할 수 없는데, 용도가 다르기 때문임
=> 일반 함수(function)는 자신이 종속된 객체를 this로 가리키며, 화살표 함수(() => {})는 자신이 종속된 인스턴스를 가리킴

  • 컴포넌트 생성 예시(모듈 불러오기, import) - src/App.js
import MyComponent from "./MyComponent"; // 모듈 불러오기

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

export default App;

 

모듈 내보내기-불러오기 실행 결과

 


3. props

 

props란?

- properties를 줄인 표현
- 컴포넌트 속성을 설정할 때 사용하는 요소
- 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값
- 컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있음(props를 바꾸려면 부모 컴포넌트에서 바꿔야 함)
- props 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트(2. Component 생성에서는 App 컴포넌트가 부모 컴포넌트임)에서 설정 가능

 

3-1. props 기본

 

  • props로 Component 생성 예시 - src/App.js
// props 사용한 Component 생성
import MyComponent from "./MyComponent";

const App = () => {
  // return <MyComponent name="React" />; // src/MyComponent.js의 props.name 값
  return <MyComponent>리액트</MyComponent>; // src/MyComponent.js의 props.children 값
};

export default App;
  • props로 Component 생성 예시 - src/MyComponent.js
const MyComponent = (props) => {
  return (
    <div>
      안녕하세요. 제 이름은 {props.name}입니다. <br />
      children 값은 {props.children}
      입니다.
    </div>
  );
};

/* src/App.js에 MyComponent에 name이 없는 경우 props.name에 아무것도 출력되지 않는데,
이 때 기본값을 주고 싶은 경우 defaultProps를 사용함 */
MyComponent.defaultProps = {
  name: "기본 이름",
};

export default MyComponent;

 

props로 Component 생성 실행 결과

 


 

3-2. 비구조화 할당

 

위의 기본 props는 항상 props.name, props.children 처럼 꽤나 길게 적어야합니다. 이 작업을 보다 더 편하게 하기 위해 비구조화 할당을 이용합니다.

그럼, 비구조화 할당 문법을 통해 props 내부의 값을 추출해봅시다.

  • 비구조화 할당으로 props를 추출해 Component 생성 예시 - src/MyComponent.js
const MyComponent = ({ name, children }) => {
  return (
    <div>
      안녕하세요, 제 이름은 {name}입니다. <br />
      children 값은 {children}
      입니다.
    </div>
  );
};

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

export default MyComponent;
  • 비구조화 할당으로 props를 추출해 Component 생성 예시 - src/App.js
// // pros 사용한 Component 생성
import MyComponent from "./MyComponent";

const App = () => {
  // return <MyComponent name="React" />; // src/MyComponent.js의 props.name 값
  // return <MyComponent>리액트</MyComponent>; // src/MyComponent.js의 props.children 값
  return <MyComponent name="React">리액트</MyComponent>;
};

export default App;

비구조화 할당으로 Component 생성 실행 결과

결과를 보면  3-1. props 기본과 같다는 것을 확인할 수 있습니다.

 


 

3-3. PropTypes

 

컴포넌트의 props의 타입을 지정, 필수 props 지정할 때 propTypes를 사용합니다.

  • 비구조화 할당 & PropTypes 이용해 Component 생성 예시 - src/MyComponent.js
import PropTypes from "prop-types";

const MyComponent = ({ name, favoriteNumber, children }) => {
  return (
    <div>
      안녕하세요, 제 이름은 {name}입니다. <br />
      children 값은 {children}
      입니다.
      <br />
      제가 좋아하는 숫자는 {favoriteNumber}입니다.
    </div>
  );
};

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

MyComponent.propTypes = {
  name: PropTypes.string, // props의 타입 지정, 무조건 name 값이 문자열(string) 형태로 전달되어야 함
  favoriteNumber: PropTypes.number.isRequired, // 필수 props 지정
};

export default MyComponent;
  • 비구조화 할당 & PropTypes 이용해 Component 생성 예시 - src/App.js
// props 사용한 Component 생성
import MyComponent from "./MyComponent";

const App = () => {
  // return <MyComponent name="React" />; // src/MyComponent.js의 props.name 값
  // return <MyComponent>리액트</MyComponent>; // src/MyComponent.js의 props.children 값
  // return <MyComponent name="React" >리액트</MyComponent>;
  return (
    <MyComponent name="React" favoriteNumber={1}>
      리액트
    </MyComponent>
  );
};

export default App;

 

비구조화 할당 & propTypes 실행 결과

 

더보기

+더 많은 PropTypes 종류

 

  • array: 배열
  • arrayOf(다른 PropType): 특정 PropType으로 이루어진 배열. ex) arrayOf(PropTypes.number) - 숫자로 이루어진 배열
  • bool: true 혹은 false 값
  • func: 함수
  • number: 숫자
  • object: 객체
  • string: 문자열
  • symbol: ES6의 Symbol
  • node: 렌더링할 수 있는 모든 것(숫자, 문자열, 혹은 JSX코드, children도 nodePropType임
  • instanceOf(클래스): 특정 클래스의 인스턴스. ex) instanceOf(MyClass))
  • oneOf(['dog', 'cat']): 주어진 배열 요소 중 값 하나
  • oneOfType([React.PropTypes.string, PropTypes.number]): 주어진 배열 안의 종류 중 하나
  • objectOf(React.PropTypes.number): 객체의 모든 키 값이 인자로 주어진 PropType인 객체
  • shape({ name: PropTypes.string, num: PropTypes.number }): 주어진 스키마를 가진 객체
  • any: 아무 종류

+proptypes에 대한 자세한 정보는 여기서 확인할 수 있습니다.

 

GitHub - facebook/prop-types: Runtime type checking for React props and similar objects

Runtime type checking for React props and similar objects - GitHub - facebook/prop-types: Runtime type checking for React props and similar objects

github.com

 


 

3-4. 클래스형 컴포넌트

 

클래스형 컴포넌트에서 props를 사용하는 경우에는 render 함수에서 this.props를 조회하면 됩니다.

defaultProps와 propTypes는 똑같은 방식으로 설정할수도 있고, class 내부에서 지정할 수도 있습니다.

  • 클래스형 컴포넌트로 Component 생성 예시 - src/MyComponent.js
import { Component } from "react";
import PropTypes from "prop-types";

// 클래스형 컴포넌트

class MyComponent extends Component {
    // 클래스형 컴포넌트에서는 class 내부에서 defaultProps와 propTypes 설정 가능
    static defaultProps = {
        name: '기본 이름'
    };
    static propTypes = {
        name: PropTypes.string,
        favoriteNumber: PropTypes.number.isRequired
    };

    render() {
        const { name, favoriteNumber, children } = this.props; // 비구조화 할당
        return (
          <div>
            안녕하세요, 제 이름은 {name}입니다. <br />
            children 값은 {children}
            입니다.
            <br />
            제가 좋아하는 숫자는 {favoriteNumber}입니다.
          </div>
        );
  }
}

// // 함수, 클래스형 컴포넌트 사용 시 아래의 형태로 defaultProps,propTypes 사용 가능

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

// MyComponent.propTypes = {
//     name: PropTypes.string, // props의 타입 지정, 무조건 name 값이 문자열(string) 형태로 전달되어야 함
//     favoriteNumber: PropTypes.number.isRequired, // 필수 props 지정
// };

export default MyComponent;
  • 클래스형 컴포넌트로 Component 생성 예시  - src/App.js
// props 사용한 Component 생성
import MyComponent from "./MyComponent";

const App = () => {
  // return <MyComponent name="React" />; // src/MyComponent.js의 props.name 값
  // return <MyComponent>리액트</MyComponent>; // src/MyComponent.js의 props.children 값
  // return <MyComponent name="React" >리액트</MyComponent>;
  return (
    <MyComponent name="React" favoriteNumber={1}>
      리액트
    </MyComponent>
  );
};

export default App;

클래스형 컴포넌트로 Component 생성 실행 결과

 


4. state

 

리액트에서 state는 컴포넌트 자체적으로 지닌 값으로, 컴포넌트 내부에서 바뀔수 있는 값을 의미합니다.

리액트에는 두 가지 종류의 state가 존재합니다.

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

 

4-1. 클래스형 컴포넌트

 

  • state를 사용한 클래스형 컴포넌트 예시1 - src/Counter.js
// 예시1 - constructor 과 this.setState 사용하는 Counter (값 + 1)

import { Component } from "react";

class Counter extends Component {
  // 컴포넌트에 state를 설정할 때는 다음과 같이 constructor 메서드를 작성하여 설정함
  // 컴포넌트 생성자 메서드
  constructor(props) {
    super(props); // 클래스형 컴포넌트에서 constructor를 작성할 때는 반드시 이 문장을 호출해야 함
    // 함수 호출 시 현재 클래스형 컴포넌트가 상속받고 있는 리액트의 Component 클래스가 지닌 생성자 함수를 호출해줌
    // this.state에 (state의) 초기값 설정하기, component의 state는 객체 형식이어야 함
    this.state = {
      number: 0,
      fixedNumber: 0,
    };
  }

  render() {
    const { number, fixedNumber } = this.state; // state를 조회할 때는 this.state로 조회
    return (
      <div>
        <h1>{number}</h1>
        <h2>바뀌지 않는 값: {fixedNumber}</h2>
        <button
          // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정합니다.
          // button안에 onClick이라는 값을 props로 넣어줌 => 이벤트 설정
          onClick={() => {
            // this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
            this.setState({ number: number + 1 }); // number 변수의 값만 1을 증가시켜줌
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;
  • state를 사용한 클래스형 컴포넌트 예시1 - src/App.js
// state를 사용한 클래스형 컴포넌트
import Counter from "./Counter";

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

export default App;

 

state를 사용한 클래스형 컴포넌트 사용 예시1: 버튼 클릭 시 숫자가 1씩 올라갑니다

 

  • state를 사용한 클래스형 컴포넌트 예시2 - src/Counter.js
// 예시2 - Counter (number + 1)
import { Component } from 'react';

class Counter extends Component {
  // constructor 메서드를 선언하지 않고 state 초기값을 설정하는 방법
  state = {
    number: 0,
    fixedNumber: 0,
  };

  render() {
    const { number, fixedNumber } = this.state; // state를 조회할 때는 this.state로 조회
    return (
      <div>
        <h1>{number}</h1>
        <h2>바뀌지 않는 값: {fixedNumber}</h2>
        <button
          // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정합니다.
          // button안에 onClick이라는 값을 props로 넣어줌 => 이벤트 설정
          onClick={() => {
            // this.setState((prevState) => {
            //   // prevState: 기존 상태, props: 현재 지니고 있는 props(생략 가능)
            //   return {
            //     // 업데이트 하고 싶은 내용
            //     number: prevState.number + 1,
            //   };
            // });

            // // 위 코드와 아래 코드는 완전히 똑같은 기능을 합니다.
            // // 아래 코드는 함수에서 바로 "객체"를 반환한다는 의미입니다.
            // this.setState((prevState) => ({
            //   number: prevState.number + 1,
            // }));

            // setState를 사용해 값을 업데이트 한 후 특정 작업을 하고 싶은 경우 setState의 두 번째 파라미터로 콜백(callback) 함수를 등록하여 작업을 처리할 수 있음
            this.setState(
              {
                number: number + 1,
              },
              () => {
                console.log('방금 setState가 호출되었습니다.');
                console.log(this.state);
              }
            );
            // 화살표함수에서 값을 바로 반환하고 싶다면 {}를 생략하면 됨
            // ex) const sum = (a, b) => a + b;
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;
  • state를 사용한 클래스형 컴포넌트 예시2 - src/App.js
// state를 사용한 클래스형 컴포넌트
import Counter from './Counter';

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

export default App;

state를 사용한 클래스형 컴포넌트 사용 예시2: 버튼 클릭 시 숫자가 1씩 올라갑니다
state를 사용한 클래스형 컴포넌트 사용 예시2: console(f12)에 출력되는 모습

 


 

4-2. 함수 컴포넌트

 

리액트 16.8 이전 버전에서는 함수 컴포넌트에서 state를 사용할 수 없었습니다. 

하지만 리액트 16.8 버전 이후부터는 useState라는 함수를 사용해 함수 컴포넌트에서도 state를 사용할 수 있게 되었습니다.

이 과정에서 위에서 잠깐 언급한 Hooks를 사용하게 되는데, 여기서는 useState만 사용해보겠습니다.

 

Hooks를 사용하기 전에, 비구조화 할당(배열 안에 들어 있는 값을 쉽게 추출할 수 있도록 해주는 문법)을 잠깐 알아봅시다.

  • 비구조화 할당 예시
// 일반적으로 배열 값을 사용하는 방법
const array1 = [1, 2];
const one = array1[0];
const two = array1[1];

// 배열 비구조화 할당
const array2 = [1, 2];
const [one, two] = array2;

 

  • useState를 사용한 함수 컴포넌트 예시 - src/Say.js
import { useState } from 'react';

const Say = () => {
  const [message, setMessage] = useState(''); // useState 함수의 인자에 상태의 초기값을 넣어줌
  // 클래스형 컴포넌트에서 state 초기값은 객체 형태를 넣어주어야 하지만, useState에서는 반드시 객체가 아니어도 상관 없음. 값의 형태는 자유임(숫자, 문자, 객체, 배열 등..)
  // 함수 호출 시 배열이 반환되는데, 배열의 첫 번째 원소: 현재 상태, 두 번째 원소: 상태를 바꾸어 주는 함수(세터(setter) 함수라고 함)
  // 배열의 비구조화 할당을 통해 이름을 자유롭게 정해 줄 수 있음(현재는 message, setMessage로 이름을 지음)

  const onClickEnter = () => setMessage('안녕하세요!');
  const onClickLeave = () => setMessage('안녕히 가세요!');

  const [color, setColor] = useState('black');

  return (
    <div>
      <button onClick={onClickEnter}>입장</button>
      <button onClick={onClickLeave}>퇴장</button>
      <h1 style={{ color }}>{message}</h1>
      <button style={{ color: 'red' }} onClick={() => setColor('red')}>
        빨간색
      </button>
      <button style={{ color: 'green' }} onClick={() => setColor('green')}>
        초록색
      </button>
      <button style={{ color: 'blue' }} onClick={() => setColor('blue')}>
        파란색
      </button>
    </div>
  );
};

export default Say;
  • useState를 사용한 함수 컴포넌트 예시 - src/App.js 
// useState를 사용한 함수 컴포넌트
import Say from './Say';

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

export default App;

 

useState를 사용한 함수 컴포넌트 예시 - 초기 화면
useState를 사용한 함수 컴포넌트 예시 - 입장/초록색 클릭

 

 


 

4-3. 주의 사항

 

state를 사용할 때는 주의해야할 사항이 있습니다. state 값을 바꿔야 할 때는 setState 또는 useState를 통해 전달받은 세터 함수를 사용해야 합니다.

아래의 코드는 잘못된 state 사용 예입니다.

// 클래스형 컴포넌트에서..
this.state.number = this.state.number + 1;
this.state.array = this.array.push(2);
this.state.object.value = 5;

// 함수 컴포넌트에서..
const [object, setObject] == useState({ a:1, b: 1});
object.b = 2;

 

배열이나 객체를 업데이트 할때는 아래의 방법처럼 해야합니다.

배열이나 객체 사본을 만들고, 그 사본에 값을 업데이트한 후,

그 사본의 상태를 setState 혹은 세터 함수를 통해 업데이트를 합니다.

 

// 객체 다루기
const object = { a:1, b: 2, c:3 };
const nextObject = { ...object, b: 2}; // 사본을 만들어서 b 값만 덮어 쓰기
									   // 객체의 사본을 만들 때는 spread 연산자라 불리는 ...을 사용하여 처리
                                       // 배열의 사본을 만들 때는 배열의 내장 함수들을 활용함

// 배열 다루기
const array = [
	{ id: 1, value: true },
    { id: 2, value: true },
    { id: 3, value: false}
];
let nextArray = array.concat({ id: 4}); // 새 항목 추가
nextArray.filter(item => item.id !== 2); // id가 2인 항목 제거
nextArray.map(item => (item.id === 1 ? { ...item, value: false} : item)); // id가 1인 항목의 value를 false로 설정

 


이렇게 오늘은 Component에 대해 다뤄보았습니다.

 

읽어주셔서 감사합니다. 잘못된 정보는 댓글로 알려주세요!

728x90
반응형
LIST