一種更好的前端組件結(jié)構(gòu):組件樹
共 22772字,需瀏覽 46分鐘
·
2024-06-18 08:48
大廠技術(shù) 高級前端 Node進(jìn)階
點(diǎn)擊上方 程序員成長指北,關(guān)注公眾號
回復(fù)1,加入高級Node交流群
自很久以前遵循互聯(lián)網(wǎng)上的建議以來,我一直采用了某種“能工作就行”的組件結(jié)構(gòu)。
場景
讓我們首先想象一個簡化的前端應(yīng)用程序目錄結(jié)構(gòu),如下所示:
public/
some-image.jpg
pages/
index.tsx
components/
Heading.tsx
Logo.tsx
Layout.tsx
BoxContainer.tsx
Footer.tsx
問題所在
上面的簡單應(yīng)用程序結(jié)構(gòu)很難解釋這些組件之間是如何相互作用的。
例如,您可能會猜測Layout.tsx導(dǎo)入Footer.tsx和Header.tsx,而這又可能導(dǎo)入BoxContainer.tsx。但這僅僅從文件結(jié)構(gòu)上是不清楚的。
更糟糕的是,隨著應(yīng)用程序的增長,組件列表將變得越來越難以推斷它們是如何依賴的。
簡單方法:扁平組件結(jié)構(gòu)
通常首先想到的是將組件組織到語義正確的目錄中。
下面是這種方法的典型結(jié)果:
public/
some-image.jpg
pages/
index.tsx
components/
layout/
Layout.tsx
Heading.tsx
Footer.tsx
common/
Heading.tsx
BoxContainer.tsx
問題#1:很難擴(kuò)展好名字
作為一個開發(fā)人員,您會嘗試為每個目錄創(chuàng)建好的名稱和分類,如containers、headings等。
問題是您需要為目錄考慮更多的分類,而不僅僅是組件名稱。
你經(jīng)常會忍不住說,“我就把這個移到公共目錄吧?!睋碛衏ommon目錄是你所追求的目標(biāo)相悖的反模式,但是在這種結(jié)構(gòu)下,很容易被吸引進(jìn)入其中。
而且當(dāng)應(yīng)用程序變得足夠大時,您可能不得不開始考慮創(chuàng)建另一級目錄來保持內(nèi)容的組織性。
這需要創(chuàng)建更多的名稱,增加了存儲庫用戶的認(rèn)知負(fù)荷。最終這種方法無法很好地擴(kuò)展。
問題#2:目錄名稱的認(rèn)知負(fù)荷增加
在此之前,那些瀏覽代碼庫的人首先會通過組件的名稱以及它們之間的關(guān)系來初步了解每個組件的功能。
現(xiàn)在他們還需要理解你創(chuàng)建的目錄名稱,如果這些名稱在語義上不符合整體,這可能會使他們更加困惑。
更好的方法:組件樹模式
使用這種方法,您的重點(diǎn)是擁有命名良好的組件,這些組件隱式地解釋了它們的組成,而不用特意對具有不同名稱的組件組進(jìn)行分類。
組件導(dǎo)入規(guī)則
-
可以向上導(dǎo)入,除了它自己的父級 -
可以導(dǎo)入同級 -
無法導(dǎo)入同級組件 -
無法導(dǎo)入其父級
public/
some-image.jpg
pages/
index.tsx
components/
Layout/
components/
Heading/
components/
Logo.tsx
Menu.tsx
Heading.tsx
CopyrightIcon.tsx
Footer.tsx
Layout.tsx
BoxContainer.tsx
讓我們展示Footer.tsx的內(nèi)容,并使用上面列出的規(guī)則作為示例:
// components/Layout/components/Footer.tsx
// Can import upwards, except its own parent
import { BoxContainer } from '../../BoxContainer.tsx';
// Can import siblings
import { CopyrightIcon } from './CopyrightIcon.tsx';
// WRONG: Cannot import sibling's components
// import { Menu } from './Heading/components/Menu.tsx';
// WRONG: Cannot import its parent
// import { Layout } from '../Layout.tsx';
export const Footer = () => (
<BoxContainer>
<CopyrightIcon />
<p>All rights reserved, etc.</p>
</BoxContainer>
)
優(yōu)點(diǎn)#1:明顯的子組件關(guān)系
組件樹模式消除了猜測;組件之間的關(guān)系立即變得清晰明了。例如,Menu.tsx 作為 Heading.tsx 的內(nèi)部依賴被整齊地嵌套在其中。
同樣清晰的是Menu.tsx沒有被其他任何組件使用,這有助于您在日常開發(fā)任務(wù)中清理代碼時盡早忽略它。
優(yōu)點(diǎn)2:可重用性的定義更加細(xì)致入微
在簡單的方法中,組件被分為“常見”和“非常見”兩種??紤]到可重用性,組件樹有助于避免這種無效的二元思維。
components/
Layout/
components/
Heading/
components/
- Logo.tsx
Menu.tsx
Heading.tsx
+ Logo.tsx
CopyrightIcon.tsx
Footer.tsx
Layout.tsx
BoxContainer.tsx
在上面的例子中,如果Logo.tsx對于更多的組件變得必要,而不僅僅是Menu.tsx,我們可以簡單地將其上移一級。對于BoxContainer.tsx來說,它可能沒有足夠的可重用性(或“通用性”),但在Layout.tsx組件的上下文中它是足夠可重用的。
優(yōu)點(diǎn)#3:盡量減少命名
由于您有組件樹,因此不需要將目錄名分類在組件名之上。組件名稱是分類,當(dāng)您看到組件由哪些內(nèi)部組件組成時,為組件確定好的名稱也會更容易。
額外的好處:從組件中提取代碼到單獨(dú)的文件中,而無需考慮名稱
現(xiàn)在我們考慮一種情況,您希望從Footer.tsx中提取一些實用程序函數(shù),因為文件變得有點(diǎn)大,并且您認(rèn)為可以從中分解一些邏輯,而不是分解更多的UI。
雖然你可以創(chuàng)建一個utils/目錄,但這會迫使你選擇一個文件名來放置你的實用函數(shù)。
相反,選擇使用文件后綴,如Footer.utils.tsx或Footer.test.tsx。
components/
Layout/
components/
Heading/
components/
Logo.tsx
Menu.tsx
Heading.tsx
CopyrightIcon.tsx
+ Footer.utils.tsx
Footer.tsx
Layout.tsx
BoxContainer.tsx
這樣你就不必去想一個很合適的名字,如emailFormatters.ts或非常模糊的東西,如helpers.ts。避免命名帶來的認(rèn)知負(fù)擔(dān),這些實用程序?qū)儆?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">Footer.tsx,可以由Footer.tsx及其內(nèi)部組件使用(再次向上導(dǎo)入)。
組件樹的反駁觀點(diǎn)
“太多的組件目錄了”
第一次看到這個結(jié)構(gòu),這是大多數(shù)人的下意識反應(yīng)。
是的,有很多“組件”目錄。但當(dāng)我與團(tuán)隊一起確定項目結(jié)構(gòu)時,我總是強(qiáng)調(diào)清晰度的重要性。
我在一個代碼庫中衡量成功的方法之一是高級和初級開發(fā)人員對于清晰度的看法,而在這方面,我發(fā)現(xiàn)組件樹總是對實現(xiàn)這個目標(biāo)起到重要作用。
“呃:import … from ./MyComponent/MyComponent.tsx?
雖然import … from ./MyComponent/MyComponent.tsx可能看起來不漂亮,但它直接指示組件來自哪里帶來的清晰度更重要。
關(guān)于導(dǎo)入字符串,以下是為開發(fā)人員增加認(rèn)知負(fù)荷的示例。
-
使用像 import ... from 'common/components'這樣的導(dǎo)入別名對開發(fā)人員來說是一種精神負(fù)擔(dān) -
到處都有 index.ts文件,只需要寫import ... from './MyComponent'。但對于按文件搜索的開發(fā)人員來說,找到正確的文件可能需要更多的時間。
最終比較:復(fù)雜場景
多虧了像ChatGPT這樣的工具,為更復(fù)雜的場景測試這樣的模式非常容易。
在解釋了結(jié)構(gòu)之后,我讓ChatGPT在左列生成“平面”目錄結(jié)構(gòu),在右邊生成我稱為“組件樹”的結(jié)構(gòu)。
Flat Structure | Component Trees
------------------------------------+---------------------------------------------------
pages/ | pages/
index.tsx | index.tsx
shop.tsx | shop.tsx
product/ | product/
[slug].tsx | [slug].tsx
cart.tsx | cart.tsx
checkout.tsx | checkout.tsx
about.tsx | about.tsx
contact.tsx | contact.tsx
login.tsx | login.tsx
register.tsx | register.tsx
user/ | user/
dashboard.tsx | dashboard.tsx
orders.tsx | orders.tsx
settings.tsx | settings.tsx
|
components/ | components/
layout/ | Layout/
Layout.tsx | components/
Header.tsx | Header/
Footer.tsx | components/
Sidebar.tsx | Logo.tsx
Breadcrumb.tsx | NavigationMenu.tsx
common/ | SearchBar.tsx
Button.tsx | UserIcon.tsx
Input.tsx | CartIcon.tsx
Modal.tsx | Header.tsx
Spinner.tsx | Footer/
Alert.tsx | components/
product/ | SocialMediaIcons.tsx
ProductCard.tsx | CopyrightInfo.tsx
ProductDetails.tsx | Footer.tsx
ProductImage.tsx | Layout.tsx
ProductTitle.tsx | BoxContainer.tsx
ProductPrice.tsx | Button.tsx
AddToCartButton.tsx | Input.tsx
filters/ | Modal.tsx
SearchFilter.tsx | Spinner.tsx
SortFilter.tsx | Alert.tsx
cart/ | ProductCard/
Cart.tsx | components/
CartItem.tsx | ProductImage.tsx
CartSummary.tsx | ProductTitle.tsx
checkout/ | ProductPrice.tsx
CheckoutForm.tsx | AddToCartButton.tsx
PaymentOptions.tsx | ProductCard.tsx
OrderSummary.tsx | ProductDetails/
user/ | components/
UserProfile.tsx | ProductSpecifications.tsx
UserOrders.tsx | ProductReviews.tsx
LoginBox.tsx | ProductReviewForm.tsx
RegisterBox.tsx | ProductDetails.tsx
about/ | SearchFilter.tsx
AboutContent.tsx | SortFilter.tsx
contact/ | Cart/
ContactForm.tsx | components/
review/ | CartItemList.tsx
ProductReview.tsx | CartItem.tsx
ProductReviewForm.tsx | CartSummary.tsx
address/ | Cart.tsx
ShippingAddress.tsx | CheckoutForm/
BillingAddress.tsx | components/
productInfo/ | PaymentDetails.tsx
ProductSpecifications.tsx | BillingAddress.tsx
cartInfo/ | ShippingAddress.tsx
CartItemList.tsx | CheckoutForm.tsx
userDetail/ | PaymentOptions.tsx
UserSettings.tsx | OrderSummary.tsx
icons/ | UserProfile/
Logo.tsx | components/
SocialMediaIcons.tsx | UserOrders.tsx
CartIcon.tsx | UserSettings.tsx
UserIcon.tsx | UserProfile.tsx
| LoginBox.tsx
| RegisterBox.tsx
| AboutContent.tsx
| ContactForm.tsx
這是一個沒有任何測試文件、實用程序文件或類似文件的示例。
對于組件樹結(jié)構(gòu),您可以在組件目錄中添加后綴為的實用程序或測試文件。
至于平面結(jié)構(gòu),你可能需要創(chuàng)建一個單獨(dú)的 utils 目錄來理解已經(jīng)相當(dāng)復(fù)雜的認(rèn)知負(fù)荷。
最后
有機(jī)會的話可以嘗試這個組件結(jié)構(gòu)。你會發(fā)現(xiàn)它是如此的直觀和高效,以至于不會再回到其他更復(fù)雜的結(jié)構(gòu),它們沒有簡化組件管理的能力。
本文翻譯自 A Better Frontend Component Structure: Component Trees,作者:William Bernting, 略有刪改。 譯者:南城
Node 社群
我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。
“分享、點(diǎn)贊、在看” 支持一下
