2. 컴포넌트

나혜수·2023년 3월 13일
0

리액트

목록 보기
2/23

✅컴포넌트

리액트의 컴포넌트는 JSX를 반환하는 함수이다.

컴포넌트를 선언하는 방식은 2가지이다. 하나는 함수형 컴포넌트이고 또 다른 하나는 클래스형 컴포넌트이다. 이 둘의 차이점은 클래스형 컴포넌트의 경우 이후 배울 state 기능 및 라이프사이클 기능을 사용할 수 있다는 것과 임의 메소드를 지정할 수 있다는 것이다.

함수형 컴포넌트의 주요 단점은 state와 라이프사이클 API를 사용이 불가능하드는 점인데, 리액트 v16.8 업데이트 이후 Hooks 기능이 도입되면서 해결되었다.

  • 함수형 컴포넌트
import React from "react";
import "./App.css";

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

export default App;

  • 클래스형 컴포넌트
import React, { Component } from "react";
import "./App.css";

class App extends Component {
  
  // render 함수가 꼭 있어야 하고, 그 안에서 보여줘야 할 JSX를 반환해야 한다.
  render(){
    const name = 'react'
    return <div className="react">{name}</div>
  }
}

export default App;

컴포넌트 생성

MyComponent.js

import React from 'react'

const MyComponent = () => {
    return <div>나의 컴포넌트</div>
}

// 모듈 내보내기 
export default MyComponent

App.js

// 모듈 불러오기 
import React from "react";
import MyComponent from "./MyComponent";

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

export default App;

VScode에서 Reactjs Code Snippet 확장 프로그램을 설치했다면 컴포넌트 코드를 간편하고 빠르게 생성할 수 있다. 에디터에서 rsc를 입력하고 엔터를 누르면 된다. 참고로 클래스형 컴포넌트는 rcc를 입력해 사용할 수 있다.


✅props

프로퍼티, props (properties의 줄임말) 라고 한다.

  • 상위 컴포넌트가 하위 컴포넌트에 값을 전달할 때 사용한다. (단방향 데이터 흐름)
  • props 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트에서 설정하며, 컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있다. props를 바꾸려면 부모 컴포넌트에서 바꿔줘야 한다.
  • props에 문자열을 전달할 때는 큰 따옴표 " ", 문자열 외의 값을 전달할 때는 중괄호 { }를 사용 한다.

현 상황에서는 App 컴포넌트가 부모 컴포넌트이다.

MyComponent.js

import React from 'react'

const MyComponent = props => {
    return <div>제 이름은 {props.name} 입니다.</div>
}
export default MyComponent

App.js

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

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

export default App;

props 값을 따로 지정하지 않았을 때 보여줄 기본값을 설정하는 defaultProps에 대해 알아보자.

// MyComponent.js
import React from 'react'

const MyComponent = props => {
    return <div>제 이름은 {props.name} 입니다.</div>
}

// 비구조화 할당으로 defaultprops 설정 
/*
const MyComponent = ({name='혜수'}) => {
    return <div>제 이름은 {name} 입니다.</div>
*/

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

export default MyComponent

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

컴포넌트 태그 사이의 내용을 보여주는 prop가 있는데 바로 children이다. children의 propTypes는 node이다.

MyComponent.js

import React from "react"

const MyComponent = (props) => {
  return (
  <div>
    제 이름은 {props.name} 입니다. <br />
    children 값은 {props.children}
    입니다. 
  </div>
  )
}

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

MyComponent.propTypes = {
  name: PropTypes.string,
  cihldren: PropTypes.node
}

export default MyComponent

App.js

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

const App = () => {
  return <MyComponent>리액트</MyComponent>
}

export default App;

MyComponent 태그 사이 작성한 '리액트'라는 문자열을 MyComponent 내부에서 보여주려면 props.children 값을 보여주면 된다.

구조 분해를 이용한 props 내부 값 추출

구조 분해 문법 (비구조화 할당)은 함수의 파라미터 부분에서도 사용할 수 있다.

import React from "react"

const MyComponent = ({name, children}) => {
/* const MyComponent = (props) => {
   const {name, children} = props   */ 
  
  return (
  <div>
    제 이름은 {name} 입니다. <br />
    children 값은 {children}
    입니다. 
  </div>
  )
}

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

export default MyComponent

propTypes

