프론트엔드/리액트를 다루는 기술

리액트를 다루는 기술, 컴포넌트

Hyeon_E 2023. 6. 11. 23:07

리액트를 사용하여 애플리케이션의 인터페이스를 설계할 때

사용자가 볼 수 있는 요소는 여러가지 컴포넌트로 구성되어 있음

컴포넌트의 기능은 단순한 탬플릿 이상임

데이터가 주어졌을 때 이에 맞추어 UI를 만들어 주는 것은 물론이고

라이프사이클 API를 이용하여 컴포넌트가 화면에서 나타날 때, 사라질 때, 변화가 일어날때

주어진 작업들을 처리할 수 있으며 임의 메서드를 만들어 특별한 기능을 붙여 줄 수 있음

 

[ 3.1 클래스형 컴포넌트 ]

클래스형 컴포넌트와 함수형 컴포넌트의 차이점은

클래스형 컴포넌트의 경우 state 기능 및 라이프 사이클 기능을 사용할 수 있다는 것과 임의 메서드를 정의할 수 있다는 것

클래스형 컴포넌트에서는 render 함수가 꼭 있어야 하고 그 안에서 보여 주어야 할 JSX를 반환

 

import "./App.css";

function App() {
  const name = "리액트";
  return <div>{name}</div>;
}
export default App;

함수형 컴포넌트

 

import { Component } from "react";
import "./App.css";

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

클래스형 컴포넌트

 

함수형 컴포넌트의 장점은 클래스형 컴포넌트보다 선언하기가 훨씬 편함

메모리자원도 덜 사용함. 또한 빌드한 후 배포할때도 결과물의 파일 크기가 더 작음

단점으로는 state와 라이프사이클 API 사용이 불가능하다는 점인데 Hooks 기능이 도입되며 해결되었음

그래서 현재 리액트 공식 매뉴얼에서는 컴포넌트를 새로 작성할때 함수형 컴포넌트와 Hooks를 사용하도록 권장

 

[ 3.2 첫 컴포넌트 생성 ]

  1. 파일 만들기
  2. 코드 작성하기
  3. 모듈 내보내기 및 불러오기

 

▶ 3.2.1 src 디렉터리에 파일 생성

컴포넌트를 만들려면 컴포넌트 코드를 선언해야함

function 키워드를 사용하는 대신에 ( ) => { }를 사용한 함수를 화살표 함수라고 함

화살표함수는 ES6 문법에서 함수를 표한하는 새로운 방식임

 

기존 function을 이용한 함수 선언 방식을 아예 대체하지는 않음 사용

용도가 다르기 때문, 무엇보다 this가 가리키는 것이 다름

일반 함수는 자신이 종속된 객체를 this로 가리키고 화살표 함수는 자신이 종속된 인스턴스를 가리킴

 

화살표 함수 문법은 주로 함수를 파라미터로 전달할때 유용함

값을 연산하여 바로 반환할때 사용하면 가독성을 높일 수 있음

 

더보기

깨알팁!!

VS Code에서 Reactjs Code Snippet 확장 프로그램을 설치했다면 컴포넌트 코드를 간편하고 빠르게 생성할 수 있음

에디터에서 res를 입력하고 엔터를 누르면 됨

코드를 생성하고 나면 컴포넌트 이름이 선택된 상태로 나타나는데 수정하면 컴포넌트 이름을 변경할 수 있고

Tab을 누르면 선택을 해제할 수 있음

 

클래스형 컴포넌트는 rcc를 입력하면 됨

 

▶ 3.2.1 모듈 내보내기 및 불러오기

3.2.3.1 모듈 내보내기(export)

 

export default MyComponent;

 

다른 파일에서 이 파일을 import할때 위에서 선언한 MyComponent 클래스를 불러오도록 설정

하나의 모듈(파일)에는 하나의 export default가 존재함

export default로 모듈을 내보내면 export한 이름고 상관없이 원하는 이름으로 import가 가능함

