React(생활코딩)_4일차_컴포넌트 분리하기, state

Lina Hongbi Ko·2023년 8월 14일
0

React_생활코딩

목록 보기
5/23

이전에는 컴포넌트들을 어떻게 만들고 react-developer tool을 설치해서 보는 방법을 살펴보았다. 오늘은 컴포넌트를 파일로 분리하고, state에 대해서 공부하려고 한다.

컴포넌트를 파일로 분리하기

: 우리는 컴포넌트들을 App.js에서 한꺼번에 만들었다. 하지만 만약 컴포넌트들이 수백개, 수천개가 있다면 파일 하나에서 그것들을 다 관리할 수 있을까?

No. 불가능하다.

그래서 컴포넌들을 따로 분리해서 사용하는 것이 좋다. 그럼 어떻게 분리하느냐? 파일로 분리하면 된다. html이나 css, js도 기능별로, 또 영역별로 따로 분리해서 파일로 저장해놓고 쓰듯이 컴포넌트도 마찬가지이다.

그럼 시작해보자.

Let't get it!!

먼저, src 디렉토리 안에 components라는 디렉토리를 만든다. 이곳에 이제 컴포넌트들이 담기는 것이다.

그리고 index.js코드를 살펴보면

// index.js 파일

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
	<React.StrictMode>
    	<App />
    </React.StrictMode>,
    document.getElementById('root')
);

에서 네번째 줄인

import App from './App';

을 보면 create-react-app이 기본적으로 이 App이라는 컴포넌트를 같은 디렉토리에 있는 App.js라는파일에서 불러와 보여주는 것을 알 수 있다. 이를 참고해서 그대로 따라하면,

components 디렉토리로 이동해서 일단, TOC.js파일을 생성하고 컴포넌트를 만든다.

// TOC.js파일

import React, { Component } from 'react';

class TOC extends Component {
	render() {
    	return(
        	<nav>
            	<ul>
                	<li><a href="1.html">HTML</a></li>
                    <li><a href="2.html">CSS<a></li>
                    <li><a href="3.html">JavaScript</a></li>
                </ul>
            </nav>
        )
    }
}

export default TOC;

맨 위의 import React, { Component } from 'react'; 라는 부분은 react라는 라이브러리에서 Component라는 클래스를 로딩한 것이다. 앞의 'imort React'부분은 리액트로 코딩할때 필수적으로 필요한 것이므로 꼭 넣는것을 잊지 않도록 한다.
그리고 맨 아래의 'export default TOC'는 TOC.js에 여러가지 변수나 함수가 존재할 수 있어서, 이것들을 외부에서 사용할 수 있게 허용할 것인지를 코드로 표현한 것이다.

Content 라는 컴포넌트도 같은 방법으로 만들어보자.

import React, { Component } from 'react';

class Content extends Component {
	render(){
    	return(
        	<article>
            	<h2>{this.props.title}</h2>
                {this.props.desc}
            </article>
        );
    }
}

그리고나서
App.js에 TOC와 Content를 따로 파일로 분리한 TOC.js, Contents.js 에서 불러오도록 수정하면,

// App.js 파일

import React, { Component } from 'react';
import TOC from './components/TOC';
import Content from './components/Content';
import './App.css';

class App extends Component {
	render() {
    	return(
        	<div className="App">
                <TOC></TOC>
                <Content titile="HTML desc="HTML is HyperTextMarkupLanguage."
            </div>
        );
    }
}

export default App;

윗 줄에 import TOC from './components/TOC'; , import Content from './components/Content'; 를 써서 불러와주면 된다:)

마찬가지로 Subject도 별도의 파일로 분리해보자.

// Subject.js 파일

import React, {Component} from 'react';

class Subject extends Component {
	render() {
    	return(
        	<header>
            	<h1>{this.props.title}</h1>
                {this.props.sub}
            </header>
        );
    }
}

export default Subejct;

App.js파일에 연결해주자.

// App.js 파일

import React, { Component } from 'react';
import TOC from './components/TOC';
import Content from './components/Content';
import Subject from './components/Subject';
import './App.css';

class App extends Component {
	render() {
    	return(
        	<div className="App">
            	<Subject title="WEB" desc="world wide web!">
                <TOC></TOC>
                <Content title="HTML" sub="HTML is HyperText Markup Language."></Content>
            </div>
        );
    }
}

요렇게 하면 앞에 썼던 컴포넌트들을 분리 완료!

state

: 이번에는 state에 대해 알아보자.
생활코딩님의 말씀에 따르면 state는 props의 차이점을 통해 더 잘 이해할 수 있다고 했다. 그리고 제품을 통해 예를 들었는데, 제품을 사용하는 사용자의 입장과 그것을 구현하는 구현자의 입장으로 나누어 설명했다.

