React 뿌시기 3일차 (3) - Feat. 리액트를 다루는 기술

3장 컴포넌트

컴포넌트의 기능은 단순히 템플릿 이상
1) 데이터가 주어졌을 때 이에 맞추어 UI를 만들어 줌
2) 라이프사이클 API를 이용하여 컴포넌트가 화면에서 나타날 때, 사라질 때, 변화가 일어날 때 주어진 작업들을 처리
3) 임의 메소드를 만들어 특별한 기능을 붙여줄 수 있음




클래스형 컴포넌트

컴포넌트를 선언하는 방식 (두 가지)

1) 함수형 컴포넌트
2) 클래스형 컴포넌트

1) 함수형 컴포넌트

import React from 'react';
import './App.css';

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

export default App;

2) 클래스형 컴포넌트 (extends Component)

import React, { Component } from 'react';

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

export default App;

클래스형 컴포넌트, 함수형 컴포넌트의 차이점
클래스형 컴포넌트의 경우
1) state 기능 및 라이프사이클 기능을 사용할 수 있음
2) 임의 메서드를 정의할 수 있음

클래스형 컴포넌트

  1. Render 함수가 꼭 있어야 함
  2. 그 안에서 보여 주어야 할 JSX를 반환해야 함

함수형 컴포넌트의 장점

  1. 클래스형 컴포넌트보다 선언하기가 훨씬 편함
  2. 메모리 자원도 클래스형 컴포넌트보다 덜 사용
  3. 배포 후에도 파일 크기가 더 작음

함수형 컴포넌트의 단점

  • state와 라이프사이클 API의 사용이 불가능
    => But. Hooks 기능 도입 후 해결!

※ 리액트 공식 문서에서는 함수형 컴포넌트 + Hooks의 사용을 권장!




컴포넌트 생성

// in MyComponent.js
import React from 'react';

// 함수형 컴포넌트
const MyComponenet = () => { 
    return <div>나의 새롭고 멋진 컴포넌트 :)</div>;
}

export default MyComponenet;
// * 모듈 내보내기 (export)

// -------------------------------------------------

// in App.js
import React from 'react';
// * 모듈 불러오기 (import)
import MyComponenet from './MyComponent';

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

export default App;




props

  • properties를 줄인 표현으로 컴포넌트 속성을 설정할 때 사용하는 요소
  • props 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트에서 설정 가능

1) JSX 내부에서 props 렌더링

import React from 'react';

const MyComponenet = (props) => {
    return <div>나의 새롭고 멋진 컴포넌트 :) {props.name}</div>;
}

export default MyComponenet;

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

2) 컴포넌트를 사용할 때 props 값 지정하기

return (
  <MyComponenet name="react" />
);

3) props 기본값 설정: defaultProps

// in MyComponent.js
MyComponent.defaultProps = {
  name : '기본 이름'
}

4) 태그 사이의 내용을 보여 주는 children

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

import React from 'react';

const MyComponenet = (props) => {
    return <div>나의 새롭고 멋진 컴포넌트 :) {props.children}</div>;
}

// in App.js
export default MyComponenet;

return (
  <MyComponenet name="react"> 으아아악 </MyComponenet>
);

5) 비구조화 할당 문법을 통해 props 내부 값 추출

  • 비구조화 할당 ( = 구조 분해 문법)
  • 함수의 파라미터 부분에서도 사용 가능
    => 만약 함수의 파라미터가 객체라면 그 값을 바로 비구조화해서 사용 가능
const { name, children } = props;

// or

const MyComponent ({name, children}) => {
  ...
}

6) propTypes를 통한 props 검증

7) isRequired를 사용한 propTypes 설정

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

컴포넌트의 propTypes를 지정하는 방법

import React from 'react';
import PropTypes from 'prop-types';

const MyComponenet= ({ name, children}) => {
  ...
}
  
MyComponenet.defaultProps = {
  name: '기본 이름'
}

MyComponenet.propTypes = {
  name: PropTypes.string
  // name: PropTypes.string.isRequired
}

이렇게 설정해 주면 name 값은 무조건 문자열 형태로 전달해야 된다는 것을 의미!
만약 컴포넌트에 설정한 props가 propTypes에서 지정한 형태와 일치하지 않는다면
오류 메시지 출력!

8) PropTypes 종류

  • array
  • arrayOf
  • bool
  • func
  • number
  • object
  • string
  • symbol
  • node : 렌더링 할 수 있는 모든 것(숫자, 문자열 or JSX코드)
  • instanceOf(클래스)
  • oneOf(['dog', 'cat']) : 주어진 배열 요소 중 값 하나
  • oneOfType([React.PropTypes.string, PropTypes.number]) : 주어진 배열 안의 종류 중 하나
  • objectOf() : 객체의 모든 키 ㄱ밧이 인자로 주어진 PropType인 객체
  • shape()
  • any : 아무 종류

9) 클래스형 컴포넌트에서 props 사용하기

  • render 함수에서 this.props를 조회!
  • defaultProps와 propTypes는 똑같은 방식으로 설정
  • defaultProps와 propTypes를 class 내부에서 지정할 수도 있음
class MyComponent extends Component {
  static defaultProps = {
    name : '기본 이름'
  };

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

  render() {
    const {name, favoriteNumber, children} = this.props; // 비구조화 할당
	return (...);
  }
}




state

  • props : 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값
  • 컴포넌트 자신은 해당 props를 읽기 전용으로만 사용 (read only), 부모 컴포넌트에서만 props를 바꿀 수 있음!
  • state : 컴포넌트 내부에서 바뀔 수 있는 값
    1) 클래스형 컴포넌트가 지니는 state
    2) 함수형 컴포넌트에서 useState라는 함수를 통해 사용하는 state

