[Study] 리액트 컴포넌트 구현

productuidev·2022년 6월 12일
0

React Study

목록 보기
49/52
post-thumbnail
post-custom-banner

도서 "더 괜찮은 웹 개발자가 되기 위한 리액트 스타일 가이드 (프런트엔드를 아우르는 사용자 중심의 모던 UI 컴포넌트 설계와 개발)"에서 UI 구현 + 프로젝트 구성 부분에 대해 학습한 내용 일부 정리+복습


UI 컴포넌트 구현

  • 컴포넌트에서는 재사용이 하나의 키워드
  • 재사용을 최대화하려면 각 컴포넌트가 가진 역할을 명확히 하는 것이 중요 (일관성)
  • 리덕스 아키텍처와의 친화성을 높일 수 있음
  • Presentational 컴포넌트와 Container 컴포넌트로 구분되면 각자가 맡은 일이 명확해지므로 구현도 간결하게 유지할 수 있음. 분리를 통해 컴포넌트의 재사용성을 향상하려는 목적이 있음

1) Presentational 컴포넌트

  • Presentational : 표현 화면 위에서 GUI로 표시되므로 구조적인 관점에서 역할을 나누어 구현하는 방법. 어떤 UI를 화면에 표시할 것인가?

Functional 컴포넌트 (SFC)

  • 항상 표현에만 집중. 즉, 화면 표시 내용이 달라져서는 안됨.
  • 특정 Props가 전달되었을 때 기대한 화면을 기대한 대로 반드시 표시해야 함
  • Functional인 컴포넌트로서 내용에 부작용이 없는 순수한 함수여야 한다는 점이 중요함. 부작용이 없다는 의미는 함수의 실행에 다른 전역 변수나 정적 변수들에 영향을 주지 않는다는 의미
  • Stateless Functional Components (SFC) : 상태가 없는, 내부적으로 State를 가지지 않는 함수형 컴포넌트.
  • Presentational 컴포넌트를 만들 때는 SFC로 구현할 것을 추천
import React from 'react'

const UserCard = (props) => {
	return (
    	<div>
        	<p>{props.name}</p>
            <p>{props.age}</p>
        </div>
    );
}

export default UserCard
  • Props의 필요 없이 단순한 고정 형태만 표시하는 컴포넌트에서도 마찬가지로 SFC로 구현하면 간결하게 컴포넌트화할 수 있음 (적은 양의 코드)
  • Props로 주고받는 데이터는 문자열 뿐만 아니라 함수도 가능 (콜백 함수)
import React from 'react'

const UserCard = (props) => {
	return (
    	<div>
        	<p>{props.name}</p>
            <button onClick={props.onClickFunction}>Click</button>
        </div>
    );
}

export default UserCard
import React from 'react'
import UserCard from './UserCard'

const UserCardWrapper = () => {
	return (
    	<div>
        	<Usercard name="taro" onClickFunction={()=>{console.log("UserCard")}} />
        </div>
    );
}

export default UserCardWrapper
  • 주의할 점 : Presentational 컴포넌트가 다른 요소의 상태에 의존해서는 안 된다. 컴포넌트가 알 수 없는 요소의 상태에 따라 동작이 바뀌는 것을 반드시 피해야 함. Props가 어디서 생겨 어떻게 전달되었는가를 Presentational 컴포넌트가 알아야 할 필요는 없음. 정해진 값을 전달하면 반드시 기대한 대로 동작한다는 것이 사용하기 쉬운 컴포넌트의 최대 특징

2) Container 컴포넌트

  • Container : 용기 (화물용) 컨테이너 표시를 제어할 용기
  • 간결함을 중시하는 Presentational 컴포넌트와 많이 다른 특수한 요소로, 일반적으로 Presentational 컴포넌트를 포함하고 대부분은 구체적인 DOM 표현을 직접 갖지는 않는다.
  • 화면 표시에 이용하는 구체적인 데이터나 동작을 자식 컴포넌트에 제공하는 컨트롤러와 같은 역할을 담당한다.

  • ItemContainer 컴포넌트 : 데이터를 가진 컨테이너
    ㄴ MainTitle 컴포넌트 : 제목 표시
    ㄴ ItemList 컴포넌트 : 실제 목록 출력 (props로 ItemContainer의 데이터를 전달 받음)

  • ItemContainer 컴포넌트는 데이터 관리만 수행하므로 실제로 어떤 UI가 표시될 것인가는 신경쓸 필요가 없음. UI 표시는 자식 컴포넌트가 담당하므로 데이터를 전달하기만 하면 이 컴포넌트의 역할은 끝남.

