在眾多專案中,React程式碼的維護經常變得棘手。其中一個常見問題是:將業務邏輯直接嵌入通用元件中,導致通用元件與業務邏輯緊密耦合,使其失去“通用性”。這種做法使通用元件過於依賴具體業務邏輯,導致程式碼難以維護和擴充套件。
示例:屎山是如何逐步堆積的
讓我們看一個例子:我們在業務元件 PageA 和 PageB 中都使用了通用元件 Card。
function PageA() {
return (
<>
{/* ... */}
{listA.map(({ title, content }) => <Card title={title} content={content} />)}
</>
)
}
function PageB() {
return (
<>
{/* ... */}
{listB.map(({ title, content }) => <Card title={title} content={content} />)}
</>
)
}
function Card({ title, content }) {
return (
<div>
<Title>{title}</Title>
<Content>{content}</Content>
</div>
)
}
某一天,出現了一個新需求:在手機端的所有頁面都需要顯示 Footer。於是,程式碼被修改如下:
function PageA({ isMobile }) {
return (
<>
{/* ... */}
{listA.map(({ title, content }) => (
<Card title={title} content={content} isMobile={isMobile} />
))}
</>
)
}
function PageB({ isMobile }) {
return (
<>
{/* ... */}
{listB.map(({ title, content }) => (
<Card title={title} content={content} isMobile={isMobile} />
))}
</>
)
}
function Card({ title, content, isMobile }) {
return (
<div>
<Title>{title}</Title>
<Content>{content}</Content>
{isMobile && <Footer />}
</div>
)
}
隨後的某一天,小張接手了這個專案,又有新需求:只有第偶數個 Card 才應該顯示 Footer。於是,秉持著最小影響範圍的原則,程式碼被改成了這樣:
function PageA({ isMobile }) {
return (
<>
{/* ... */}
{listA.map(({ title, content }, index) => (
<Card title={title} content={content} isMobile={isMobile} index={index} />)
)}
</>
)
}
function PageB({ isMobile }) {
return (
<>
{/* ... */}
{listB.map(({ title, content }, index) => (
<Card title={title} content={content} isMobile={isMobile} index={index} />)
)}
</>
)
}
function Card({ title, content, isMobile, index }) {
return (
<div>
<Title>{title}</Title>
<Content>{content}</Content>
{isMobile && index % 2 === 1 && <Footer />}
</div>
)
}
隨後的某一天,小王接手了這個專案,又有新需求。秉持著最小影響範圍的原則......
分析原因
乍看之下,每次修改都是“區域性最優”的,儘量修改最少的程式碼以限制影響範圍,以確保在新增新功能時不引入錯誤。然而,實際上,由於每次“偷懶”,我們都違反了原則,導致程式碼變得越來越混亂。
原則
分離關注點原則(Separation of Concerns)是電腦科學和軟體工程的基本設計原則之一,旨在幫助程式設計師更好地組織和管理複雜的系統。該原則的核心思想是將大型系統或程式分解為多個互相獨立的元件,每個元件負責解決特定的關注點或任務,而不會受到其他關注點的干擾。這有助於提高程式碼的可維護性、可擴充套件性和可重用性。
開放-封閉原則(Open-Closed Principle,OCP):
這個原則表明軟體實體(類、模組、函式等)應該對擴充套件開放,但對修改關閉。這意味著應該透過擴充套件現有的程式碼來引入新功能,而不是修改已有的程式碼。這有助於減少程式碼的風險,因為修改現有程式碼可能導致不可預測的副作用。
重構
將上述原則應用於這個示例中:通用元件應該只瞭解與自身相關的資訊,Card 元件只關心何時顯示 Footer,而不關心它在何處使用以及是否為第偶數個。讓我們重構程式碼:
function PageA({ isMobile }) {
return (
<>
{/* ... */}
{listA.map(({ title, content }, index) => (
<Card title={title} content={content} showFooter={isMobile && index % 2 === 1} />)
)}
</>
)
}
function PageB({ isMobile }) {
return (
<>
{/* ... */}
{listB.map(({ title, content }, index) => (
<Card title={title} content={content} showFooter={isMobile && index % 2 === 1} />)
)}
</>
)
}
function Card({ title, content, showFooter }) {
return (
<div>
<Title>{title}</Title>
<Content>{content}</Content>
{showFooter && <Footer />}
</div>
)
}
透過這次重構,我們成功解耦了通用元件和業務邏輯,使程式碼更易於維護和擴充套件。