서론
최근 SSR 을 연구하는 과정에서, Next.js 에 대해 더 많은 인식을 얻었습니다:
-
전면 소개: Next.js
-
핵심 특성: [Next.js 혼합 렌더링](/articles/next-js 混合 렌더링/)
-
디자인 기법: 본고
본고는 Next.js 시리즈의 세 번째 글 (그리고 마지막 글) 로, 거기서 발견한 디자인 기법 (API 디자인, 문서 디자인, 프레임워크 디자인 등) 을 기록하고 당신과도 공유합니다
기본 클래스를 정의하는 것보다 모듈을 정의하는 것이 나을 수 있음
먼저, 클래스 (Class) 와 모듈 (Module) 은 모두 코드를 조직화하는 선택 방식이며, API 디자인 시나리오에 놓이면, 모두 작성 방식을 제약하고 프레임워크 능력을 노출하는 데 사용될 수 있습니다. 모듈 개념이 정통이 되기之前, 프론트엔드 프레임워크 대부분은 이 필요를 충족시키기 위해 기본 클래스를 제공했습니다. 선택지가 없었기 때문입니다
전형적으로, React 는 React.Component 기본 클래스를 통해 다양한 라이프사이클 Hook 을 노출하고, 동시에 컴포넌트 작성 방식을 정의합니다:
// Components
class Clock extends React.Component {
// Props
constructor(props) {
super(props);
// State
this.state = {date: new Date()};
}
// Lifecycle
componentDidMount() { }
componentWillUnmount() { }
render() {
// Template
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Props, State, Lifecycle, Template 등의 프레임워크 능력을 하나의 Class 에 통합하여 컴포넌트라고 합니다. 그리고, 긴 시간 동안, React 에서 컴포넌트라고 불릴 수 있는 것은 Class 뿐이었습니다
이 긴 시간은 얼마나 긴가?
React 탄생之初부터 React Hooks 가 출시되고 완전 형태로 진화할 때까지입니다. 현재 (2021/1/2) React Hooks 는 아직 완전 형태가 아니며, componentDidCatch, getSnapshotBeforeUpdate, getDerivedStateFromError 등의 특성은 아직 건전하지 않습니다. 상세는 Do Hooks cover all use cases for classes? 참조
즉, 오늘날까지, React Components 는 여전히 Class Components 와 동등하며, 초기의 함수형 컴포넌트는 Stateless Components 라고만 불릴 수 있었고, Hooks 의 지원을 얻은 후의 함수형 컴포넌트는 Stateless 에서 벗어났지만, 완전 형태의 Class Components 와는 아직 조금差距가 있습니다
Components 개념을 Class 와 강하게 묶는 것은 정말 끔찍한 선택이었습니다. 기대를 받은 Hooks 가 이 점을 충분히 설명합니다. 그러나 Props, State, Lifecycle, Template 등의 프레임워크 능력은 무언가로 담당해야 합니다. 그렇다면, 더 나은 선택은 무엇일까요?
아마 Module 일 것입니다. 아마라고 강조하는 것은, 코드를 조직화한다는 점에서만, Module 이 Class 보다 더 순수하기 때문입니다. Module 은 코드만 조직화하며, 변수, 함수 등의 구문 요소를 함께 둘러쌀 뿐, Class 처럼 인스턴스 상태, 멤버 메서드 등의 추가 개념을 강요하지 않습니다
예를 들어, Next.js 의 Page 정의는 그저 파일 모듈일 뿐입니다:
// pages/about.js
function About() {
return <div>About</div>
}
export default About
가장 간단한 Page 는, 기본적으로 React 컴포넌트 하나를 노출하기만 하면 됩니다. 더 많은 기능이 필요하면, 추가로按需로 더 많은 기정 API 를 노출합니다:
// pages/blog.js
function Blog({ posts }) {
// Render posts...
}
// API 1
export async function getStaticProps() { }
// API 2
export async function getStaticPaths() { }
// API 3
export async function getServerSideProps() { }
// API n
export async function xxx() { }
export default Blog
Class 형태의 API 디자인과 비교하여, 이Module 식 API 디자인은 더 순수하며, 추가 구문 요소 (특히 Class 와 같은根基庞大한 구문 요소, 일련의 super(), bind(this), static 을 가져오는) 를 강요하지 않아, 어떤 시나리오에서는 더 나은 선택이라고 할 수 있습니다
파일 약정 루트
Next.js 에는 Router.register 도 new Route() 도 app.use() 도, 생각할 수 있는 모든 루트 정의 API 가 존재하지 않습니다
API 가 전혀 없기 때문에, 루트는 파일 경로 약정을 채택합니다:
// 정적 루트
pages/index.js → /
pages/blog/index.js → /blog
pages/blog/first-post.js → /blog/first-post
pages/dashboard/settings/username.js → /dashboard/settings/username
// 동적 루트
pages/blog/[slug].js → /blog/:slug (/blog/hello-world)
pages/[username]/settings.js → /:username/settings (/foo/settings)
pages/post/[...all].js → /post/* (/post/2020/id/title)
즉, 소스 코드가所在한 파일 경로를 통해 루트를 식별하며, 심지어 와일드카드도 지원할 수 있습니다. 이렇게 신기하니, 물론 소스 코드 디렉토리를 직접 눈으로 봐야 시각적 충격을 느낄 수 있습니다:
pages
├── _app.js
├── _document.tsx
├── api
│?? ├── collection
│?? │?? ├── [id].tsx
│?? │?? └── index.tsx
│?? ├── photo
│?? │?? ├── [id].tsx
│?? │?? ├── download
│?? │?? │?? └── [id].tsx
│?? │?? └── index.tsx
│?? ├── stats
│?? │?? └── index.tsx
│?? └── user
│?? └── index.tsx
├── collection
│?? └── [slug].tsx
└── index.tsx
API 간의 매끄러운 연동
앞의 두 글을 통해, Next.js 가 해결하려는 문제는 프리렌더링이며, 프리렌더링을 둘러싸고 SSG, SSR 의 두 가지 렌더링 모드를 탐색하고, 이를 기반으로 CSR 을 포함한 다른 렌더링 모드의 혼용을 지원한다는 것을 알 수 있습니다:
- ISR(Incremental Static Regeneration): 증분 정적 재생성, 런타임에서 정기적으로 정적 HTML 재생성
- SSG 降级 SSR: 미리 생성된 정적 HTML 에 미스했을 때, 즉시 SSR 수행
- SSR 帯정적 캐시:SSR 완료 후, 결과를 캐시하고, 다음 정적 캐시 적중 시 직접 반환 (SSG 에 상당)
- SSG 결합 CSR: 컴파일 시 정적 부분 (페이지 외곽) 생성, CSR 로 동적 부분 (페이지 내용) 충전
- SSR 연동 CSR:URL 직접 액세스는 더 빠른 SSR, SPA 점프는 더 체험이 우수한 CSR
API 디자인의 각도에서 얼핏 보면, 각 조합에 별致的인 이름을 붙이고 전문 API 를 노출해야 할 것 같습니다. SSGwithFallback, SSRwithStaticCache, PartialSSG, SPAMode 처럼...
그러나, Next.js 는 이 모든 혼용 특성을 지원할 뿐만 아니라,顶层 API 를 전혀 추가하지 않았습니다. 그做法는 몇 가지 옵션을 추가하는 것입니다. 예를 들어:
// SSG 기본형
export async function getStaticProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}
// SSG 변신 ISR, 반환값에 revalidate 속성 추가
export async function getStaticProps(context) {
return {
props: {}, // will be passed to the page component as props
// Next.js will attempt to re-generate the page:
// - When a request comes in
// - At most once every second
revalidate: 1, // In seconds
}
}
// SSG感知루트의 고급형, getStaticPaths 구현
export async function getStaticPaths() {
return {
paths: [
{ params: { ... } } // See the "paths" section below
],
fallback: false
};
}
// SSG 변신 SSR 帯정적 캐시, fallback 옵션을 true 로 변경
export async function getStaticPaths() {
return {
paths: [
{ params: { ... } } // See the "paths" section below
],
fallback: true
};
}
// SSG 변신 SSG 降级 SSR, fallback 옵션을'blocking'으로 변경
export async function getStaticPaths() {
return {
paths: [
{ params: { ... } } // See the "paths" section below
],
fallback: 'blocking'
};
}
이세분 옵션 기반의 API 연동은 더 가볍게 사용되며, 항상 사용자에게 점진적인 체감을 가져다주고, 처음부터 모든 API, 관련 디자인 개념을 이해할 필요가 없으며,顶层에서 내 시나리오가 어떤 유형에 속하는지 구분하고 어떤 API 를 사용해야 하는지가 아니라, 시나리오의 심화와 함께 가장 적합한 API/옵션이 거기에 있다는 것을 발견합니다
문서에서 확연히 이 차이를 느낄 수 있습니다. 예를 들어, Next.js 는 ISR 을 소개하는 곳에서 사용자를 관련 SSR 帯정적 캐시 모드로 유도합니다:
Incremental Static Regeneration
With getStaticProps you don't have to stop relying on dynamic content, as static content can also be dynamic. Incremental Static Regeneration allows you to update existing pages by re-rendering them in the background as traffic comes in.
This works perfectly with fallback: true. Because now you can have a list of posts that's always up to date with the latest posts, and have a blog post page that generates blog posts on-demand, no matter how many posts you add or update.
포인트, 인터랙티브 초보자 튜토리얼
이 점은 문서 디자인 기법으로 볼 수 있습니다 (문서도 물론 디자인이 필요합니다). 많은 공식 문서/튜토리얼을 보았지만, 깊은 인상을 남긴 것은 단 3 개뿐입니다:
-
Redux 문서: 이야기성 문서,手把手로 조금씩 redux 를 설계해 나가며, 읽다 보면 전혀 멈출 수 없습니다
-
Electron Demo App: 인터랙티브 문서, 정확히는 완전 문서가 있는 Demo 로, Demo App 을 체험하면서 관련 특성 사용법을 이해합니다. React 在做中学 보다 더 게으른 방법입니다
-
Next.js 튜토리얼: 포인트, 인터랙티브 초보자 튜토리얼, 수십 페이지의 튜토리얼을 한숨에 다 읽습니다
P.S.Redux 문서는 2017 년 버전 을指하며, 현재는 많은 버전으로 변경되어 읽기가 매우 나빠졌습니다 (이만큼의 개념을 어떻게 그렇게 많은 문서로 만들 수 있는지)
포인트, 인터랙티브 초보자 튜토리얼의 위력은 어느 정도인가?
졸려서迷糊한 상태에서도 튜토리얼의 전 내용을 다 읽을 수 있었고, 모든 테스트 문제에 정답했으며, 500 포인트를 가득 채울 수 있었습니다 (물론, 환상하지 마십시오. 전정답이라도 아무 보상도 없습니다).事后回想해도 불가사의하게 느껴집니다. 그 기법은 다음과 같습니다:
-
튜토리얼과 문서 분리: 네비게이션바 1 급 메뉴에서 Docs 와 Learn 을 명확히 구분. 튜토리얼 중의 부분 개념은 문서로의 링크가 있지만, 완전히 보지 않아도 완전히 따라갈 수 있습니다
-
포인트: 튜토리얼의 눈에 띄는 위치에 획득 포인트를置顶展示하며, 한 편 클릭마다 점수 추가
-
인터랙티브: 중요 장절에 테스트 문제가 있으며, 정답해도 점수 추가. 총 포인트는 소셜 플랫폼 (Twitter) 에서 공유 가능
이렇게 보면, 문서에 소량의 온라인 교육의 성숙 모드를 융합시키는 것은, 효과가 극히 좋을 수 있습니다
기본적으로 베스트 프랙티스 제공
체험科技과 좋은 제품 을 읽고, 그 중 玉伯이 제시한기본적으로好用에 깊은 인상을 받았습니다. Next.js 는 기본적으로好用이 프레임워크 디자인상의 진실한 사례라고 할 수 있습니다
예를 들어:
-
"자동"으로最佳 렌더링 모드 채택: 이 자동은 앞의 두 개와 다르며, 프레임워크 각도에서 사용자의按需使用特性への응답을 강조하며, 프레임워크가 렌더링 모드 (SSR 인지 SSG 인지) 를 판단하고, 사용자가 명시적으로 지정/전환할 필요가 없습니다
생산 활동의 각도에서 보면, 베스트 프랙티스는 본래 기본적으로 제공되어야 합니다. 새로 나타난 베스트 프랙티스를不断에 환경 층으로下沉시킵니다. npm package, ES Module, Babel 등처럼, 현재의 프론트엔드 개발자는 이러한曾经的 베스트 프랙티스를 거의关心할 필요가 없어졌습니다
프레임워크 디자인 각도에서만 말하면, 기본적으로好用은 베스트 프랙티스를 제공하는基礎上에서 한 걸음 더 나아가, 베스트 프랙티스를 만들어 없애고, 사용자가 게으르게 모든 것이 본래如此하다고 생각할 수 있도록 해야 합니다. 따라서, 베스트 프랙티스는 단지 임시態일 뿐이며, 베스트 프랙티스가 형성되지 않은 부분이야말로 개발자가关心하고, 차별화 경쟁력을 체현하는 곳입니다. 일단 널리认同된 베스트 프랙티스가 형성되면, 기본적으로 기초 시설로沉淀해야 하며, 개발자는关心하지 않아도 이러한 베스트 프랙티스가 가져오는 다양한 이점을 얻을 수 있습니다
베스트 프랙티스가 형성되지 않은 단계에서, 베스트 프랙티스를 제공하는 단계, 기본적으로 베스트 프랙티스를 제공하는 단계까지의 3 단계는, 이미지 지연 로딩의 예를 통해 이해할 수 있습니다:
// 첫 번째 단계: 베스트 프랙티스가 형성되지 않음
scroll
IntersectionObserver
// 업무 각자 구현, 사용법 예시 존재하지 않음
// 두 번째 단계: 베스트 프랙티스 제공
React Lazy Load Component
// 사용법 예시
<LazyLoad height={683} offsetTop={200}>
<img src='http://apod.nasa.gov/apod/image/1502/2015_02_20_conj_bourque1024.jpg' />
</LazyLoad>
// 세 번째 단계: 기본적으로 베스트 프랙티스 제공
next/image
// 사용법 예시
<Image
src="/me.png"
alt="Picture of the author"
layout="fill"
/>
세 번째 단계와 두 번째 단계의 차이는, 개발자가 어떤 컴포넌트가 지연 로딩 기능을 제공할 수 있는지 (베스트 프랙티스 선택) 关心할 필요가 없고, 컴포넌트 라이브러리에서 가장普通の Image 컴포넌트를 직접 사용하기만 하면,该有的 기능은 자연스럽게 있으며, 지연 로딩은 그 중 한 항목일 뿐이라는 것입니다
Serverless 로 연장
Serverless 浪潮 아래, 프론트엔드 생태도 몇 가지 변화를 일으키고 있으며, 다양한일체화 애플리케이션이涌现하고 있습니다:
-
프론트엔드 프로젝트/백엔드 프로젝트를 주체로 하는 일체화 애플리케이션: Midway Serverless 처럼, React, Vue 등의 프론트엔드 프로젝트 통합을 지원
-
SSR 을 주체로 하는 일체화 애플리케이션: Next.js 처럼, SSR 과 데이터 인터페이스 (API endpoints) 를 Serverless Functions 로 배포하는 것을 지원
Next.js 는 SSR 지원을 제공하며, 본래 서버사이드 환경을 필요로 했습니다. Serverless 의興起는 SSR 렌더링 서비스의 운용 보수 문제를很好地에 해결했습니다. 따라서, 그 Vercel 플랫폼은 기본적으로 SSR 서비스와 API 를 Serverless Functions 형태로 배포하는 것을 지원합니다:
Pages that use Server-Side Rendering and API routes will automatically become isolated Serverless Functions. This allows page rendering and API requests to scale infinitely.
이러한 일체화 애플리케이션은 아직 베스트 프랙티스를 형성하지 않았지만, 전통적인 프론트엔드 프레임워크는 변혁을 겪고 있습니다. 아마, 미래의 어느 날에는, Serverless 기술과 충분히 융합된 일체화 애플리케이션 프레임워크로 대체되고, Universal 체계가 대行其道할지도 모릅니다
아직 댓글이 없습니다