1) 클래스형 컴포넌트의 state

import React, {Component} from 'react';

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});
                    }}
                ></button>
            </div>
        );
    }
}

export default Counter;

컴포넌트에 state를 설정 = constructor 메소드 작성

constructor(props) {
  super(props)
  
  // state의 초깃값 설정하기 (컴포넌트의 state는 객체 형식이어야 함)
  this.state = {
    number : 0
  };
}
  • 컴포넌트의 생성자 메소드
  • 클래스형에서 constructor를 작성할 때는 반드시 super(props)를 호출해주어야 함
  • 이 함수가 호출되면 현재 클래스형 컴포넌트가 상속받고 있는 리액트의 Component 클래스가 지닌 생성자 함수를 호출해줌

render 함수

render() {
    const {number} = this.state; // state를 조회할 때는 this.state로 조회
    return (
        <div>
            <h1>{number}</h1>
            <button
                onClick={() => {
                    this.setState({number : number + 1});
                }}
            ></button>
        </div>
    );
}
  • 현재 state를 조회할 때는 this.state를 조회하면 O
  • 이벤트 설정 = button 안에 onClick이라는 값을 props로 넣어 주었는데, 버튼이 클릭될 때 호출시킬 함수를 설정할 수 있게 해줌
  • 이벤트로 설정할 함수를 넣어 줄 때는 화살표 함수 문법을 사용해 넣어 주어야 함
  • 함수 내부에서는 this.setState라는 함수를 사용
    => 이 함수가 state 값을 바꿀 수 있게 해줌

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

import React, {Component} from 'react';

class Counter extends Component {
    constructor(props) {
        super(props);

        this.state = { // 컴포넌트 내부에서 가질 수 있는 값
            number : 0,
            fixedNumber : 0
        };
    }

    render() {
        const {number, fixedNumber} = this.state; // state를 조회할 때는 this.state로 조회
        return (
            <div>
                <h1>{number}</h1>
                <h1>{`바뀌지 않는 값 ${fixedNumber}`}</h1>
                <button
                    onClick={() => {
                        this.setState({number : number + 1});
                    }}
                >
                    +1
                </button>
            </div>
        );
    }
}

export default Counter;

state를 constructor에서 꺼내기

import React, { Component } from 'react';

class Counter extends Component {
  state = {
    number: 0,
    fixedNumber: 0
  };

  render() {
    const { number, fixedNumber } = this.state
  }
}

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

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

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

화살표 함수에서 값을 바로 반환하고 싶다면 코드 블록 { } 를 생략하면 됨!
예를 들어, a,b를 받아서 합을 구한다면 다음과 같이 작성할 수 있음

const sum = (a,b) => a+b;

onClick에서 두 번째로 this.setState 함수를 사용할 때는 화살표 함수에서 바로 객체를 반환하도록 했기 때문에 prevState => ({})와 같은 형태로 코드가 이루어짐

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

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

this.setState(변경할 state 정보, 콜백 함수)




2) 함수형 컴포넌트에서 useState 사용하기

  • 리액트 버전 16.8 이후부터 useState 함수를 사용하여 함수형 컴포넌트에서도 state를 사용할 수 있게 됨
  • 이 과정에서 Hooks라는 것을 사용하게 됨

배열 비구조화 할당

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

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

useState 사용하기

import React, { useState } from 'react';

const Say = () => {
    const [ message, setMessage ] = useState('');
    // useState 함수 = 인자에 상태의 초깃값을 넣어줌
    /**
     * 클래스형 컴포넌트에서의 state 초깃값은 객체 형태로 넣어 주어야 함
     * useState에서는 반드시 객체가 아니어도 상관없음 (값의 형태는 자유!)
     * ex) 숫자, 문자열, 객체, 배열
     * 
     * useState 함수의 return 값 = 함수
     * [ 현재 상태, 상태를 바꾸어 주는 함수 (Setter 함수) ]
     * => 배열 비구조화 할당을 통해 이름을 자유롭게 정할 수 ㅇ
     */
    const onClickEnter = () => setMessage('안녕하세요!');
    const onClickLeave = () => setMessage('안녕가세요!');

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

export default Say;

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

  • useState는 한 컴포넌트에서 여러 번 사용해도 상관 없음! (각기 다른 state 객체를 만들어줄 때 마다~)
    ex)
    const [message, setMessage] = useState('')
    const [color, setColor] = useState('black')

state를 사용할 때 주의사항

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

// 잘못된 예시! ❌ (in 클래스 컴포넌트)
this.state.number = this.state.number + 1;

// 잘못된 예시! ❌ (in 함수형 컴포넌트)
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값만 덮어 쓰기

// 배열 다루기
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);
nextArray.map((item) => (item.id === 1 ? {...item, value: false} : item));
  1. 객채에 대한 사본을 만들 때
  • spread 연산자라 불리는 ...을 사용하여 처리
  1. 배열에 대한 사본을 만들 때
  • 배열의 내장 함수들을 활용




정리

  1. props는 부모 컴포넌트가 설정하고, state는 컴포넌트 자체적으로 지닌 값으로 컴포넌트 내부에서 값을 업데이트!
  2. props를 사용한다고 해서 값이 무조건 고정적이지 않음
  • 부모 컴포넌트의 state를 자식 컴포넌트의 props로 전달하고, 자식 컴포넌트에서 특정 이벤트가 발생할 때 부모 컴포넌트의 메소드를 호출하면 props도 유동적으로 사용 가능
  1. state를 다루기 위한 방법
    1) 클래스형 컴포넌트의 state
    2) 함수형 컴포넌트의 useState (앞으로 함수형 컴포넌트, Hooks를 사용하는 것이 주요 컴포넌트 개발 방식이 될 것)

좋은 웹페이지 즐겨찾기