프로퍼티의 자료형, 타입 정의
컴포넌트의 필수 props를 지정하거나 props 타입을 지정할 때는 propTypes를 사용한다. propTypes를 사용하려면 코드 상단에 import를 사용해 불러와야 한다.

MyComponent.js

import React from "react"
import PropTypes from 'prop-types' // '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 타입 지정 
    favoriteNumber: PropTypes.isRequired   // 필수 props
}

export default MyComponent

이렇게 설정하면 name 값은 무조건 문자열 형태로 전달해야 한다. 만약 App 컴포넌트에서 name 값에 숫자를 전달하며 console 창에 경고 메세지가 출력된다.
PropTypes.isRequired를 이용해 필수 props를 설정할 수 있다.

App.js

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

const App = () => {
  return <MyComponent name="혜수" favoriteNumber={3}>리액트</MyComponent>
}

export default App;

PropType의 종류는 다음과 같다.

  • string, number, bool, func, symbol, object, array

  • arrayOf : 특정 propType으로 이루어진 배열을 의미한다. 예를 들어 arrayOf(PropTypes.number)는 숫자로 이루어진 배열이다.

  • node : 렌더링할 수 있는 모든 것 (숫자, 문자열, JSX코드, children)

  • 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 : 아무 종류


클래스형 컴포넌트에서 props

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

import React, { Component } from "react"
import PropTypes from 'prop-types'

class MyComponent extends Component {
    render(){
        const {name, favoriteNumber, children} = this.props
        return (
            <div>
              제 이름은 {name} 입니다. <br/>
              children 값은 {children}
              입니다. 
              <br/>
              제가 제일 좋아하는 숫자는 {favoriteNumber}입니다. 
            </div>
        )
    }
}


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

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

export default MyComponent

defaultProps와 propTypes를 설정할 때 클래스 내부에서 지정하는 방법도 있다.

import React, { Component } from "react"
import PropTypes from 'prop-types'

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>
        )
    }
}

export default MyComponent

❗defaultProps와 propTypes 설정은 컴포넌트의 필수 사항은 아니다.


✅state

state는 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다. props는 부모 컴포넌트가 설정하는 값이며, 컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있다. props를 바꾸려면 부모 컴포넌트에서 바꿔줘야 한다.

리액트에는 2종류의 state가 있다. 하나는 클래스형 컴포넌트가 지니고 있는 state, 다른 하나는 함수형 컴포넌트에서 useState 함수를 통해 사용하는 state이다.

클래스형 컴포넌트의 state

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

컴포넌트의 state는 객체 형식이어야 하며, render 함수에서 현재 state를 조회할 때는 this.state를 이용한다. state 객체 안에는 여러 값이 있을 수 있다. this.setState 함수는 인자로 전달된 객체 안에 들어있는 값만을 바꿔준다.


Counter.js

import React, {Component} from 'react'

class Counter extends Component{
  
    constructor(props){
        // 클래스형 컴포넌트에서 constructor 작성 시 반드시 super(props) 호출해줘야 함
        super(props)

        // state 초기값 설정 
        this.state = {
            number: 0,
            fixedNumber: 0
        }
    }
    render(){
        const {number, fixedNumber} = this.state
        return (
            <div>
                <h1>{number}</h1>
                <h2>{fixedNumber}</h2>          
                <button onClick={()=>{
                    this.setState({number: number+1})
                }}>
                    +1
                </button>
            </div>
        )
    }
}

export default Counter

App.js

import React from "react";
import Counter from './Counter'

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

export default App;

state 초기값 지정을 위해 constructor 메소드를 선언하는 방식 외에도 또 다른 방법으로 state 초기값을 설정할 수 있다.

import React, {Component} from 'react'

