淺談antd@5使用心得

MangoGoing發表於2023-03-26

antd v5版本釋出至今已有整整4個月了,在v5版本釋出後我也是第一時間把之前v4專案遷移到了v5,對於此次升級還是有很大的變化跟改進的,故在此淺聊一下我的遷移經歷和建議,前提是你已經通讀過官方的遷移指南

重大改進

除了 ui 風格變化、更好的支援 Typescript、最佳化底層庫的依賴等,我覺得此次大版本更新帶來最大的變化是 CSS-in-JS 替代原本 less 的技術方案變更。升至v5後 antd 產物不再匯出 css 檔案,只有一個 reset.css ,況且這個重置樣式檔案也只是為了讓開發者不再去依賴其他的諸如 Normalize.css 的第三方庫。
image-20230319144130194

為什麼說這是一次重大改進?做過 antd 切換主題功能的開發者應該深有體會,我在之前的文章也有介紹過實現方案:[前端實現切換主題方案](https://segmentfault.com/a/1190000042573976) 。流程是先提前編譯出來每種模式下的對應 less 檔案,然後透過包裹 antd 提供的 ConfigProvider ,根據 mode 的變幻動態傳值修改 prefixCls 的值以動態修改元件內的樣式,這樣做有以下幾種弊端:

  1. 每加一種主題就要編譯一套less,儘管有很多重複的樣式程式碼。
  2. 使得專案打包後的體積變得越來越臃腫。
  3. 維護起來非常費勁,因為每改一個 antd 元件的通用樣式就要同時去修改每套主題下的 less 檔案對應的相同程式碼,儘管他們只有 prefixCls 不同。
  4. 修改樣式要格外注重樣式權重問題,加重了開發者的心智負擔。
  5. 修改主題可能會花費更多的時間和寫更多的程式碼,因為開發者需要去找到修改樣式的元素的dom層級,然後在樣式檔案中為了權重問題需要寫 full path
  6. ...(我沒經歷過的其他問題)

而採用 CSS-in-JS 這樣的技術方案則會大大改善以上問題的出現:

  1. 首先它是一種將 css 程式碼寫成 JavaScript 物件或函式的方法,因此可以透過使用變數、函式等動態生成樣式,方便響應式設計。
  2. 並且相比 less 它作用域控制更容易,因此很好的解決了命名衝突和樣式權重的問題。
  3. 在一定程度上他解決了一部分效能最佳化的問題,因為減少了請求 css 樣式檔案的 http 請求。

遷移指南

如果專案中沒有除了黑夜\白天模式以外的其他主題,並且沒有適配 IE 瀏覽器的需求,那麼恭喜你,遷移此專案的成本將是非常輕量級的,直接 yarn add antd 升級庫版本,移除 v4 專案中引用到的 antd 內建的檔案(antd/dist/antd.css),之前在 less 檔案中透過 :global 去修改 antd 元件預設樣式的行為同樣會生效,如果之前做了主題切換功能的專案同樣也可以把之前的主題樣式檔案刪了,antd 內建了白天(default)、黑夜模式和緊湊模式的演算法(algorithm)。這個 algorithm 在體現上來看可以理解為風格或者主題吧。那如果要修改單個元件的樣式,比如antd 按鈕預設高度為32,在某個業務場景下,這個按鈕需要改成40,那麼可以修改主題中的 token 去實現這一目的而不用去修改樣式檔案,這個 token 怎麼理解?可以看成是組成一個元件的每個顆粒化的樣式,也就是一些樣式變數名而已。

那麼,為什麼修改 、token 的方案會比之前修改樣式檔案的方案要更優呢?

舉個簡單的例子,我要修改 Select 的高度,以前的做法是修改它的樣式,但是理想中只要修改 height 屬性就行了,但是往往不是,他還有 line-height 等其他樣式會影響到佈局,所以在過去,我們要修改這類元件的樣式,雖然僅僅是想改變高度,但是還是不得已去改變其他肯能會影響到佈局的樣式,比如:

<Select
  className={styles.input}
  style={{ width: 200 }}
  placeholder="請選擇...."
/>
.input {
  height: 40px;
}

效果如下,可以明顯看到input的高度沒變,只是改了外層container的高度,並且圖示也不居中了。

image-20230319153415312

image-20230319153437506

再看一下再v5版本中如何實現改變高度:

<ConfigProvider
  theme={{
    token: {
      controlHeight: 40
    }
  }}
>
  {/* container 只是為了讓 Select 居中顯示 */}
  <div className={styles.container}>
    <Select
      className={styles.input}
      style={{ width: 200 }}
      placeholder="請選擇...."
    />
  </div>
</ConfigProvider>

效果如下,而我只是修改了一個變數值而已,其他的可能會導致佈局的問題我一概不用去管。

image-20230319153725566

如何實現主題切換功能

如果你只是需要實現白天黑夜模式的切換,那非常簡單,因為 antd 內建了白天(預設)和黑夜模式的變數(演算法),只要在專案中根據你的 theme mode 動態傳遞給 ConfigProvider 就行了,如果是全域性生效,你只要在你的入口檔案(jsx 或者 tsx)中配置即可:

<ConfigProvider
  theme={{
    algorithm:
      theme === 'dark'
        ? antdTheme.darkAlgorithm
        : antdTheme.defaultAlgorithm
  }}
>
  <div className={styles.container}>
    <Select
      className={styles.input}
      style={{ width: 200 }}
      placeholder="請輸入...."
    />
  </div>
</ConfigProvider>

如下就是 dark 模式下的 Select 元件樣式:

image-20230319154913216

ConfigProvider 在v4版本中我只會在需要修改一些全域性配置的時候會用到,比如說國際化、配置 prefixCls 等,但是在v5中我會頻繁使用到,因為經常要用它來修改一些 antd 元件的區域性樣式,透過修改 token 變數值的方法,比如以下:

<ConfigProvider
  theme={{
    algorithm:
      theme === 'dark'
        ? antdTheme.darkAlgorithm
        : antdTheme.defaultAlgorithm
  }}
>
  <div className={styles.container}>
    <Select
      className={styles.input}
      style={{ width: 200 }}
      placeholder="請輸入...."
    />
    <ConfigProvider
      theme={{
        components: {
          Button: { controlHeight: 26 }
        }
      }}
    >
      <Button>按鈕</Button>
    </ConfigProvider>
  </div>
</ConfigProvider>

那如何實現更多的自定義主題呢?其實思路跟v4是一樣的,修改樣式變數值,只不過在v5中是透過修改js變數,而在以前的版本是修改 less 變數,具體實現方法可以參考 antd v5 版本中 default(對應原始碼 components/theme/themes/default(或者dark)),本質上是覆寫變數值,只是變數名需要一一對應起來。

開發時使用技巧

使用v5版本這麼長時間以來,除了在 nextjs 專案中最初版本會有水合問題以外,最大的問題就是你想去修改某個元件的某個區域性樣式,但是不知道對應的變數名,我的建議是 clone 一份 antd 最新版本的原始碼,在不知道變數名(token)的時候去原始碼中 components 目錄下找到你要的元件,然後找到其下的 style 目錄,找到對應樣式下它引用的 token 屬性名,以 Button 元件的背景色為例:

image-20230319160829808

那麼,修改 colorBgContainer 就ok了:

<ConfigProvider
  theme={{
    token: {
      colorPrimary: '#FF9B50', // 主題色
      colorBgContainer: '#FF9B50' // 寫在這裡跟寫在下面都一樣
    },
    components: {
      Button: { controlHeight: 50, colorBgContainer: '#FF9B50' }
    }
  }}
>
  <Button className={styles.btn} onClick={handleDeposit}>
    <span style={{ color: '#fff' }}>
      {isConnected ? `Deposit 3 ETH` : 'Unlock WalletConnect'}
    </span>
  </Button>
</ConfigProvider>

如果你只是針對某個業務場景下的某個元件修改區域性樣式,建議寫在 components 下,如果是某個模組中通用的樣式,則選擇 token,本質上都是修改 token,只是劃分割槽域更細緻化而已。

使用案例

  1. Phalcon:強大的區塊鏈交易瀏覽器
  2. MetaSleuth:加密貨幣資金視覺化分析追蹤工具