하지만 이름을 일치하는것을 권장함

 

3.2.3.2 모듈 불러오기(import)

 

import React from "react";
import MyComponent from "./MyComponent";

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

export default App;

 

import 구문을 사용하여 MyComponent 컴포넌트를 불러왔음

 

더보기

깨알 팁!!

Ctrl + Shift + L하면 같은 이름을 한번에 바꿀수 있음!!

잘만 이용하면 이름을 변경할때 여러군데 바꿀 필요 없고 누락되는 경우도 없앨 수 있음

 

[ 3.3 props ]

props(properties)는 컴포넌트 속성을 설정할 때 사용하는 요소

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

 

▶ 3.3.1 JSX 내부에서 props 렌더링

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

props는 JSX 내부에서 { } 기호로 감싸주면 됨

 

props의 값을 따로 지정하지 않으면 내용이 비게됨

이럴경우 props 값을 따로 지정하지 않았을 때 보여줄 기본값을 설정하는 defaultProps가 있음

 

const MyComponent = (props) => {
  return <div>안녕하세요, 제 이름은 {props.name} 입니다.</div>;
};
MyComponent.defaultProps = {
  name: "JS",
};

 

▶ 3.3.4 태그 사이의 내용을 보여 주는 children

리액트 컴포넌트를 사용할 때 컴포넌트 태그 사이의 내용을 보여주는 props가 있음

이것을 바로 children이라고 함

 

const MyComponent = (props) => {
  return (
    <div>
      안녕하세요, 제 이름은 {props.name} 입니다 <br />
      children 값은 {props.children} 입니다.
    </div>
  );
};
MyComponent.defaultProps = {
  name: "JS",
};

 

props.children자리에 컴포넌트 태그 사이의 내용을 보여줌

 

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

props.이라는 키워드를 붙일 필요 없이 ES6의 비구조화 할당 문법을 사용하여 내부 값을 바로 추출할 수 있음

객체에서 값을 추출하는 문법을 비구조화 할당이라고 부름

 

const MyComponent = (props) => {
  const { name, children } = props;
  return (
    <div>
      안녕하세요, 제 이름은 {props.name} 입니다 <br />
      children 값은 {props.children} 입니다.
    </div>
  );
};

 

비구조화 할당은 구조 분해 문법이라고도 불리며, 함수의 파라미터 부분에서도 사용할 수 있음

함수의 파라미터 객체라면 그 값을 바로 비구조화해서 사용할 수 있음

 

const MyComponent = (props) => {
  const { name, children } = props;
  return (
    <div>
      안녕하세요, 제 이름은 {props.name} 입니다 <br />
      children 값은 {props.children} 입니다.
    </div>
  );
};

 

이렇게 하면 함수형 컴포넌트에서 props를 사용할때 훨씬 더 편리함

 

▶ 3.3.6 propTypes를 통한 props 검증

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

컴포넌트의 propTypes를 지정하는 방법은 defaultProp을 설정하는 것과 비슷함

 

import PropTypes from "prop-types";
const MyComponent = ({ name, children }) => {
  return (
    <div>
      안녕하세요, 제 이름은 {name} 입니다 <br />
      children 값은 {children} 입니다.
    </div>
  );
};
MyComponent.propTypes = {
  name: PropTypes.string,
};

 

위에 코드대로 설정하면 name값은 무조건 문자열 형태로 전달해야 된다는 것을 의미함

만약 문자열 형태가 아닌 다른 형태로 들어오면 값이 나타나기는 하지만 콘솔에 경고메세지를 출력하여 개발자에게 propTypes가 잘못되었다는 것을 알려줌

 

3.3.6.1 isRequired를 사용하여 필수 propTypes 설정

propTypes를 지정하지 않았을때 경고 메세지를 띄워줄려면 propTypes를 지정할때 뒤에 isRequired를 붙여주면 됨

 

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

 

