1장. 프레임워크에 대한 이야기
프레임워크는 필요 없다. 중요한 것은 그림이지 프레임이 아니다 - 클라우스 킨스키
💡 1장에서는 프레임워크에 대한 저자의 생각과 프레임워크 없이 개발하는 방법을 배우는 것이 왜 중요한지에 대한 저자의 믿음으로 시작한다. 프레임워크 없이 작업하는 방법을 배우고 나면 이 방법이 자신의 프로젝트에 적합한 선택인지 알 수 있게 될 것이다.
프레임워크란?
무언가를 만들 수 있는 지지 구조 - 캠브리지 사전의 정의
프레임워크는 특정 문제를 해결하기 위한 뼈대 또는 틀을 제공하는 구조를 말합니다. - GPT
- 더 자세히 설명하면:
예를 들어:
- 미리 만들어진 구성 요소와 규칙을 제공하여 개발자가 복잡한 작업을 효율적으로 수행할 수 있도록 돕는 소프트웨어 환경입니다.
- 재사용 가능한 코드와 구조를 제공하여 개발 시간을 단축하고 코드 품질을 향상시킵니다.
- 일관성과 표준화를 유지하여 팀 개발을 용이하게 합니다.
프레임워크를 사용하면 다음과 같은 이점이 있습니다.
- 웹 개발 프레임워크: Spring, Django, Ruby on Rails 등은 웹 애플리케이션 개발에 필요한 기본적인 구조와 기능을 제공합니다.
- 모바일 개발 프레임워크: React Native, Flutter 등은 모바일 앱 개발에 필요한 UI 구성 요소, 네트워킹 기능, 데이터베이스 연동 등을 제공합니다.
- 데이터 분석 프레임워크: Pandas, Scikit-learn 등은 데이터 분석에 필요한 데이터 처리, 머신러닝 알고리즘, 시각화 도구 등을 제공합니다.
결론적으로 프레임워크는 개발자가 더 빠르고 효율적으로 소프트웨어를 개발할 수 있도록 돕는 필수적인 도구입니다.
- 개발 시간 단축: 미리 만들어진 구성 요소를 사용하여 개발 시간을 절약할 수 있습니다.
- 코드 품질 향상: 일관성과 표준화를 유지하여 코드 품질을 향상시킬 수 있습니다.
- 유지보수 편의성: 코드 구조가 일관성 있게 구성되어 유지보수가 편리합니다.
- 팀 개발 효율성 증대: 팀원 간의 협업을 용이하게 합니다.
프레임워크와 라이브러리의 비교
프레임워크
- 앵귤러: 서비스, 구성 요소와 파이프 같은 기본 요소를 사용해 애플리케이션을 빌드하는 데 필요한 구조를 제공한다.
라이브러리
- 로대쉬(Loadash)와 모멘트 같이(Moment) 애플리케이션 코드를 어떻게 구성해야 하는지에 대해 특별한 형식을 요구하지 않는다.
프레임워크와 라이브러리의 차이: 프레임워크는 코드를 호출한다. 코드는 라이브러리를 호출한다. 프레임워크는 내부적으로 하나 이상의 라이브러리를 사용할 수 있지만 개발자가 모듈식 프레임워크를 선택하면 프레임워크를 단일 단위나 여러 모듈로 보는 개발자에게는 이러한 사실이 숨겨진다.
💡 그렇다면 React는 라이브러리일까? 아니면 프레임워크일까? 정답은 아래에 😆😆
프레임워크 방식
Moment.js같은 라이브러리는 개발자가 어떻게 코드에 통합하는지 강요하지 않는다. 이에 반해 앵귤러는 매우 독선적이다. 다음 절에서는 몇 가지 제약 조건을 알아본다.
1. 언어
앵귤러는 TypeScript를 사용한다. TypeScript는 일반 자바스크립트 컴파일 되는 자바스크립트의 형식화된 슈퍼 세트다. 유형 검사 외에도 어노테이션 같은 원본 언어에서는 존재하지 않는 여러 기능을 제공한다.
2. 의존성 주입
요소가 앵귤러 애플리케이션에서 통신할 수 있게 하려면 유형에 따라 의존성 주입 메커니즘을 사용해 요소를 주입해야 한다.
💡 의존성 주입(Dependency Injection, DI)은 애플리케이션의 클래스들이 자신의 의존성을 직접 생성하지 않고 외부에서 제공받는 설계 패턴이다. 이 방식은 코드의 재사용성, 테스트 용이성, 유지보수성을 크게 향상시킨다. - GPT
앵귤러 의존성 주입 이해하기
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root' // root 인젝터에 등록되어 애플리케이션 전역에서 사용 가능
})
export class DataService {
constructor() { }
getData() {
return ['Data1', 'Data2', 'Data3'];
}
}
서비스 정의 및 데코레이션
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-data',
templateUrl: './data.component.html',
styleUrls: ['./data.component.css']
})
export class DataComponent implements OnInit {
data: string[];
// DataService 의존성을 주입
constructor(private dataService: DataService) { }
ngOnInit() {
this.data = this.dataService.getData();
}
}
의존성 주입
// 특정 모듈에서만 서비스 제공
@NgModule({
providers: [DataService]
})
export class DataModule { }
의존성 등록
3. 옵저버블
앵귤러는 옵저버블(observable)을 사용한 반응형 프로그래밍용 라이브러리인 RxJS를 기반으로 설계됐다. 그래서 데이터를 가져오러면 Observable객체의 subscribe메서드를 사용해야된다. 이 접근 방식은 HTTP 요청이 프라미스 처럼 설계되는 다른 프론트엔드 프레임워크와 다르다
옵저버블을 사용하지 않는 앵귤러 서비스와 구성 요소
// 1-5) 옵저버블을 사용하지 않은 앵귤러 서비스
import axios from 'axios';
const URL = '<http://example.api.com/>';
export default {
list() {
return axios.get(URL);
}
}
// 1-6) 옵저버블을 사용하지 않는 앵귤러 구성 요소
import people from 'people.js';
export class PeopleList {
load() {
people
.list()
.then(people => {
this.people = people
});
}
}
옵저버블을 사용한 앵규러의 데이터 통신 방식
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataService {
private apiUrl = '<https://jsonplaceholder.typicode.com/posts>';
constructor(private http: HttpClient) { }
getPosts(): Observable {
return this.http.get(this.apiUrl);
}
}
서비스 생성
import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service';
@Component({
selector: 'app-posts',
templateUrl: './posts.component.html',
styleUrls: ['./posts.component.css']
})
export class PostsComponent implements OnInit {
posts: any[] = [];
constructor(private dataService: DataService) { }
ngOnInit(): void {
this.dataService.getPosts().subscribe(
data => this.posts = data,
error => console.error('There was an error!', error)
);
}
}
컴포넌트에서 서비스 사용
<div *ngIf="posts.length > 0">
<h2>Posts</h2>
<ul>
<li *ngFor="let post of posts">
{{ post.title }}
</li>
</ul>
</div>
<div *ngIf="posts.length === 0">
<p>No posts available.</p>
</div>
HTML 템플릿 설정
리액트에 대해 이야기해보자
리액트 홈페이지에서 리액트는 ‘사용자 인터페이스 구축을 위한 자바스크립트 라이브러리’라고 정의돼 있다.
그러나 현실은 이보다 훨씬 더 복잡하다!
리액트의 주요 제약 사항
- 선언적 패러다임을 사용한다.
- DOM을 직접 조작하는 대신 구성 요소의 상태를 수정한다.
선언적 패턴으로 PoseExample 컴포넌트 구현
import React, { useState } from 'react';
import posed from 'react-pose';
const Box = posed.div({
hidden: { opacity: 0 },
visible: { opacity: 1 },
transition: {
ease: 'linear',
duration: 500
}
});
const PoseExample = () => {
const [isVisible, setIsVisible] = useState(true);
const toggle = () => {
setIsVisible(!isVisible);
};
const pose = isVisible ? 'visible' : 'hidden';
return (
<div>
<Box className='box' pose={pose} />
<button onClick={toggle}>Toggle</button>
</div>
);
};
export default PoseExample;
PoseExample 컴포넌트
명령형패턴으로 PoseExample 컴포넌트 구현
const PoseExample = () => {
const [isVisible, setIsVisible] = useState(true);
const boxRef = useRef(null);
useEffect(() => {
const box = boxRef.current;
if (isVisible) {
box.style.opacity = 1;
} else {
box.style.opacity = 0;
}
box.style.transition = 'opacity 0.5s linear';
}, [isVisible]);
const toggle = () => {
setIsVisible(!isVisible);
};
return (
<div>
<div ref={boxRef} className='box' />
<button onClick={toggle}>Toggle</button>
</div>
);
};
export default PoseExample;
// Box.css
.box {
width: 100px;
height: 100px;
background-color: red;
opacity: 1; /* 초기 상태 */
transition: opacity 0.5s linear; /* 트랜지션 정의 */
}
💡 리액트 개발자라면 명령형 패턴으로 PoseExample 컴포넌트를 구현 한 예제가 매우 이상하게 보일 것이다. 명령형으로 Box를 움직이기 때문이다! 이 ‘기묘함(strangeness)’ 이 리액트는 라이브러리가 아닌 프레임워크라고 저자가 믿는 이유다. 저자는 이것이 코드 때문이 아니라 리액트 커뮤니티에서 이를 사용할 때 수용한 제약 사항 때문이라고 믿는다. 다시 말해 선언적 패턴은 리액트 방식의 일부이다.
팁: 작업을 처리할 때 ‘프레임워크 방식’을 사용하고 있다면 프레임워크라고 볼 수 있다.
자바스크립트 프레임워크 연혁
제이쿼리
2006년 존 레식이 만든 제이쿼리는 모든 자바스크립트 프레임워크의 모체가 됐다. 오늘날 프론트엔드 개발자들이 제이쿼리를 우습게 여기는 경향이 있지만 제이쿼리는 현대 웹 개발의 초석 역할을 했다.
<aside> 💡 사실 jQuery로 만든 레거시 프로젝트가 너무 많기에 아직까지도 굳건히 순위 안에 들고 있다.
Stack OverflowStack Overflow Developer Survey 2023
앵귤러JS
앵귤러JS는 2009년 미스코 헤브리에 의해 부수적인 프로젝트로 개발됐다. 나중에 그는 구글의 직원이 됐고 이런 이유로 앵귤러JS는 지금은 구글 엔지니어가 관리한다. 1.0 버전은 2011년 5월에 릴리스됐다. 앵귤러JS는 단일 페이지 애플리케이션을 주류로 만드는 데 큰 역할을 했다.
가장 주목할 만한 기능은 양방향 데이터 바인딩이다.
양방향 데이터 바인딩 덕분에 개발자는 웹 애플리케이션을 빠르게 작성할 수 있게 됐다. 그러나 양방향 데이터 바인딩이 대규모 애플리케이션에는 적합하지 않기 때문에 시간이 지나자 많은 개발자가 앵귤러JS를 떠났다.
💡 구글은 앵귤러JS의 단점을 보완하기 위해 앵귤러(앵귤러2)를 출시 했지만 앵귤러JS로 생긴 부정적인 선입견과 아키텍처의 대규모 변화 그리고 높은 러닝커브로 많은 웹개발자들이 Angular대신 React나 Vue를 선택했다. 그러나 구글은 6개월에 한번씩 메이저 버전을 발표하는등 매우 부지런한 행보를 보이고 있다.
릴리즈 정책
리액트
2011년 페이스북에서 만들어 2013년 오픈소스로 공개한 리액트는 현재 가낭 인기 있는 프론트엔드 라이브러리다. 리액트는 선언적 패러다임으로 동작한다. 일반적으로 DOM을 직접 수정하는 대신 setState 메서드로 상태를 변경하면 리액트가 나머지 작업을 수행한다.
기술 부채
프레임워크 비용
- 미래에 코드 변경이 어렵다는 측면에서 보면 모든 프레임워크가 기술 부채를 갖고있다.
- 프레임워크는 아키텍처 자체에 이미 비용을 포함하고 있다.
- 시간이 지남에 따라 시장이나 다른 요인으로 인해 소프트웨어의 변경이 필요하며, 아키텍처 역시 변경돼야한다. 대부분의 경우 프레임워크는 이런 변경이 필요한 상황에서 장애물이 된다.
기술 투자
- 저자는 프레임워크를 반대하는 것이 이책의 목적은 아니라고 말했다.
- 기술 부채가 항상 나쁜 것만은 아니다. 중요한 것은 부채 자체가 아니라 부채가 필요한 이유다.
- 합당한 이유로 선정된 프레임워크는 비용이 아니라 자산이다.
- 8장에서는 프레임워크가 프로젝트의 자산이 될지 판단하는 데 사용하는 몇 가지 기술적 방법과 ‘비용’이 적게 드는 프레임워클 선택하는 방법을 알아본다.
💡NextJS는 프레임워크 일까?
NextJS 공식 문서는 웹용 React 프레임워크로 소개하고 있다. 차근차근 NextJS의 제약 조건을 알아보자.
제약 조건(app router 기준)
1. Routing
Next.js 13에서 소개된 app router는 레이아웃, 중첩 라우팅, 로딩 상태, 오류 처리 등을 지원하는 서버 컴포넌트 위에 구축된 파일 시스템 기반 라우터다. 이를 사용하기위해 우리는 NextJS에서 설정한 폴더 구조를 엄격하게 따라야한다
경로의 각 폴더는 경로 세그먼트를 타나낸다. 각 경로 세그먼트는 URL 경로의 해당 세그먼트에 매핑된다.
2. Rendering
NextJS app router는 서버와 클라이언트 컴포넌트를 모두 지원하기 때문에 서버측에서 작동되어야하는 코드와 클라이언트 측에서 작동되어야하는 코드가 구분되어야한다. 이를 해결하기 위해 NextJS는 "use client" , “use server"지시어를 사용한다.
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
클라이언트 컴포넌트 예시
그리고 아래와 같이서버와 클라이언트 컴포넌트를 사용할 때 권장되는 몇 가지 구성 패턴을 다룬다
What do you need to do? | Server Component | Client Component |
Fetch data | O | X |
Access backend resources (directly) | O | X |
Keep sensitive information on the server (access tokens, API keys, etc) | O | X |
Keep large dependencies on the server / Reduce client-side JavaScript | O | X |
Add interactivity and event listeners (onClick(), onChange(), etc) | X | O |
Use State and Lifecycle Effects (useState(), useReducer(), useEffect(), etc) | X | O |
Use browser-only APIs | X | O |
Use custom hooks that depend on state, effects, or browser-only APIs | X | O |
Use https://react.dev/reference/react/Component | X | O |
3. Data Fetching
서버 컴포넌트에서 비동기/대기 기능을 통해 데이터 가져오기를 간소화하고 요청 메모화, 데이터 캐싱 및 재검증을 위한 확장된 가져오기 API를 제공한다.
Next.js는 네이티브 fetch Web API를 확장하여 서버에서 각 불러오기 요청에 대한 캐싱 및 재검증 동작을 구성할 수 있다.
async function getData() {
const res = await fetch('<https://api.example.com/>...')
// The return value is *not* serialized
// You can return Date, Map, Set, etc.
if (!res.ok) {
// This will activate the closest `error.js` Error Boundary
throw new Error('Failed to fetch data')
}
return res.json()
}
export default async function Page() {
const data = await getData()
return
}
4. Optimizations
Next.js에는 애플리케이션의 속도와 핵심 Web Vitals을 개선하기 위해 설계된 다양한 최적화 기능이 내장되어 있다. 대표적인 예로 Next.js 이미지 컴포넌트는 자동 이미지 최적화를 위한 기능으로 HTML <img> 요소를 확장한 Image 컴포넌트 사용을 권장한다.
import Image from 'next/image'
import profilePic from './me.png'
export default function Page() {
return (
<Image
src={profilePic}
alt="Picture of the author"
// width={500} automatically provided
// height={500} automatically provided
// blurDataURL="data:..." automatically provided
// placeholder="blur" // Optional blur-up while loading
/>
)
}
💡 저자의 시점에서 본다면 NextJS는 프로젝트 시작부터 끝까지 강한 제약들이 존재한다. 웹 개발의 선두를 달리고 있지만 과연 5년뒤에도 NextJS가 추구하는 아키텍처, 폴더 구조들이 변하지 않는다고 장담 할 수 있을까? 지금까지 나는 프로젝트 시작부터 매우 큰 부채를 떠안고 시작한게 아닌가 싶다. 앞으로 스터디를 진행하면서 우리가 사용하는 프레임워크가 프로젝트의 자산이 될지 판단하는 방법을 터득할 생각에 기대가 된다.
참고:
'독서' 카테고리의 다른 글
맥킨지는 일하는 방식이 다르다 - 에단 라지엘 (0) | 2024.05.05 |
---|---|
소프트 스킬 - 존 손메즈 (0) | 2024.03.30 |
테크 커리어 - 돈 존스 (0) | 2024.03.30 |
[이번 생은 망했다 다음 생에나 책 읽자] 오독오독 책 씹어 먹는 청춘 5인의 42가지 제안 - 요약 & 독후감 (2) | 2023.10.02 |
[어른의 문답법] - 독후감 (1) | 2023.09.30 |