import React, { Component } from 'react';
import MainTitle from './MainTitle';
import ItemList from './ItemList';

class ItemContainer extends Component {
	constructor() {
    	super();
        
        // 자신이 상태를 저장
        this.state = {
        	items: ['Item 1', 'Item 2', 'Item 3', 'Item 4']
        }
        
        this.handleUpdateFlag = this.handleUpdateFlag.bind(this);
 	}
 
    handleUpdateFlag() {
      	this.state.items.push("AAA");
        this.setState({
            items: this.state.items
        });
    }
        
    render() {
    	// 자식 컴포넌트에 대해 데이터를 전달하기만 함
        return (
           <div>
      	   	 <MainTitle text="My Items Page" url="https://www.example.com" />
             <ItemList items={this.state.items} />
           </div>
        );
    }
}

export default ItemContainer
  • Container 컴포넌트에는 화면 표시 요소가 추상화되어 있다는 점에도 주목해야 함. 이전의 웹 페이지의 소스 코드에서는 HTML이나 CSS 코드가 복잡하게 얽혀 있으므로, 실제 어떤 요소를 화면에 표시하는가를 알려진 모든 코드를 읽고 이해해야 했지만 지금은 어떤 목적의 요소를 어떤 값으로 화면에 표시하는가가 간결하게 표현되며 각 요소의 구성 요소를 재빠르게 이해할 수 있습니다.

정리

Presentational 컴포넌트는 렌더링과 관련된 책임을 지고,
Container 컴포넌트는 데이터 구조나 데이터 흐름을 담당한다.

3) 데이터 반영

  • Container 컴포넌트를 이용하여 데이터를 집중적으로 관리하고자 할 때 의문 : 사용자가 버튼을 클릭하거나 폼에 입력할 때 이러한 이벤트로 생긴 변화는 어떻게 확인하여 다른 컴포넌트에 반영해야 할까?
  • Presentational 컴포넌트에 함수를 전달하여 UI에서 데이터 변경이 실현되도록 Container 컴포넌트에서 데이터 업데이트가 수행되도록 하는 것이 설계 포인트 중 하나
  • Container 쪽에서 데이터 변경 처리를 구현하면 Presentational 쪽에서는 전달받은 함수 실행만 담당하면 되므로 전달받은 함수가 무엇을 처리하는지는 신경쓰지 않아도 됨
  • 버킷 릴레이(Bucket Relay) : 컴포넌트의 의존 관계가 여러 층에 걸치게 되는 중첩 구조가 될 때는 이들 모두를 거쳐 이벤트 핸들러를 전달해야 함 (중간 규모 이상의 복잡해지기 쉬운 애플리케이션에서는 구현이 어려워지는 원인)
import React from 'react'

const ViewFlagValue = (props) => {
	// 전달받은 데이터를 이용하여 화면만 표시
	return <p>{props.flag ? '유효' : '무효'}</p>; 
}

export default ViewFlagValue
import React from 'react'

const FlagSwitch = () => {
	// 이 버튼을 클릭했을 때 Flag값을 바꾸고 싶음
	return <button>Switch Flag</button>
}

export default FlagSwitch
import React, { Component } from 'react'
import FlagSwitch from './FlagSwitch'
import VieFlagValue from './ViewFlagValue'

class SwitchContainer extends Component {
	constructor() {
    	super();
        this.state = { flag: flase }
    }
    
    render() {
    	return (
        	<div>
            	<FlagSwitch></FlagSwitch>
                <ViewFlagValue flag={this.state.flag}></ViewFlagValue>
            </div>
        )
    }
}

