掃二維碼與項(xiàng)目經(jīng)理溝通
我們在微信上24小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
本文轉(zhuǎn)載自微信公眾號「前端向后」,作者黯羽輕揚(yáng) 。轉(zhuǎn)載本文請聯(lián)系前端向后公眾號。

本文記錄了我從中發(fā)現(xiàn)的設(shè)計(jì)技巧,包括 API 設(shè)計(jì)、文檔設(shè)計(jì)、框架設(shè)計(jì)等,也分享給你
定義基類,可能不如定義模塊
首先,類(Class)和模塊(Module)都是組織代碼的可選方式,放到 API 設(shè)計(jì)的場景,都能用來約束寫法,暴露框架能力。而在模塊概念成為正統(tǒng)之前,前端框架大多提供基類來滿足這種需要,因?yàn)闆]得選
典型的,React 通過React.Component基類暴露出各種生命周期 Hook,同時(shí)定義了組件寫法:
- // Components
- class Clock extends React.Component {
- // Props
- constructor(props) {
- super(props);
- // State
- this.state = {date: new Date()};
- }
- // Lifecycle
- componentDidMount() { }
- componentWillUnmount() { }
- render() {
- // Template
- return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
- );
- }
將 Props、State、Lifecycle、Template 等框架能力整合成一個 Class,稱之為組件。并且,在很長的一段時(shí)間里,React 中能稱為組件的只有 Class
這段很長的時(shí)間有多長?
從 React 誕生之初一直到React Hooks推出并進(jìn)化成完全形態(tài)。目前(2021/1/2)React Hooks 仍然不是完全形態(tài),componentDidCatch、getSnapshotBeforeUpdate、getDerivedStateFromError等特性還不健全,具體見Do Hooks cover all use cases for classes?
也就是說,時(shí)至今日,React Components 仍等價(jià)于 Class Components,早期的函數(shù)式組件只能叫 Stateless Components,獲得 Hooks 加持之后的函數(shù)式組件雖然擺脫了 Stateless,但與完全形態(tài)的 Class Components 還有一點(diǎn)點(diǎn)差距
將 Components 概念與 Class 強(qiáng)綁定在一起真是個糟糕的選擇,被寄予厚望的 Hooks 充分說明了這一點(diǎn)。但 Props、State、Lifecycle、Template 這些框架能力又總要有東西來承載,那么,更好的選擇是什么呢?
可能是 Module。強(qiáng)調(diào)可能,是因?yàn)閮H在組織代碼這一點(diǎn)上,Module 比 Class 更純粹。Module 只組織代碼,將變量、函數(shù)等語法元素圈在一起,而不像 Class 會強(qiáng)加實(shí)例狀態(tài)、成員方法等額外概念
例如,Next.js 的 Page 定義就只是個文件模塊:
- // pages/about.js
- function About() {
- return
About- }
- export default About
最簡單的 Page,只要默認(rèn)暴露出一個 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 設(shè)計(jì),這種Module 式 API 設(shè)計(jì)更加純粹,不強(qiáng)加額外語法元素(尤其是 Class 這種根基龐大的語法元素,帶來一眾super()、bind(this)、static),在某些場景下不失為一種更好的選擇
文件約定路由
Next.js 里沒有Router.register、沒有new Route()、也沒有app.use(),沒有一切你能想到的路由定義 API
因?yàn)楦緵]有 API,路由采用的是文件路徑約定:
- // 靜態(tài)路由
- pages/index.js → /
- pages/blog/index.js → /blog
- pages/blog/first-post.js → /blog/first-post
- pages/dashboard/settings/username.js → /dashboard/settings/username
- // 動態(tài)路由
- 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)
也就是說,通過源碼所在文件路徑來標(biāo)識路由,甚至還能支持通配符,這么神奇,當(dāng)然要親眼看看源碼目錄才能感受到視覺沖擊:
- 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 之間的無縫聯(lián)動
通過前兩篇文章,我們知道 Next.js 要解決的問題是預(yù)渲染,圍繞預(yù)渲染探索出了 SSG、SSR 兩種渲染模式,并在此基礎(chǔ)上支持了包括 CSR 在內(nèi)的不同渲染模式混用:
從 API 設(shè)計(jì)的角度乍一看,似乎需要給每種組合取個別致的名字,并暴露出專門的 API,就像 SSGwithFallback、SSRwithStaticCache、PartialSSG、SPAMode…
然而,Next.js 不僅支持了所有這些混用特性,而且沒有增加任何頂層 API,它的做法是增加一些選項(xiàng),例如:
- // SSG 基礎(chǔ)款
- 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 感知路由的高級款,實(shí)現(xiàn)了 getStaticPaths
- export async function getStaticPaths() {
- return {
- paths: [
- { params: { ... } } // See the "paths" section below
- ],
- fallback: false
- };
- }
- // SSG 變身 SSR帶靜態(tài)緩存,fallback選項(xiàng)改為true
- export async function getStaticPaths() {
- return {
- paths: [
- { params: { ... } } // See the "paths" section below
- ],
- fallback: true
- };
- }
- // SSG 變身 SSG降級SSR,fallback選項(xiàng)改為'blocking'
- export async function getStaticPaths() {
- return {
- paths: [
- { params: { ... } } // See the "paths" section below
- ],
- fallback: 'blocking'
- };
- }
這種基于細(xì)分選項(xiàng)的 API 聯(lián)動用起來更輕量,始終保持帶給用戶的漸進(jìn)式體感,不需要一上來就了解全部 API、相關(guān)設(shè)計(jì)概念,從頂層區(qū)分我的場景屬于哪類,該用哪個 API,而是隨著場景的深入,發(fā)現(xiàn)那個最合適的 API/選項(xiàng)就在那里
能從文檔夠明顯地感受到這種差異,例如,Next.js 介紹 ISR 的地方將用戶指引到與之關(guān)聯(lián)的 SSR 帶靜態(tài)緩存模式:
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.
積分、互動式新手教程
這一點(diǎn)算作文檔設(shè)計(jì)技巧(文檔,當(dāng)然也要有設(shè)計(jì)),看過許多官方文檔/教程,留下深刻印象的只有 3 個:
P.S.Redux 文檔指的是2017 年的版本,現(xiàn)在貌似改過許多版,讀著很差勁了(這么點(diǎn)兒概念怎么能整出來那么多文檔)
積分、互動式新手教程威力大到什么程度?
讓我能在困到迷糊的狀態(tài)下堅(jiān)持看完教程的全部內(nèi)容,答對所有測試題目,積滿 500 分(當(dāng)然,不用幻想,全對是沒有任何獎勵的),事后回想起來也覺得不可思議,其中的技巧在于:
如此看來,在文檔中融入少量在線教育的成熟模式,可能效果極佳
默認(rèn)提供最佳實(shí)踐
讀過體驗(yàn)科技與好的產(chǎn)品,對其中玉伯提出的默認(rèn)好用印象很深,而 Next.js 算是默認(rèn)好用在框架設(shè)計(jì)上的一個真實(shí)案例
例如:
從生產(chǎn)活動的角度來看,最佳實(shí)踐本就應(yīng)該是默認(rèn)提供的,將新出現(xiàn)的最佳實(shí)踐不斷地下沉到環(huán)境層,就像 npm package、ES Module、Babel 等,如今的前端開發(fā)者已經(jīng)幾乎不需要關(guān)心這些曾經(jīng)的最佳實(shí)踐
僅從框架設(shè)計(jì)角度而言,默認(rèn)好用要求在提供最佳實(shí)踐的基礎(chǔ)上更進(jìn)一步,要把最佳實(shí)踐做沒,讓使用者能夠偷懶地以為一切本該如此。因此,最佳實(shí)踐只是一個臨時(shí)態(tài),尚未形成最佳實(shí)踐的部分才是開發(fā)者需要關(guān)心,并體現(xiàn)差異化競爭力的地方,一旦形成廣泛認(rèn)同的最佳實(shí)踐,就應(yīng)該沉淀成為默認(rèn)的基礎(chǔ)設(shè)施,開發(fā)者無需關(guān)心即可獲得這些最佳實(shí)踐帶來的種種好處
從尚未形成最佳實(shí)踐,到提供最佳實(shí)踐,到默認(rèn)提供最佳實(shí)踐,這 3 個階段可以通過一個圖片懶加載的示例來理解:
- // 第一階段:尚未形成最佳實(shí)踐
- scroll
- IntersectionObserver
- // 業(yè)務(wù)各自實(shí)現(xiàn),不存在用法示例
- // 第二階段:提供最佳實(shí)踐
- React Lazy Load Component
- // 用法示例
![]()
- // 第三階段:默認(rèn)提供最佳實(shí)踐
- next/image
- // 用法示例
- src="/me.png"
- alt="Picture of the author"
- layout="fill"
- />
第三階段與第二階段的區(qū)別在于,開發(fā)者不必關(guān)心哪個組件能夠提供懶加載功能(選擇最佳實(shí)踐),直接使用組件庫中最普通的 Image 組件,該有的功能自然就有,而懶加載只是其中一項(xiàng)
向 Serverless 延伸
Serverless 浪潮之下,前端生態(tài)也正在發(fā)生著一些變化,涌現(xiàn)出各式各樣的一體化應(yīng)用:
Next.js 提供 SSR 支持,本就需要服務(wù)端環(huán)境,Serverless 的興起很好地解決了 SSR 渲染服務(wù)的運(yùn)維問題,因此,其 Vercel 平臺默認(rèn)支持以 Serverless Functions 的形式部署 SSR 服務(wù)與 API:
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.
諸如此類的一體化應(yīng)用雖未形成最佳實(shí)踐,但傳統(tǒng)的前端框架正在歷經(jīng)變革。也許,在未來的某一天,取而代之的是與 Serverless 技術(shù)充分融合的一體化應(yīng)用框架,Universal 體系大行其道也未可知。
原文鏈接:https://mp.weixin.qq.com/s/F_4yg-0hsX0PSQ1oEOopZg

我們在微信上24小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流