컴포넌트

nasagong·2023년 2월 4일
0

React

목록 보기
4/15
post-thumbnail

📚 들어가며

리액트 프로젝트를 구성하는 가장 작은 단위인 컴포넌트에 대한 단원이다. 앞 파트에서 이미 어느정도 사용해봤으니 이번에 확실하게 친해져보자.

1. 클래스형 컴포넌트

앞서 작성했던 컴포넌트들은 전부 함수형 컴포넌트였다. 하지만 컴포넌트는 클래스 형태로도 선언이 가능하다. 예시를 확인해보자.

import { Component } from 'react';

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

역할 자체는 함수형 컴포넌트와 큰 차이가 없다. 한 가지 낯선 부분이 있다면 render함수다. 클래스형 컴포넌트는 반드시 render함수가 있어야 한다. 보여줄 부분을 render함수에 넣는다고 단순하게 생각해도 될 것 같다.

함수형 컴포넌트 대신 클래스형을 선택할 이유가 있을까? 함수형 컴포넌트는 클래스형과 달리 state와 라이프사이클 API를 사용할 수 없다는 단점이 있다고 한다.(당연히 난 아직은 이게 뭔지 모른다). 하지만 Hooks를 사용하면 함수형에서도 사용할 수 있기에 리액트 공식 문서에서는 함수형 컴포넌트와 Hooks를 사용해 코드를 작성하는 걸 권장한다고 한다.

2. 컴포넌트 불러오기

컴포넌트는 다른 컴포넌트에서 렌더링 될 수 있다.

const MyComponent = () => {
    return <div>New Component</div>
}
export default MyComponent;

가령 위 같은 컴포넌트를 작성한 후 export를 통해 내보냈다고 하자. 이제 이 컴포넌트를 App.js에서 불러와보자.

import MyComponent from "./MyComponent";

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

서버를 확인해보면 New Component가 렌더링되고 있다. import를 사용해 간단하게 컴포넌트를 불러와주고, 태그처럼 사용해 쉽게 렌더링 할 수 있다.

3. Props

Props는 Properties를 줄인 표현으로, 컴포넌트 속성을 설정할 때 사용된다. Props는 해당 컴포넌트를 불러와서 사용하고 있는 부모 컴포넌트에서 설정할 수 있는데, 와닿지 않을테니 코드로 이해해보자.

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

앞서 작성한 MyComponent를 위와 같이 수정해봤다. name이라는 props에 접근해야 하는데, name은 어디서 설정되는 걸까? 앞서 언급했던 부모 컴포넌트에서 설정할 수 있다. 여기서는 App컴포넌트가 되겠다!

import MyComponent from "./MyComponent";

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

부모 컴포넌트에서 Mycomponent의 props인 name을 리액트로 설정해줬다. 화면에는 ”My name is React"가 정상적으로 렌더링 됐다.

부모 컴포넌트에서 props값을 지정하지 않았을 때를 대비해 defaultProps를 설정하는 것도 가능하다. App 컴포넌트에서 name값을 지우고 MyComponent에서 디폴트 값을 설정해보자.

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

MyComponent.defaultProps ={
    name : '기본값'
};

객체를 생성해 기본값을 지정해줬다. 예상대로 “My name is 기본값”이 렌더링됐다.

children 값

컴포넌트를 태그 사이의 내용을 보여주는 props를 children이라고 한다. 역시나 코드로 확인해보자.

// App.js
import MyComponent from "./MyComponent";

const App = () =>{
    return <MyComponent>리액트</MyComponent>
}
export default App;
// MyComponent.js 
const MyComponent = props => {
    return <div>칠드런 값은 {props.children}입니다.</div>
};
export default MyComponent;

"칠드런 값은 리액트입니다.“ 가 렌더링됐다. 이처럼 컴포넌트 태그 사이에 있는 props요소를 children이라고 부른다.

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

Destructing을 통해 props에 접근할 때마다 일일히 props를 입력하는 수고를 줄일 수 있다.

const MyComponent = props => {
    const {name,children} = props;
    return <div>이름은 {name}입니다.<br/>칠드런 값은 {children}입니다.</div>
};
export default MyComponent;

이런 식으로 컴포넌트 내에서 선언하는 것도 가능하며

const MyComponent = ({name,children}) => {
    return <div>이름은 {name}입니다.<br/>칠드런 값은 {children}입니다.</div>
};
export default MyComponent;