3.3.6.2 더 많은 PropTypes 종류

  • array: 배열
  • arrayOf(다른 PropType): 특정 PropType으로 이루어진 배열을 의미함
    • arrayOf(PropTypes.number)는 숫자로 이루어진 배열
  • bool: true 혹은 false 값
  • func: 함수
  • number: 숫자
  • object: 객체
  • string: 문자열
  • symbol: ES6 Symbol
  • node: 렌더링할 수 있는 모든 것(숫자, 문자열 혹은 JSX 코드, children도 node PropType임)
  • 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: 아무 종류

▶ 3.3.7 클래스형 컴포넌트에서 props 사용하기

클래스형 컴포넌트에서 props를 사용할 때render 함수에서 this.props를 조회하면 됨

defaultProps와 propTypes는 똑같은 방식으로 설정할 수있음

 

클래스형 컴포넌트에서 defaultProps와 propTypes를 설정할때 class 내부에서 지정하는 방법도 있음

defaultProps와 propTypes는 꼭 사용해야 할 필요는 없지만

협업한다면 해당 컴포넌트에 어떤 props가 필요한지 쉽게 알 수 있어 개발 능률이 좋아질 것임

 

class MyComponent extends Component {
  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>
    );
  }
}

 

[ 3.4 state ]

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

props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값이며, 컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있음 즉 props를 바꾸려면 부모 컴포넌트에서 바꾸어 주어야 함

 

리액트는 두가지의 종류가 있음

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