export default SwitchContainer
  • SwitchContainer는 state 값을 가지고 있고, FlagSwitch와 ViewFlagValue에 props로 flag 데이터를 반영한다.

4) Local State

  • Presentational 컴포넌트는 SFC로, State를 가지지않도록 구현할 것을 추천하지만 대량의 데이터를 주고받아야 할 상황이 있음.
  • Presentational 컴포넌트여도 상황에 따라 설계상, 컴포넌트 안에 데이터를 가둔 형태로 State를 갖는 것을 허용하는 것이 결과적으로는 전체 소스 코드를 더 잘 운영할 수 있게 한다. 이러한 State를 Local State라고 한다.

5) 컴포넌트 디렉토리

  • Presentational 안에서 아토믹 디자인에 따른 디렉토리 구조를 가지게 분류
    src/index.js
    src/container/header.js
    src/container/footer.js
    src/presentational/atoms/button.js
    src/presentational/atoms/checkbox.js
    src/presentational/molecules/seachbox.js
    src/presentational/molecules/sharebutton.js

  • Container 안에서 아토믹 디자인에 따라 역할 분담을 수행하도록 분류
    src/index.js
    src/container/organisms/header.js
    src/container/organisms/footer.js
    src/container/templates/top.js
    src/container/templates/404.js
    src/presentational/atoms/...
    src/presentational/molecules/...

  • 특정 처리를 modules 디렉토리로 뽑아내도록 분류
    src/index.js
    src/components/container/...
    src/components/presentational/...
    src/modules/...

  • 1 컴포넌트 1 디렉토리로 분류
    src/button/index.js
    src/button/button.css
    src/button/button.spec.js

SSR과 결합

서버사이드렌더링

SEO (Search Engine Optimization)

  • 검색엔진 최적화 : 검색 엔진에 인덱스 되고 자연 검색에 의해 유입된 사용자 수가 중요한 경우, 이에 영향이 생기는 건 서비스 자체 존속에도 매우 민감한 주제
  • 보통 검색 엔진에 등록되려면 크롤러라 불리는 웹상의 콘텐츠나 리소스를 수집하는 로봇 프로그램에 탐지되고 수집될 필요가 있음. 크롤러에 대한 최적 구현(높은 수집 능력)도 필요해지게 됨
  • 대규모의 트랙픽이 자연 검색을 통해 유입되는 서비스에서 크롤러의 성능만 믿는 것은 여전히 위험할 수 있음

초기 표시 성능

  • 서버 송신 후 최초로 돌아오는 HTML에 콘텐츠가 포함되어 있으면 브라우저는 바로 렌더링할 수 있는데, JS로 이용하여 DOM을 형성할 경우 스크립트 로딩이 끝난 후 실행되어 그때 렌더링됨. 사용자에게 초기 표시가 느리다, 무겁다 등의 인상을 주게 되므로 웹사이트 품질에 크게 영향을 준다. 복잡한 애플리케이션일수록 번들된 JS 파일도 커지는 경향이 있는데 이는 사용자 경험상 아주 중요한 초기 표시에 직접 영향을 준다. 클라이언트만으로 DOM을 생성할 때 생기는 유념할 점 중 하나이다.
  • 리액트는 서버에서도 가상적인 DOM을 렌더링할 수 있으므로 같은 컴포넌트 리소스를 그대로 이용하여 서버 응답 시에 렌더링 처리를 실행하도록 한다.

Next.js

  • SSR을 더욱 간단히 실현하고자 할 때는 Next.js라 불리는 프레임워크를 활용할 것을 추천한다.
  • SSR을 전제로 한 리액트 애플리케이션 구현 프레임워크로, 리액트를 쉽게 구현할 수 있게 도와주는 간단한 프레임워크이다.
  • 코드 스플리팅, 간단한 클라이언트 사이드 라우팅, 커스텀 서버를 통해서 라우트를 마스킹 등이 가능하다.

학습자료


💬 컴포넌트의 상태 관리(리덕스) 부분은 다른 프로젝트를 실습해보고 읽어볼 예정이다.

profile
필요한 내용을 공부하고 저장합니다.
post-custom-banner

0개의 댓글