사용자 입장에서 보았을때 한 제품을 조작하는 장치는 버튼이나 화면의 터치 일 것이다. 이를 user interace 라고 하는데, 리액트 관점에서 props는 유저인터페이스이다. 즉, 사용자가 제품을 조작하는 장치라고 할 수 있다.

그리고 제품을 만드는 입장에서는 제품의 내부적인 구현을 위해 다양한 내부적인 조작장치와 상태를 가지고 있다. 이를 mechanism 이라고 한다. 이 매커니즘을 비유적으로 이야기한다면 state라고 볼 수 있다.

그래서 props는 사용자가 컴포넌트를 사용하는 입장에서 중요한 것이고, state는 props의 값에 따라 내부구현에 필요한 데이터 라고 할 수 있다.

좀 더 구체적으로 이 책에서는 props는 컴포넌트들을 그대로만 쓴다면 너무 아쉽기 때문에 속성을 사용한다고 한다.

사용자가 해당 컴포넌트를 조작할 수 있게 props를 사용하게 하는데 이는 사용자에게 중요한 정보이다. 반면에, 사용자는 알 필요도 없고 알아서도 안되는 컴포넌트 내부적으로 사용되는 것들을 state라고 설명한다.

컴포넌트를 만들고 어떤 시스템의 좋은 부품이 되기 위해서는 그 컴포넌트를 사용하는 외부의 props와 그 props에 따라 컴포넌트를 실제로 구현하는 내부의 state라는 정보가 철저하게 분리되어 있어야 한다고 강조한다.

왜냐? 어떤 제품을 쓸때 우리는 제품 전선이 밖으로 나와 있는 것을 보면 좋은 제품이라고 생각하기 어렵다.
이와 마찬가지로 사용하는 쪽과 그것을 구현하는 쪽을 철저히 분리해서 양쪽의 편의성을 각자 도모하는 것이 좋은 부품을 만드는 핵심이고 리액트도 마찬가지다.

state 사용

: 그렇다면 state를 사용해보자.
위에서 본 Subject 컴포넌트로 실행해보자.

<div className="App>
	<Subject title="WEB" sub="world wide web!"></Subejct>
    <TOC></TOC>
    <Content title="HTML" desc="HTML is HyperText Markup Language."></Content>
</div>

위에서 본 Subject 컴포넌트는 props값을 직접 컴포넌트 안에 넣어서 하드코딩하고 있다.
이 값을 state로 만들고 그 state의 값을 Subject의 props로 전달해보자.

이를 위해서 App의 컴포넌트를 수정해야한다.

// App.js 파일

... 생략 ...

class App extends Component {
	constructor(props) {
    	super(props);
        this.state = {
        	subject:{title:'WEB', sub:'World Wide Web!'}
        }
    }
    
    render() {
    	return(
        	<div className="App">
            	<Subject title={this.state.subject.title} sub={this.state.subject.sub}></Subject>
                <TOC></TOC>
                <Content title="HTML" desc="HTML is HyperText Markup Language"></Content>
            </div>
        );
    }
}

export default App;

App 컴포넌트에 constructor를 쓰고, state를 쓰기 위해서는 위처럼 작성해주면 된다.

위의 형태를 잠깐 보자면, constructor 함수를 쓰고 props를 Component에서 super()을 통해 받아오고 있다. 그리고 this는 App을 가르키며 state을 객체의 형태로 키와 값으로 작성해 subject라는 키에 title과 sub가 담긴 객체를 다시 값으로 지정해주었다.

어떤 컴포넌트가 실행될때, render 이라는 함수가 먼저 실행되기 전에 해당 컴포넌트를 초기화하고 싶다면 constructor에 작성한다. 그래서 컴포넌트가 실행 될 때 constructor 함수가 가장 먼저 실행되어 초기화를 담당한다. 그리고 state값으로 컴포넌트의 props 값을 설정한다.

App 컴포넌트 내부에 state를 써서 필요한 값들을 쓰고, 마치 전선이 내부에 있는것처럼 지정해 주었다. 그리고 그 state를 가지고 사용자가 props를 이용해서 우리가 눈으로 확인할 수 있도록 값을 지정해 보여주는 것을 알 수 있다.

위를 보면 Subject 컴포넌트의 props인 title값을 {this.state.subject.title}과 같이 작성한 것을 알 수 있다. 리액트에서는 따옴표로 묶으면 그것을 문자열로 취급하는데, 위와 같이 중괄호( {} )로 묶으면 자바스크립트 코드로 취급한다. 위 코드에서 Subject 컴포넌트를 보면 props 중 하나인 title의 값을 { }로 지정해준 것을 확인할 수 있다.