아예 파라미터에서 선언해두는 것도 가능하다. 아무튼 이런 식으로 구조분해를 통해 코드를 좀 더 짧게 쓸 수 있다는 것..!
교재에서도 쭉 이렇게 작성할 예정이니 익숙해지도록 하자

PropTypes를 통해 Props검증하기

PropTypes를 통해 컴포넌트의 필수 Props를 지정하거나 Props의 타입을 지정할 수 있다. defaultProps를 설정할 때와 마찬가지로 해당 컴포넌트가 작성된 코드에서 객체형태로 설정하면 된다.

import PropTypes from 'prop-types';

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

이런 식으로 말이다. 위 코드는 props.name의 값을 문자열로 지정하고 있다. 만약 props.name이 string 외의 타입으로 선언될 경우 개발자도구에서 오류가 뜬다. (렌더링은 된다)

이번엔 isRequred를 사용해 필수 PropsType을 지정해보자.

import PropTypes from 'prop-types';

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

favoriteNumber라는 이름의 Props는 숫자여야하고, 설정되어 있어야 한다. 둘 중 하나라도 충족하지 못한다면 콘솔창에서 오류가 출력될 것이다. (역시나 그냥 빈자리로 두고 렌더링이 되기는 한다.)

PropTypes에서 설정할 수 있는 타입은 정말 다양한데, 공식문서 등을 통해 확인할 수 있다. 필요해지면 그때 다시 찾아보는 걸로..

클래스형 컴포넌트에서 Props 사용하기

import {Component} from 'react';

class MyComponent extends Component {
    render(){
        const {name,children} = this.props;
        return (
            <div>
                이름은 {name}입니다. <br/>
                칠드런 값은 {children}입니다.놀랍죠?
            </div>
        );
    }
}
export default MyComponent;

클래스형에서 props를 사용할 땐 this.props를 조회한다.

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

class MyComponent extends Component {
    static defaultProps ={
        name : '기본'
    };
    static propTypes ={
        name : PropTypes.string
    };
    render(){
        const {name,children} = this.props;
        return (
            <div>
                이름은 {name}입니다. <br/>
                칠드런 값은 {children}입니다.놀랍죠?
            </div>
        );
    }
}

export default MyComponent;

추가로, 클래스형 컴포넌트에선 defaultProps와 propTypes를 static을 사용해 class내부에서 지정할 수 있다.

4. state

앞서 클래스형 컴포넌트를 처음 소개할 때, 함수형 컴포넌트는 state를 사용할 수 없음을 언급했다. 이번엔 그 state가 무엇인지에 대해 알아볼 것이다. 간단하게 말해서 컴포넌트 안에서 갱신될 수 있는 값을 state라고 부른다. 코드 하나를 먼저 쭉 읽어보자.

import { Component } from 'react';

class Counter extends Component {
    constructor(props){
        super(props);
        this.state = {
            number:0
        };
    }
    render(){
        const {number} = this.state;
        return (
            <div>
                <h1>{number}</h1>
                <button onClick={
                    ()=>{
                        this.setState({number : number+1});
                    }}>
                        +1
                    </button>
            </div>
        );
    }
}
export default Counter;

App컴포넌트에서 위 코드를 렌더링해보면, 버튼을 누를 때마다 브라우저에 보이는 숫자가 +1 되는 페이지가 나온다. state기능을 사용해 바로바로 number를 갱신하고 있는 것이다!

이제 코드를 하나하나 살펴보자.

constructor(props){
      super(props);
      this.state = {
            number: 0
      };
}

생성자 함수는 자바스크립트에서 봤기에 익숙하다. 클래스형 컴포넌트에서 생성자 함수를 작성할 때는 항상 super(props)를 호출해줘야 한다. 그래야만 클래스가 상속받고 있는 리액트 Component 클래스의 생성자 함수를 호출해주기 때문이라고 한다.

클래스형 컴포넌트는 class (클래스이름) extends Component 이런 식으로 선언되고 있는데, 여기서 extends 뒤에 나온 Component의 생성자를 호출한다.. 이 말 같다. 일단은 super를 호출해야 한다는 사실만 알고 가자.

생성자 함수 내부에서 this.state 값을 설정하고 있다. 객체 내에서 키-값을 지정해주는 식으로 선언하고 있는 모습이다.
여기까지가 state의 초깃값 설정이다.

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

