如何編寫難以維護的 React 程式碼?耦合通用元件與業務邏輯

2023-10-10 15:01:29

在眾多專案中,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>
  )
}

通過這次重構,我們成功解耦了通用元件和業務邏輯,使程式碼更易於維護和擴充套件。