者 | Lakindu Hewawasam
譯者 | 許學文
策劃 | 丁曉昀
如果你在開發工作中使用的是 React 框架,那么首當其沖要學習的就是思考如何設計組件。組件設計并非簡單地將多個組件合成一個集合,而是需要思考如何設計更小、復用性更強的組件。例如,思考下面這張組件圖:
簡化的組件圖
圖中有三個組件,分別是:
如圖所示,Typography 組件同時被 Footer 和 Sizeable Box 組件使用。通常我們以為這樣,就能構建一個簡單、易維護和易排除錯誤的應用了。但其實只是這樣思考組件的設計是遠遠不夠的。
如果你知道如何從組件的視角思考問題,就可以通過在 React 組件中使用設計模式來提高代碼的整體模塊性、可擴展性和可維護性。
因此,下面這五種設計模式,是你在使用 React 時必須要掌握的。
首先,在使用 React 時候,請嘗試為應用設計基礎組件。
基礎UI組件,就是一個具備默認行為且支持定制化的組件。
例如,每個應用都會通過基礎的樣式、按鈕設計或者基礎的排版,來實現應用在視覺和交互上的一致性。這些組件的設計特點是:
通過一個 Button 組件就能很好地說明基礎組件模式的實現。示例如下:
現在你就可以利用基礎組件模式進行設計,使組件的使用者可以改變其行為。請參考我基于基礎組件模式完成的Button組件,示例代碼如下:
import React, { ButtonHTMLAttributes } from 'react';
// 按鈕組件的形態:實心或者空心
type ButtonVariant='filled' | 'outlined';
export type ButtonProps={
/**
* the variant of the button to use
* @default 'outlined'
*/
variant?: ButtonVariant;
} & ButtonHTMLAttributes<HTMLButtonElement>;;
const ButtonStyles: { [key in ButtonVariant]: React.CSSProperties }={
filled: {
backgroundColor: 'blue', // Change this to your filled button color
color: 'white',
},
outlined: {
border: '2px solid blue', // Change this to your outlined button color
backgroundColor: 'transparent',
color: 'blue',
},
};
export function Button({ variant='outlined', children, style, ...rest }: ButtonProps) {
return (
<button
type='button'
style={{
...ButtonStyles[variant],
padding: '10px 20px',
borderRadius: '5px',
cursor: 'pointer',
...style
}} {...rest}>
{children}
</button>
);
}
復制代碼
仔細觀察代碼會發現,這里 Button 組件的 props 類型合并了原生 HTML 中 button 標簽屬性的全部類型。這意味著,使用者除了可以為 Button 組件設置默認配置外,還可以設置諸如 onClick、aria-label 等自定義配置。這些自定義配置會通過擴展運算符傳遞給 Button 組件內部的 button 標簽。
通過不同的上下文設置,可以看到不同的 Button 組件的形態,效果截圖如下圖。
這個可以查看具體設置:
https://bit.cloud/lakinduhewa/react-design-patterns/base/button/~compositions
基礎組件在不同上下文中的使用效果
通過不同的上下文,你可以設定組件的行為。這可以讓組件成為更大組件的基礎。
在成功創建了基礎組件后,你可能會希望基于基礎組件創建一些新的組件。
例如,你可以使用之前創建的 Button 組件來實現一個標準的 DeleteButton 組件。通過在應用中使用該 DeleteButton,可以讓應用中所有刪除操作在顏色、形態以及字體上保持一致。
不過,如果出現重復組合一組組件來實現相同效果的現象,那么你可以考慮將它們封裝到一個組件中。
下面,讓我們來看看其中一種實現方案:
https://bit.cloud/lakinduhewa/react-design-patterns/composition/delete-button
使用組合模式創建組件
如上面的組件依賴圖所示,DeleteButton 組件使用基礎的 Button 組件為所有與刪除相關的操作提供標準的實現。下面是基本代碼實現:
// 這里引入了,基礎按鈕組件和其props
import { Button, ButtonProps } from '@lakinduhewa/react-design-patterns.base.button';
import React from 'react';
export type DeleteButtonProps={} & ButtonProps;
export function DeleteButton({ ...rest }: DeleteButtonProps) {
return (
<Button
variant='filled'
style={{
background: 'red',
color: 'white'
}}
{...rest}
>
DELETE
</Button>
);
}
復制代碼
我們使用基于模式一創建的 Button 組件來實現的 DeleteButton 組件的效果如下:
現在我們可以在應用中使用統一的刪除按鈕。此外,如果你使用類似 Bit 的構建系統進行組件的設計和構建,那么當 Button 組件發生改變時,可以讓CI服務自動將此改變傳遞到DeleteButton組件上,就像下面這樣(當 Button 組件從 0.0.3 升級到了 0.0.4,那么 CI 服務會自動觸發,將 DeleteButton 組件從 0.0.1 升級到 0.0.2):
Bit 上的一個 CI 構建
React Hooks 是React v16就推出來的特性,它不依賴類組件實現狀態管理、負效應等概念。簡而言之,就是你可以通過利用 Hooks API 擺脫對類組件的使用需求。useSate 和 useEffect 是最廣為人知的兩個 Hooks API,但本文不打算討論它們,我想重點討論如何利用 Hooks 來提高組件的整體可維護性。
例如,請考慮下面這個場景:
基于上面的案例,你可能會像下面這樣將 API 邏輯直接寫在函數組件中:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const BlogList=()=> {
const [blogs, setBlogs]=useState([]);
const [isLoading, setIsLoading]=useState(true);
const [error, setError]=useState(null);
useEffect(()=> {
axios.get('https://api.example.com/blogs')
.then(response=> {
setBlogs(response.data);
setIsLoading(false);
})
.catch(error=> {
setError(error);
setIsLoading(false);
});
}, []);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h2>Blog List</h2>
<ul>
{blogs.map(blog=> (
<li key={blog.id}>{blog.title}</li>
))}
</ul>
</div>
);
};
export default BlogList;
復制代碼
這樣寫,組件也能正常工作。它將會獲取博客文章列表并且渲染在 UI 上。但是,這里將 UI 邏輯和 API 邏輯混在一起了。
理想情況下,React 組件應該不需要關系如何獲取數據。而只需要關心接收一個數據數組,然后將其呈現在 DOM 上。
因此,實現這一目標的最佳方法是將 API 邏輯抽象到 React Hook 中,以便在組件內部進行調用。這樣做就可以打破 API 調用與組件之間的耦合。通過這種方式,就可以在不影響組件的情況下,修改底層的數據獲取邏輯。
其中一種實現方式如下。
import { useEffect, useState } from 'react';
import { Blog } from './blog.type';
import { Blogs } from './blog.mock';
export function useBlog() {
const [blogs, setBlogs]=useState<Blog[]>([]);
const [loading, setLoading]=useState<boolean>(false);
useEffect(()=> {
setLoading(true);
// 注意:這里的setTimeout非實際需要,只是為了模擬API調用
setTimeout(()=> {
setBlogs(Blogs);
setLoading(false);
}, 3000);
}, []);
return { blogs, loading }
}
復制代碼
如上代碼所示,useBlog hook 獲取博客列表數據,然后賦值給狀態變量,最后通過導出變量給到消費者(BlogList 組件)使用:
Hook 效果
import React from 'react';
// 引入上面封裝的 useBlog hook
import { useBlog } from '@lakinduhewa/react-design-patterns.hooks.use-blog';
export function BlogList() {
const { blogs, loading }=useBlog();
if (loading) {
return (
<p>We are loading the blogs...</p>
)
}
return (
<ul>
{blogs.map((blog)=> <ol
key={blog.id}
>
{blog.title}
</ol>)}
</ul>
);
}
復制代碼
BlogList 組件效果
通過調用 useBlog 和使用其導出的狀態變量,我們在 BlogList 組件中使用了 Hooks。如此,相對于之前,我們可以減少大量代碼,并以最少的代碼和精力維護兩個組件。
此外,當你使用類似 Bit 這樣的構建系統時(就像我一樣),只需將 useBlog 組件導入本地開發環境,然后再修改完成之后重新推送回 Bit Cloud。Bit Cloud 的構建服務器可以依托依賴樹將此修改傳遞給整個應用。因此如果只執行一些簡單修改,甚至不需要訪問整個應用。
此模式的核心是解決組件狀態共享。我們都曾是 props 下鉆式傳遞的受害者。但如果你還沒有經歷過,那這里簡單解釋下:“props 下鉆式傳遞”就是當你在組件樹中進行 props 傳遞時,這些 props 只會在最底層組件中被使用,而中間層的組件都不會使用該 props。例如,看看下面這張圖:
props 下鉆式傳遞
從 BlogListComponent 一直向下傳遞一個 isLoading 的 props 到 Loader。但是,isLoading 只在 Loader 組件中使用。因此,在這種情況下,組件不但會引入不必要的 props,還會有性能開銷。因為當 isLoading 發生變化時,即使組件沒有使用它,React 依然會重新渲染你的組件樹。
因此,解決方案之一就是通過利用 React Context 來使用 React Context Provider 模式。React Context 是一組組件的狀態管理器,通過它,你可以為一組組件創建特定的上下文。通過這種方式,你可以在上下文中定義和管理狀態,讓不同層級的組件都可以直接訪問上下文,并按需使用 props。這樣就可以避免 props 下鉆式傳遞了。
主題組件就是該模式的一個常見場景。例如,你需要在應用程序中全局訪問主題。但將主題傳遞到應用中的每個組件并不現實。你可以創建一個包含主題信息的 Context,然后通過 Context 來設置主題。看一下我是如何通過React Context實現主題的,以便更好地理解這一點:
https://bit.cloud/lakinduhewa/react-design-patterns/contexts/consumer-component
import { useContext, createContext } from 'react';
export type SampleContextContextType={
/**
* primary color of theme.
*/
color?: string;
};
export const SampleContextContext=createContext<SampleContextContextType>({
color: 'aqua'
});
export const useSampleContext=()=> useContext(SampleContextContext);
復制代碼
在 Context 中定義了一種主題顏色,它將在所有實現中使用該顏色來設置字體顏色。接下來,我還導出了一個 hook——useSampleContext,該 hook 讓消費者可以直接使用 Context。
只是這樣還不行,我們還需要定義一個 Provider。Provider 是回答 "我應該與哪些組件共享狀態?"問題的組件。Provider的實現示例如下:
import React, { ReactNode } from 'react';
import { SampleContextContext } from './sample-context-context';
export type SampleContextProviderProps={
/**
* primary color of theme.
*/
color?: string,
/**
* children to be rendered within this theme.
*/
children: ReactNode
};
export function SampleContextProvider({ color, children }: SampleContextProviderProps) {
return <SampleContextContext.Provider value={{ color }}>{children}</SampleContextContext.Provider>
}
復制代碼
Provider 在管理初始狀態和設置 Context 可訪問狀態的組件方面起著至關重要的作用。
接下來,你可以創建一個消費者組件來使用狀態:
消費者組件
最后一個想和大家分享的是條件渲染模式。今天,人人都知道 React 中的條件渲染。它通過條件判斷來選擇組件進行渲染。
但在實際使用中我們的用法常常是錯誤的:
// ComponentA.js
const ComponentA=()=> {
return <div>This is Component A</div>;
};
// ComponentB.js
const ComponentB=()=> {
return <div>This is Component B</div>;
};
// ConditionalComponent.js
import React, { useState } from 'react';
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';
const ConditionalComponent=()=> {
const [toggle, setToggle]=useState(true);
return (
<div>
<button onClick={()=> setToggle(!toggle)}>Toggle Component</button>
{toggle ? <ComponentA /> : <ComponentB />}
</div>
);
};
export default ConditionalComponent;
復制代碼
你是否注意到,這里我們將基于條件的邏輯耦合到了 JSX 代碼片段中。通常,你不應該在 JSX 代碼中中添加任何與計算相關的邏輯,而只將與 UI 渲染相關的內容放在其中。
解決這個問題的方法之一是使用條件渲染組件模式。創建一個可重用的 React 組件,該組件可以根據條件渲染兩個不同的組件。它的實現過程如下:
import React, { ReactNode } from 'react';
export type ConditionalProps={
/**
* the condition to test against
*/
condition: boolean
/**
* the component to render when condition is true
*/
whenTrue: ReactNode
/**
* the component to render when condition is false
*/
whenFalse: ReactNode
};
export function Conditional({ condition, whenFalse, whenTrue }: ConditionalProps) {
return condition ? whenTrue : whenFalse;
}
復制代碼
我們創建了一個可以按條件渲染兩個組件的組件。當我們將其集成到其他組件中時,會使代碼更簡潔,因為無需在 React 組件中加入復雜的渲染邏輯。你可以像下面這樣使用它:
export const ConditionalTrue=()=> {
return (
<Conditional
condition
whenFalse="You're False"
whenTrue="You're True"
/>
);
}
export const ConditionalFalse=()=> {
return (
<Conditional
condition={false}
whenFalse="You're False"
whenTrue="You're True"
/>
);
}
復制代碼
實際的輸入如下:
掌握這五種設計模式,為 2024 年做好充分準備,構建出可擴展和可維護的應用吧。
如果你想詳細深入本文中討論的模式,請隨時查看我在Bit Cloud的空間:
https://bit.cloud/lakinduhewa/react-design-patterns
感謝你的閱讀!
原文鏈接:2024年,你應該知道的5種React設計模式_架構/框架_InfoQ精選文章
日常開發中,我們有一些組件的樣式是重復的。
例如,Text 組件的 width、height 都是一樣的。
@Entry
@Component
struct Index {
build() {
Column() {
Text("春明不覺曉")
.width(100)
.height(50)
Text("處處聞啼鳥")
.width(100)
.height(50)
Text("夜來風雨聲")
.width(100)
.height(50)
Text("花落知多少")
.width(100)
.height(50)
}
.width('100%')
}
}
那么,我們可以將這些重復的樣式抽出來,做成一個公共樣式。
公共樣式是一個函數,格式為:@Style 樣式名稱() { 樣式描述 }
例如這里的 @Style textSize(){}。
@Entry
@Component
struct Index {
@Styles textSize(){
.width(100)
.height(50)
}
build() {
Column() {
Text("春明不覺曉")
.width(100)
.height(50)
Text("處處聞啼鳥")
.width(100)
.height(50)
Text("夜來風雨聲")
.width(100)
.height(50)
Text("花落知多少")
.width(100)
.height(50)
}
.width('100%')
}
}
接下來,我們就可以使用 textSize 去替換掉那些重復的樣式。
@Entry
@Component
struct Index {
@Styles textSize(){
.width(100)
.height(50)
}
build() {
Column() {
Text("春明不覺曉")
.textSize()
Text("處處聞啼鳥")
.textSize()
Text("夜來風雨聲")
.textSize()
Text("花落知多少")
.textSize()
}
.width('100%')
}
}
運行結果是不變的:
使用公共樣式可以幫我們減少很多重復的代碼。
推薦大家在日常開發中多多使用 @Style 定義公共樣式。
注意:當前 @Styles 僅支持通用屬性和通用事件。
通用屬性文檔地址:
https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-universal-attributes-size-0000001428061700-V3
通用事件文檔地址:
https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-universal-events-click-0000001477981153-V3
還有一點需要注意的是,@Styles方法不支持參數。下列操作是不允許的:
// 錯誤寫法,不允許有參數
@Styles textSize(message: string){
.width(100)
.height(50)
}
至此,@Style 基本使用方法介紹完畢。
者|Next.js 團隊
譯者|無明
出處丨前端之巔
在經過 26 次金絲雀發布和 340 萬次下載之后,近日,我們正式發布了 Next.js 7.0,新功能包括:
Next.js 的主要目標之一是提供最佳的性能和開發者體驗。最新版本為構建和調試管道帶來了很多重大改進。
得益于 Webpack 4 和 Babel 7,以及我們對代碼庫做出的很多改進和優化,Next.js 現在在開發過程中的啟動速度提高了 57%。
我們新增了增量編譯緩存,讓變更代碼的構建速度快了 40%。
以下是我們收集的一些示例數據:
因為使用了 webpackbar,在開發和構建的同時可以看到更好的實時反饋:
使用 react-error-overlay 更好地報告錯誤
準確地渲染錯誤對于良好的開發和調試體驗來說是至關重要的。到目前為止,我們可以渲染錯誤消息和堆棧跟蹤信息。我們在此基礎上更進一步,我們使用 react-error-overlay 來豐富堆棧跟蹤信息:
這是之前和之后的錯誤顯示比較:
另外,借助 react-error-overlay,你只需單擊特定代碼塊就可以輕松打開文本編輯器。
從發布第一個版本以來,Next.js 一直使用 Webpack 來打包代碼和重用豐富的插件。Next.js 現在使用了最新的 Webpack 4,其中包含很多改進和 bug 修復。
另一個新功能是支持 WebAssembly,Next.js 甚至可以進行 WebAssembly 服務器渲染。
這里有一個例子:
https://github.com/zeit/next.js/tree/canary/examples/with-webassembly
因為使用了 Webpack 4,我們引入了一種從捆綁包中提取 CSS 的新方法,這個插件叫作 mini-extract-css-plugin(https://github.com/webpack-contrib/mini-css-extract-plugin)。
mini-extract-css-plugin 提供了 @zeit/next-css、@zeit/next-less、@zeit/next-sass 和 @zeit/next-stylus。
這些 Next.js 插件的新版本解決了與 CSS 導入相關的 20 個問題,例如,現在支持 import() 動態導入 CSS:
// components/my-dynamic-component.js import './my-dynamic-component.css' export default ()=> <h1>My dynamic component</h1> // pages/index.js import dynamic from 'next/dynamic' const MyDynamicComponent=dynamic(import('../components/my-dynamic-component')) export default ()=> <div> <MyDynamicComponent/> </div>
一個重大改進是現在不再需要在 pages/_document.js 中添加一下內容:
<link rel="stylesheet" href="/_next/static/style.css" />
Next.js 會自動注入這個 CSS 文件。在生產環境中,Next.js 還會自動向 CSS URL 中添加內容哈希,當文件發生變更時,最終用戶就不會得到舊文件,并且能夠獲得不可變的永久緩存。
簡而言之,要在 Next.js 項目中支持導入.css 文件,只需要在 next.config.js 中注冊 withCSS 插件:
const withCSS=require('@zeit/next-css') module.exports=withCSS({/* my next config */})
從版本 3 開始,Next.js 就通過 next/dynamic 來支持動態導入。
作為這項技術的早期采用者,我們必須自己編寫解決方案來處理 import()。
因此,Next.js 逐漸缺失 Webpack 后來引入的一些功能,包括 import()。
例如,無法手動命名和捆綁某些文件:
import(/* webpackChunkName: 'my-chunk' */ '../lib/my-library')
另一個例子是在 next/dyanmic 模塊之外使用 import()。
從 Next.js 7 開始,我們不再直接使用默認的 import(),Next.js 為我們提供了開箱即用的 import() 支持。
這個變更也是完全向后兼容的。使用動態組件非常簡單:
import dynamic from 'next/dynamic' const MyComponent=dynamic(import('../components/my-component')) export default ()=> { return <div> <MyComponent /> </div> }
這段代碼的作用是為 my-component 創建一個新的 JavaScript 文件,并只在渲染< MyComponent/>時加載它。
最重要的是,如果沒有進行渲染,< script>標記就不會出現在初始 HTML 文檔中。
為了進一步簡化我們的代碼庫并利用優秀的 React 生態系統,在 Next.js 7 中,我們使用 react-loadable 重寫了 next/dynamic 模塊。這為動態組件引入了兩個很棒的新特性:
Next.js 6 中就已經引入了 Babel 7 測試版。后來 Babel 7 穩定版本發布,現在,Next.js 7 正在使用這個新發布的穩定版 Babel 7。
一些主要特性:
如果你的 Next.js 項目中沒有自定義 Babel 配置,那么就不存在重大變更。
但如果你具有自定義 Babel 配置,則必須將相應的自定義插件 / 預設升級到最新版本。
如果你從 Next.js 6 以下的版本升級,可以使用 babel-upgrade 工具。
除了升級到 Babel 7 之外,當 NODE_ENV 被設置為 test 時,Next.js Babel 預設(next/babel)現在默認將 modules 選項設置為 commonjs。
這個配置選項通常是在 Next.js 項目中創建自定義.babelrc 的唯一理由:
{ "env": { "development": { "presets": ["next/babel"] }, "production": { "presets": ["next/babel"] }, "test": { "presets": [["next/babel", { "preset-env": { "modules": "commonjs" } }]] } } }
使用 Next.js 7,這將變成:
{ "presets": ["next/babel"] }
現在可以刪除.babelrc,因為在沒有 Babel 配置時,Next.js 將自動使用 next/babel。
Next.js 在預渲染 HTML 時會將頁面內容放在< html>、< head>、< body>結構中,并包含頁面所需的 JavaScript 文件。
這個初始載荷之前約為 1.62kB。在 Next.js 7 中,我們優化了初始 HTML 載荷,現在為 1.5kB,減少了 7.4%,讓頁面變得更加精簡。
我們主要通過以下幾種方式來縮小文件:
在 Next.js 5 中,我們引入了 assetPrefix 支持,讓 Next.js 可以自動從某個位置(通常是 CDN)加載資源。如果你的 CDN 支持代理,可以使用這種辦法。你可以像這樣請求資源:
https://cdn.example.com/_next/static/<buildid>/pages/index.js
通常,CDN 先檢查緩存中是否包含這個文件,否則直接從源中請求文件。
不過,某些解決方案需要將目錄直接預先上傳到 CDN 中。這樣做的問題在于 Next.js 的 URL 結構與.next 文件夾中的文件夾結構不匹配。例如我們之前的例子:
https://cdn.example.com/_next/static/<buildid>/pages/index.js // 映射到: .next/page/index.js
在 Next.js 7 中,我們改變了.next 的目錄結構,讓它與 URL 結構相匹配:
https://cdn.example.com/_next/static/<buildid>/pages/index.js // 映射到: .next/static/<buildid>/pages/index.js
盡管我們建議使用代理類型的 CDN,但新結構也允許不同類型 CDN 的用戶將.next 目錄上傳到 CDN。
我們也引入了 styled-jsx 3,Next.js 的默認 CSS-in-JS 解決方案,現在已經為 React Suspense 做好了準備。
如果一個組件不屬于當前組件作用域的一部分,那么該如何設置這個子組件的樣式呢?例如,如果你將一個組件包含在父組件中,并只有當它被用在父組件中時才需要特定的樣式:
const ChildComponent=()=> <div> <p>some text</p> </div> export default ()=> <div> <ChildComponent /> <style jsx>{` p { color: black } `}</style> </div>
上面的代碼試圖選擇 p 標簽,但其實不起作用,因為 styled-jsx 樣式被限定在當前組件,并沒有泄漏到子組件中。解決這個問題的一種方法是使用:global 方法,將 p 標記的前綴移除。但這樣又引入了一個新問題,即樣式泄露到了整個頁面中。
在 styled-jsx 3 中,通過引入一個新的 API css.resolve 解決了這個問題,它將為給定的 syled-jsx 字符串生成 className 和< style>標簽(styles 屬性):
import css from 'styled-jsx/css' const ChildComponent=({className})=> <div> <p className={className}>some text</p> </div> const { className, styles }=css.resolve`p { color: black }` export default ()=> <div> <ChildComponent className={className} /> {styles} </div>
這個新 API 可以將自定義樣式傳給子組件。
由于這是 styled-jsx 的主要版本,如果你使用了 styles-jsx/css,那么在捆綁包大小方面有一個重大變化。在 styled-jsx 2 中,我們將生成外部樣式的“scoped”和“global”版本,即使只使用“scoped”版本,我們也會將“global”版本包含在內。
使用 styled-jsx 3 時,全局樣式必須使用 css.global 而不是 css,這樣 styled-jsx 才能對包大小進行優化。
App 和 Page 之間的 React Context(服務器端渲染)
從 Next.js 7 開始,我們支持 pages/_app.js 和頁面組件之間的 React Context API。
以前,我們無法在服務器端的頁面之間使用 React 上下文。原因是 Webpack 保留了內部緩存模塊而不是使用 require.cache,我們開發了一個自定義 Webpack 插件來改變這種行為,以便在頁面之間共享模塊實例。
這樣我們不僅可以使用新的 React 上下文,在頁面之間共享代碼時還能減少 Next.js 的內存占用。
從 Next.js 首次發布以來,就已獲得相當多的用戶,從財富 500 強公司到個人博客。我們非常高興看到 Next.js 的采用量一直在增長。
目前,超過 12,500 個被公開索引的域名在使用 Next.js。我們有超過 500 名貢獻者,他們至少提交過一次代碼。在 GitHub 上,這個項目已經有 29,000 個 star。自首次發布以來,已提交了大約 2200 個拉取請求。
Next.js 社區在 spectrum.chat/next-js 上擁有近 2000 名成員。
英文原文
https://nextjs.org/blog/next-7
*請認真填寫需求信息,我們會在24小時內與您取得聯系。