key props

: state를 좀 더 사용해보자.
위에서 살펴본 state는 subject라는 프로퍼티에 값하나로 구성되어 있다. 하지만 여러 개의 값을 다룰 때는 사용법이 조금 달라진다.
TOC 컴포넌트를 보면, 목록들을 여러개 넣어서 화면에 보여줘야 하는 것을 볼 수 있다. TOC 안의 목록 데이터를 App 컴포넌트에서 주입하는 방식으로, 자동으로 데이터가 바뀌게 만들어 보자.

// App.js 파일

import React, { Component } from 'react';
import TOC from './components/TOC';
import Content from './components/Content';
import Subject from './components/Subject';
import './App.css';

class App extends Component {
	constructor(props) {
    	super(props);
        this.state = {
        	subject: {title:'WEB', sub:'World Wide Web!'},
            contents: [
            	{id:1, title:'HTML', desc:'HTML is for information'}, 
            	{id:2, title:'CSS', desc:'CSS is for design'},
            	{id:3, title:'JavaScript', desc:'JavaScript is for interactive'}
            ]
        }
    }
    
    render(){
    	return(
        	<div className="App">
            	<Subject title={this.state.subject.title} sub={this.state.subject.sub}></Subject>
                <TOC data={this.state.contents}></TOC>
                <Content title="HTML" desc="HTML is HyperText Markup Language."></Content>
            </div>
        );
    }
}

위처럼 작성한다. state에 contents 라는 프로퍼티를 추가했고, 목록은 데이터가 여러개라서 대괄호로 '배열'을 만들었다. 그리고 data라는 이름의 props에 this.state.contents를 주입하였다.

그렇다면 TOC컴포넌트는 어떻게 수정하면 될까?

// TOC.js 파일

import React, {Component} from 'react';

class TOC extends Component {
	render() {
    	let lists = [];
        const data = this.props.data;
        for(let i = 0; i < data.length < i++) {
        	lists.push(<li key={data[i].id}><a href={"/content/" + data[i].id}>{data[i].title}</a></li>);
        }
        return(
            <nav>
                <ul>
                    {lists}
                </ul>
            </nav>
        );
    }
}

export default TOC;

컴포넌트는 내부적으로 this.props.data를 가지고 있다. TOC 컴포넌트에 이 data를 가지고 변수를 지정해서 여러개의 목록들을 표시 할 수 있도록 반복문을 작성했다. 여기서 반복문은 this.props.data 배열의 길이값만큼 반복해서 lists의 push함수가 실행된다.
추가되는 각 항목은 a태그를 사용해 링크를 구성하고, href값은 "/content/"뒤에 data의 id값을 추가한 문자열로 지정한다.
마지막으로, ul내에 {list} 배열을 그대로 갖다 놓고 저장하면 끝!!!

하지만 주의할 것이 있다. 여러개의 엘리멘트를 자동으로 생성하는 경우에는 에러가 발생할 수 있으니 '각 리스트 항목은 키라고 하는 prop을 가져야 한다.' 즉, 여러개의 항목으로 구성된 목록을 자동으로 생성할때는 각 항목을 서로 구분할 수 있는 식별자를 지정해야 한다. 그래서 li엘리멘트 안에 key={data[i].id}가 쓰여 있는 것을 확인할 수 있을 것이다.

💡 정리

부모인 App 컴포넌트입장에서는 state라는 내부 정보를 사용하였고, 그것을 자식 컴포넌트에 전달할 때는 props를 이용하였다.

오늘 작성한 코드로 예를 들면, App 컴포넌트 입장에서는 TOC 컴포넌트가 내부적으로 어떻게 동작하는지 알 필요가 없기 때문에 data라는 props로 정보를 전달한다.

<TOC data={this.state.contents}>

이처럼 우리가 어떤 제품을 사용할 때 내부적으로 무슨일이 일어나고 있는지 모르고 단지 사용만 잘 하면 되듯이, 컴포넌트도 리액트에서 사용될때 이렇게 내부적인것은 꽁꽁 숨기고 외부적인 것을 이용하는 방법을 사용하는 것이 좋다.

번외_
공부를 하다가 든 생각인데, 이것은 마치 내가 c++을 배울때와 비슷한 원리인것 같다. 모두 객체지향이며 클래스를 사용해서 그런지 몰라도 비슷한 점이 많은 것 같다:)


출처: 생활코딩! React 프로그래밍 책

profile
프론트엔드개발자가 되고 싶어서 열심히 땅굴 파는 자

1개의 댓글

comment-user-thumbnail
2023년 8월 14일

글 재미있게 봤습니다.

답글 달기