class Counter extends Component{
    // state 초기값 설정 
    state = {
        number: 0,
        fixedNumber: 0
    }

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

export default Counter

비동기적 setState

this.setState를 사용해 state 값을 업데이트할 때는 상태가 비동기적으로 업데이트 된다.
setState는 this.state 를 즉시 변경하지 않고 state 변화를 대기하도록 만든다. 따라서 setState를 호출한 직후에 this.state에 접근하면 기존 값을 반환받을 수 있다.

setState가 동기적으로 동작하는데 자주 일어나게되면, 성능적인 문제가 생길 수 있다. 하나의 상태값이 바뀔 때마다 꼬박꼬박 한번씩 리렌더링 하는 것은 효율적이지 않다. setState가 여러번 일어난다면, 일괄적으로 setState를 묶어 실행하는 편이 더 효율적일 것이다. 이렇게 일괄적으로 변경된 상태 값들을 처리하는 과정을 batch update라고 한다. React에서는 16ms 단위로 batch update를 진행한다. 16ms 동안 변경된 상태 값들을 모아 리렌더링을 진행한다.

import React, {Component} from 'react'

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

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

export default Counter

위 코드에서 this.setState를 2번 사용하는 것임에도 불구하고 버튼을 클릭하면 숫자가 1씩 더해진다. this.setState를 사용한다고 해서 state 값이 바로 바뀌지 않기 때문이다. setState 함수는 이벤트 핸들러 함수에서 바로 state값을 갱신하는 것이 아니라 이벤트 핸들러 함수 종료 후 값을 갱신하기 때문이다.

이에 대한 해결책으로 this.setState를 사용할 때 객체 대신 콜백함수를 넣을 수 있다.

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

// prevState는 기존 상태
// props는 현재 가지고 있는 props (필요없으면 생략 가능) 

import React, {Component} from 'react'

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

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

                    // 화살표 함수에서 값을 바로 반환하고 싶으면 코드블록 { } 생략 
                    // 화살표 함수에서 바로 객체를 반환하고 있음 
                    this.setState(prevState => ({
                        number: prevState.number + 1
                    }))
                }}>
                    +1
                </button>
            </div>
        )
    }
}

export default Counter

콜백 함수에 지정된 작업은 상태가 업데이트된 후에만 수행된다.

setState 함수를 사용하여 값을 업데이트한 후 특정 작업을 실행하고 싶을 때는 setState의 두번째 파라미터로 콜백함수를 넣는다.

import React, {Component} from 'react'

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

    render(){
        const {number, fixedNumber} = this.state
        return (
            <div>
                <h1>{number}</h1>
                <h2>{fixedNumber}</h2>          
                <button onClick={()=>{
                    this.setState(
                        {
                            number: number + 1
                        },
                        () => {
                            console.log('방금 setState가 호출되었습니다.')
                            console.log(this.State)
                        }
                    )
                }}>
                    +1
                </button>
            </div>
        )
    }
}

export default Counter



함수형 컴포넌트에서 useState

useState 함수의 인자에는 상태의 초기값을 넣어준다. 값의 형태는 자유로, 클래스형 컴포넌트처럼 객체 형태가 아니어도 상관없다. useState 함수는 배열을 반환한다. 배열의 첫번째 원소는 현재 상태, 두번째 원소는 상태를 바꾸어주는 함수이다. 이 함수를 setter 함수라고 부른다. 그리고 배열 분해 문법을 통해 이름을 자유롭게 정해줄 수 있다.

Say.js

import React, {useState} from "react";

const Say = () => {

    // useState 인자에 상태의 초기값 설정 (값의 형태는 자유)
    // useState는 배열을 반환한다. (배열 분해 문법)
    // 배열의 첫번째 원소는 현재 상태, 두번째 원소는 상태를 바꾸어주는 함수 (setter)
    const [message, setMessage] = useState('')

    const onClickEnter = () => setMessage('안녕하세요.')
    const onClickLeave = () => setMessage('안녕가세요.')

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

export default Say

App.js

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

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

export default App;


state 사용 시 주의사항

state 값을 바꿀 때는 setState or useState를 사용해야 한다. 배열이나 객체 사본을 만들고 그 사본에 값을 업데이트한 후, 사본의 상태를 setState or useState를 통해 업데이트 한다.

// 객체 
const object = {a:1, b:2, c:3}
const nextObject = {...object, b:2}

// 배열 
const array = [
  {id:1, value:true},
  {id:2, value:true},
  {id:3, value:false}
]

let nextArray = arrat.concat({id:4})
nextArray.filter(item => item.id !== 2) // id 2 제거 
nextArray.map(item => item.id ===1 ? {...item, value:false} : item)
// id 1의 값을 false로 바꿈 

정리

props와 state는 둘 다 컴포넌트에서 사용하거나 렌더링할 데이터를 담고 있다. props는 부모 컴포넌트가 설정하고, state는 컴포넌트 자체적으로 지닌 값으로 컴포넌트 내부에서 값을 업데이트할 수 있다.

profile
오늘도 신나개 🐶

0개의 댓글