▶ 3.4.1 클래스형 컴포넌트의 state

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { number: 0 };
  }
  render() {
    const { number } = this.state; //state조회할때는 this.state로 조회
    return (
      <div>
        <h1>{number}</h1>
        <button
          onClick={() => {
            this.setState({ number: number + 1 });
            //this.setState를 사용하여 state에 새로운 값을 넣을 수 있음
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

 

컴포넌트에 state를 설정할 때는 constructor 메서드를 작성하여 설정

constructor는 컴포넌트의 생성자 메서드임. constructor를 작성할때는 반드시 super(props)를 호출해 주어야 함

이 함수가 호출되면 현재 클래스형 컴포넌트가 상속받고 있는 리액트 Component 클래스가 지닌 생성자 함수를 호출해줌

컴포넌트의 state는 객체 형식이어야 함 render 함수에서 현재 state를 조회할때는 this.state를 조회하면 됨

이벤트로 설정할 함수를 넣어 줄때는 화살표 함수 문법을 사용하여 넣어 주어야 함

this.setState 함수가 state 값을 바꿀 수 있게 해줌

 

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

state 안에 또 다른 값을 추가해 주면 됨

또한 값을 변경할때는 this.setState 함수는 인자로 전달된 객체 안에 들어 있는 값만 바꾸기 때문에 원하는 값만 변경해줌

 

3.4.1.2 state를 constructor에서 꺼내기

constructor 메서드 말구도 state의 초기값을 지정해 줄 수 있음

 

state = {
    number: 0,
    fixedNumber: 0,
};

 

이렇게 하면 constructor 메서드를 선언하지 않고도 state 초기값을 설정할 수 있음

 

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

this.setState를 사용하여 state 값을 업데이트할 때는 상태가 비동기적으로 업데이트

this.setState를 사용한다고 해서 state 값이 바로 바뀌지 않음

이를 해결하기 위해 this.setState를 사용할 때는 객체 대신에 함수를 인자로 넣어줌

 

this.setState((prevState, props_ => {
  return {
    // 업데이트하고 싶은 내용
   }
}

 

prevState는 기존 상태이고, props는 현재 지니고 있는 props를 가리킴

업데이트 과정에서 props가 필요하지 않다면 생략해도 됨

 

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

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

 

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

setState를 사용하여 값을 업데이트하고 난 다음에 특정 작업을 하고 싶을 때는

setState의 두번째 파라미터로 콜백 함수를 등록하여 작업을 처리할 수 있음

 

this.setState(
      {
        number: number + 1,
      },
      () => {
        console.log("방금 setState가 호출되었습니다");
      }
    );

 

▶ 3.4.2 함수형 컴포넌트에서 useState 사용하기

리액트 16.8 이전 버전에서는 함수형 컴포넌트에서 state를 사용할 수 없었음

하지만 이후부터는 useState라는 함수를 사용하여 함수형 컴포넌트에서도 state를 사용할 수 있게 됨

이 과정에서 Hooks라는 것을 사용하게 됨

 

3.4.2.1 배열 비구조화 할당

객체 비구조화 할당과 비슷함. 배열 안에 들어 있는 값을 쉽게 추출할 수 있도록 해주는 문법

 

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

 

3.4.2.2 useState 사용하기

 

const Say = () => {
  const [message, setMessage] = useState("");
  const onClickEnter = () => setMessage("안녕하세요");
  const onClickLeave = () => setMessage("안녕히 가세요");
  return (
    <div>
      <button onClick={onClickEnter}>입장</button>
      <button onClick={onClickLeave}>퇴장</button>
      <h1>{message}</h1>
    </div>
  );
};

 

useState 함수의 인자에는 상태의 초기값을 넣어줌

클래스형 컴포넌트에서의 state 초기값과는 다르게 useState에서는 반드시 객체가 아니어도 상관없음

함수를 호출하면 배열이 반환되는데 첫번째 원소는 현재 상태이고, 두번째 원소는 상태를 바꾸어 주는 함수

이 두번째 원소는 세터(Setter) 함수라고 부름. 배열 비구조화 할당을 통해 이름을 자유롭게 정해 줄수 있음

 

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

 

const Say = () => {
  const [message, setMessage] = useState("");
  const [color, setColor] = useState("black");
  const onClickEnter = () => setMessage("안녕하세요");
  const onClickLeave = () => setMessage("안녕히 가세요");
  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>
  );
};

 

[ 3.5 state를 사용할 때 주의 사항 ]

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

그렇기 때문에 배열이나 객체를 업데이트 해야하는 경우 사본을 만들고 그 사본에 값을 업데이트 한후

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

 

// 객체 다루기
const object = { a: 1, b: 2, c: 3};
const nextObject = { ...object, b: 2};
// 배열 다루기
const array = [
  {id: 1, value: true},
  {id: 1, value: true},
  {id: 1, value: true},
];
let nextArray = array.concat({ id: 4});   //새 항목 추가
nextArray.filter(item => item.id !== 2);  //id가 2인 항목 제거
nextArray.map(item => (item.id === 1 ? {...item, value: false}: item));

 

객체에 대한 사본을 만들때는 spread 연산자라 불리는 ...을 사용하여 처리하고 배열에 대한 사본을 만들 때는 배열의 내장 함수들을 활용함

 

[ 3.6 정리 ]

propsstate는 둘 다 컴포넌트에서 사용하거나 렌더링할 데이터를 담고 있으므로 비슷해 보일 수 있지만 역활은 매우 다름. props는 부모 컴포넌트가 설정하고 state는 자체적으로 지닌 값으로 컴포넌트 내부에서 값을 업데이트할 수 있음

 

props를 사용한다고 해서 값이 무조건 고정적이지 않음

부모 컴포넌트의 state를 자식 컴포넌트의 props로 전달하고 자식 컴포넌트에서 특정 이벤트가 발생할 때 부모 컴포넌트의 메서드를 호출하면 props도 유동적으로 사용할 수 있음

 

새로운 컴포넌트를 만들때는 useState를 사용할 것을 권장함

코드가 더 간결해질 뿐만 아니라 리액트 개발팀이 함수형 컴포넌트와 Hooks를 사용하는 것이 주요 컴포넌트 개발 방식이 될것이라고 공지했기 때문