render 함수를 보자. this.state로 state에 접근해 구조분해할당을 하는 모습이 맨 처음 보인다. 아래에 보이는 setState에 주목하자. 예상대로 state값을 갱신하는 역할을 하는 함수다. 역시나 객체형태로 파라미터를 넣었다. 아마 리액트 자체가 이런 스타일인가보다.

생성자 없이 state 설정하기

state는 생성자 함수 없이도 설정할 수 있다. 생성자 함수를 매번 작성하는 것 보다는 이게 더 편해보인다.

class Counter extends Component {
    state = {
        number :0
    };
    render(){ ... }
}

그냥 이렇게 적으면 된다. 헉.. 좀 어이없네

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

이 글을 먼저 좀 읽어봤다. setState가 비동기 함수라 일어나는 문제에 대해 적혀있다. 그렇기에 setState는 콜백함수를 인자로 넘기는 게 더 안전하다고 한다.

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

setState의 인자를 콜백함수로 작성해봤다. 작동 자체는 객체를 인자로 넘겨줄 때와 똑같이 한다.

this.setState((prevState,props)=>{
    return {...};
})

기본적으로 이런 형태로 작성하면 된다. 콜백함수의 두번째 인자인 props는 현재 지니고 있는 props를 의미한다.

seState가 끝나고 난 후 특정 작업 실행하기

setState의 두번째 피라미터에 콜백함수를 넣으면 state가 갱신된 후 원하는 작업이 실행되도록 할 수 있다.

this.setState({number:number+1},()=>{
                 alert('setState가 호출되었습니다.');
            });

이런 식으로 말이다.

함수형 컴포넌트에서 state사용하기

useState함수를 사용하면 함수형 컴포넌트에서도 state를 사용할 수 있다. 먼저 코드를 확인해보자.

import {useState} from 'react';

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>
    );
}
export default Say;

useState함수는 인자로 초기값을 받는다. 초기값의 형태는 배열, 객체, 숫자 등등 뭐든 상관없다. 함수는 원소가 두개인 배열을 반환하는데, 첫번째 값은 인자로 넣은 초기값이고 두번째는 상태를 변경시키는 함수다. (이를 setter라고 부른다.)

즉, 위 코드에서 message는 ‘초기값입니다’가 되며 setMessage가 세터 함수가 된다. 세터 함수에 값을 넣으면 상태가 변화된다.

최종적으로 위 코드를 App에서 렌더링해보면, 입장/퇴장 버튼에 따라 다른 텍스트가 출력되는 화면이 표시된다.

useState 여러개 사용하기

const Say = () =>{
    const [message,setMessage] = useState('초기값입니다');
    const onClickEnter = () => setMessage('안녕하세요!');
    const onClickLeave = () => setMessage('안녕히 가세요!');

    const [color,setColor] = useState('black');
    {/* ’color:black‘형태로 리턴되기에 style적용이 가능함*/}

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

당연히 하나의 컴포넌트에서 useState를 여러번 사용할 수 있다. 이번엔 message의 색깔 상태를 변화시키는 버튼들을 useState를 사용해 만들어봤다.

배열 혹은 객체를 업데이트하기

함수형 컴포넌트던 클래스형 컴포넌트던 state값을 바꾸기 위해선 setState를 사용하거나 useState에서 반환한 세터함수를 사용해야 한다.

const [object,setObject] = useState({a:1, b:1});
object.b = 2;

위 코드 처럼 냅다 state값을 갱신하려고 하면 안 된다는 거다.
그렇다면 배열이나 객체의 state를 변경시키기 위해선 어떡해야될까?

사본을 만들어서 원하는대로 업데이트 후 업데이트된 사본의 상태를 state에 반영시키면 된다. 생각보다 원시적(?)이다.

import React from 'react';
const App = () => {
  const [member, setMember] = 
        useState({name:"nasagong", age:23});
  function updateMember() {
    let temp = member;
    temp.name = "고길동";
    setMember(temp);
  }
  return (...);
}

이렇게 간접적으로 값을 바꾸라는 것.

클래스 컴포넌트를 배우긴 했지만 가급적 권장되는 함수형 컴포넌트와 hooks를 사용하도록 하자!

profile
잘쫌해

0개의 댓글